Когда устанавливаются переменные экземпляра Ruby?
-
05-07-2019 - |
Вопрос
class Hello
@hello = "hello"
def display
puts @hello
end
end
h = Hello.new
h.display
Я создал приведенный выше класс.Он ничего не выводит на печать.Я думал, что переменная экземпляра @hello была установлена во время объявления класса.Но когда я вызываю метод display, результат равен "nil".Каков правильный способ сделать это?
Решение
Переменные экземпляра в 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.Другой способ четко документировать, какие переменные экземпляра должны быть общедоступными, - это использовать методы модуля 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
на уровне класса.
Внутренние методы самость будет объектом, для которого вызывается метод, следовательно, вы пытаетесь напечатать значение переменной экземпляра с именем @hello
в объекте, который не существует (обратите внимание, что совершенно законно читать несуществующую переменную экземпляра).
Другие советы
Вам нужно добавить initialize
способ:
class Hello
def initialize
@hello = "hello"
end
def display
puts @hello
end
end
h = Hello.new
h.display
Первый @hello
в вашем коде это называется переменной экземпляра класса.
Это переменная экземпляра класса object, константа которого Hello
указывает на.(и который является экземпляром класса Class
.)
Технически, когда вы находитесь в пределах class
сфера применения, ваш self
устанавливается на объект вашего текущего класса, и @variables
относятся к вашему текущему self
.Боже, я не умею объяснять такие вещи.
Вы можете прояснить все это и многое другое, наблюдая эта коллекция скринкастов стоимостью по 5 долларов за штуку от Прагматичных программистов.
(Или вы можете попросить разъяснений здесь, и я постараюсь внести изменения.)
в книге "Язык программирования 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 существует концепция "переменной экземпляра класса".В любом случае, проблема OP казалась загадочной и на самом деле не рассматривалась ни в одном из предыдущих ответов, за исключением намека в ответе kch:это проблема масштаба.(Добавлено при редактировании:На самом деле, ответ 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