Когда устанавливаются переменные экземпляра Ruby?
class Hello
@hello = "hello"
def display
puts @hello
end
end
h = Hello.new
h.display
Я создал класс выше. Это ничего не распечатывает. Я думал, что переменная экземпляра @hello была установлена во время объявления класса. Но когда я вызываю метод display, вывод равен 'nil'. Какой правильный способ сделать это?
6 ответов
Переменные экземпляра в ruby могут немного сбивать с толку при первом изучении Ruby, особенно если вы привыкли к другому языку OO, например Java.
Вы не можете просто объявить переменную экземпляра.
Одна из самых важных вещей, которые нужно знать о переменных экземпляра в ruby, кроме записи с префиксом @ sign, заключается в том, что они возникают при первом назначении.
class Hello
def create_some_state
@hello = "hello"
end
end
h = Hello.new
p h.instance_variables
h.create_some_state
p h.instance_variables
# Output
[]
["@hello"]
Вы можете использовать метод Object#instance_variables
перечислить все переменные экземпляра объекта.
Обычно вы "объявляете" и инициализируете все переменные экземпляра в методе initialize. Другой способ четко задокументировать, какие переменные экземпляра должны быть общедоступными, - это использовать методы Module. attr_accessor
(читай пиши), attr_writer
(написать) и attr_reader
(читать). Эти методы будут синтезировать различные методы доступа для указанной переменной экземпляра.
class Hello
attr_accessor :hello
end
h = Hello.new
p h.instance_variables
h.hello = "hello"
p h.instance_variables
# Output
[]
["@hello"]
Переменная экземпляра по-прежнему не создается до тех пор, пока она не назначена для использования синтезированного Hello#hello=
метод.
Другая важная проблема, подобная описанной в kch, заключается в том, что вам нужно знать о различных активных контекстах при объявлении класса. При объявлении класса получателем по умолчанию (self) во внешней области видимости будет объект, который представляет сам класс. Следовательно, ваш код сначала создаст переменную экземпляра класса при назначении @hello
на уровне класса.
Внутри методов self будет объектом, для которого вызывается метод, поэтому вы пытаетесь напечатать значение переменной экземпляра с именем @hello
в объекте, который не существует (обратите внимание, что вполне допустимо читать несуществующую переменную экземпляра).
Вам нужно добавить initialize
метод:
class Hello
def initialize
@hello = "hello"
end
def display
puts @hello
end
end
h = Hello.new
h.display
Первый @hello
в вашем коде называется переменная экземпляра класса.
Это переменная экземпляра объекта класса, который константа Hello
указывает на. (и который является экземпляром класса Class
.)
Технически, когда вы находитесь в пределах class
сфера, ваш self
устанавливается на объект вашего текущего класса, и @variables
относятся к вашему нынешнему self
, Мальчик, я отстой в объяснении этих вещей.
Вы можете получить все это и многое другое прояснить для вас, посмотрев эту коллекцию 5-долларовых скринкастов от The Pragmatic Programmers.
(Или вы можете попросить разъяснения здесь, и я постараюсь обновить.)
В книге "Язык программирования ruby" есть четкое описание, прочитать его будет очень полезно. Я вставляю это здесь (из главы 7.1.16):
Переменная экземпляра, используемая внутри определения класса, но вне определения метода экземпляра, является переменной экземпляра класса.
class Point
# Initialize our class instance variables in the class definition itself
@n = 0 # How many points have been created
@totalX = 0 # The sum of all X coordinates
@totalY = 0 # The sum of all Y coordinates
def initialize(x,y) # Initialize method
@x,@y = x, y # Sets initial values for instance variables
end
def self.new(x,y) # Class method to create new Point objects
# Use the class instance variables in this class method to collect data
@n += 1 # Keep track of how many Points have been created
@totalX += x # Add these coordinates to the totals
@totalY += y
super # Invoke the real definition of new to create a Point
# More about super later in the chapter
end
# A class method to report the data we collected
def self.report
# Here we use the class instance variables in a class method
puts "Number of points created: #@n"
puts "Average X coordinate: #{@totalX.to_f/@n}"
puts "Average Y coordinate: #{@totalY.to_f/@n}"
end
end
......
Поскольку переменные экземпляра класса являются просто переменными экземпляра объектов класса, мы можем использовать attr, attr_reader и attr_accessor для создания для них методов доступа.
class << self
attr_accessor :n, :totalX, :totalY
end
Определив эти методы доступа, мы можем ссылаться на наши необработанные данные как Point.n, Point.totalX и Point.totalY.
Я забыл, что в Ruby существует концепция "переменная экземпляра класса". В любом случае, проблема ОП казалась загадочной, и до сих пор ее не решали ни в одном из ответов, за исключением подсказки в ответе КЧ: это проблема охвата. (Добавлено при редактировании: на самом деле ответ sris в конце концов решает эту проблему, но я все равно оставлю этот ответ в силе, так как думаю, что пример кода может быть полезен для понимания проблемы.)
В классе Ruby имя переменной начинается с @
может ссылаться на одну из двух переменных: либо на переменную экземпляра, либо на переменную экземпляра класса, в зависимости от того, где в классе он упоминается. Это довольно тонкий вопрос.
Пример прояснит суть. Вот небольшой тестовый класс Ruby (весь код протестирован в irb):
class T
@@class_variable = "BBQ"
@class_instance_variable_1 = "WTF"
@class_instance_variable_2 = "LOL"
def self.class_method
puts "@@class_variable == #{@@class_variable || 'nil'}"
puts "@class_instance_variable_1 == #{@class_instance_variable_1 || 'nil'}"
puts "@class_instance_variable_2 == #{@class_instance_variable_2 || 'nil'}"
puts "@instance_variable == #{@instance_variable || 'nil'}"
end
def initialize
@instance_variable = "omg"
# The following line does not assign a value to the class instance variable,
# but actually declares an instance variable withthe same name!
@class_instance_variable_1 = "wtf"
puts "@@class_variable == #{@@class_variable || 'nil'}"
# The following two lines do not refer to the class instance variables,
# but to the instance variables with the same names.
puts "@class_instance_variable_1 == #{@class_instance_variable_1 || 'nil'}"
puts "@class_instance_variable_2 == #{@class_instance_variable_2 || 'nil'}"
puts "@instance_variable == #{@instance_variable || 'nil'}"
end
def instance_method
puts "@@class_variable == #{@@class_variable || 'nil'}"
# The following two lines do not refer to the class instance variables,
# but to the instance variables with the same names.
puts "@class_instance_variable_1 == #{@class_instance_variable_1 || 'nil'}"
puts "@class_instance_variable_2 == #{@class_instance_variable_2 || 'nil'}"
puts "@instance_variable == #{@instance_variable || 'nil'}"
end
end
Я назвал переменные в соответствии с тем, что, как я думал, они были, хотя оказывается, что это не всегда так:
irb> T.class_method
@@class_variable == BBQ
@class_instance_variable_1 == WTF # the value of the class instance variable
@class_instance_variable_2 == LOL # the value of the class instance variable
@instance_variable == nil # does not exist in the class scope
=> nil
irb> t = T.new
@@class_variable == BBQ
@class_instance_variable_1 == wtf # the value of the instance variable
@class_instance_variable_2 == nil # the value of the instance variable
@instance_variable == omg
=> #<T:0x000000015059f0 @instance_variable="omg", @class_instance_variable_1="wtf">
irb> t.instance_method
@@class_variable == BBQ
@class_instance_variable_1 == wtf # the value of the instance variable
@class_instance_variable_2 == nil # the value of the instance variable
@instance_variable == omg
=> nil
irb> T.class_method
@@class_variable == BBQ
@class_instance_variable_1 == WTF # the value of the class instance variable
@class_instance_variable_2 == LOL # the value of the class instance variable
@instance_variable == nil # does not exist in the class scope
=> nil
@@class_variable
а также @instance_variable
всегда ведите себя так, как вы ожидаете: первый определяется на уровне класса, и независимо от того, упоминается ли он в методе класса или в методе экземпляра, он содержит значение, присвоенное ему в верхней части. Последний только получает значение в объекте класса T
поэтому в методе класса он ссылается на неизвестную переменную, значение которой равно nil
,
Метод класса с оригинальным именем class_method
выводит значения @@class_variable
и два @class_instance_variable
s как положено, то есть как инициализировано в верхней части класса. Однако в экземпляре методы initialize
а также instance_method
Доступ к различным переменным с одинаковым именем, то есть переменные экземпляра, а не переменные экземпляра класса.
Вы можете видеть, что назначение в initialize
метод не влияет на переменную экземпляра класса @class_instance_variable_1
потому что поздний зов class_method
выводит свое старое значение, "WTF"
, Вместо этого метод initialize
объявил новую переменную экземпляра, которая также названа (вводит в заблуждение) @class_instance_variable_1
, Значение, присвоенное ему, "wtf"
, выводится методами initialize
а также instance_method
,
Переменная @class_instance_variable_2
в примере код эквивалентен переменной @hello
в исходной задаче: он объявлен и инициализирован как переменная экземпляра класса, но когда метод экземпляра ссылается на переменную с таким именем, он фактически видит переменную экземпляра с тем же именем - той, которая никогда не объявлялась, поэтому его значение ноль.
Я также рекомендовал бы посмотреть на переменные класса с префиксом "@@" - вот несколько примеров кода, чтобы показать вам, как различаются классы и экземпляры:
class Vars
@@classvar="foo"
def test
@instancevar="bar"
end
def Vars.show
puts "classvar: #{@@classvar}"
puts "instancevar: #{@instancevar}"
end
def instance_show
puts "classvar: #{@@classvar}"
puts "instancevar: #{@instancevar}"
end
end
# only shows classvar since we don't have an instance created
Vars::show
# create a class instance
vars = Vars.new
# instancevar still doesn't show b/c it hasn't been initialized
vars.instance_show
# initialize instancevar
vars.test
# now instancevar shows up as we expect
vars.instance_show