Когда устанавливаются переменные экземпляра Ruby?

class Hello
@hello = "hello"
    def display
        puts @hello
    end
end

h = Hello.new
h.display

Я создал класс выше. Ничего не распечатывает. Я думал, что переменная экземпляра @hello была установлена ​​во время объявления класса. Но когда я вызываю метод отображения, результат равен нулю. Как правильно это сделать?


person pez_dispenser    schedule 05.05.2009    source источник


Ответы (6)


Переменные экземпляра в ruby ​​могут немного сбивать с толку при первом изучении Ruby, особенно если вы привыкли к другому объектно-ориентированному языку, такому как Java.

Вы не можете просто объявить переменную экземпляра.

Одна из самых важных вещей, которые нужно знать о переменных экземпляра в ruby, помимо записи с префиксом знака @, заключается в том, что они появляются в жизни при первом назначении.

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 для вывода списка всех переменных экземпляра объекта.

Обычно вы «объявляете» и инициализируете все переменные экземпляра в методе инициализации. Еще один способ четко задокументировать, какие переменные экземпляра должны быть общедоступными, — это использовать методы модуля 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 в несуществующем объекте (обратите внимание, совершенно законно читать несуществующую переменную экземпляра).

person sris    schedule 06.05.2009
comment
Вы говорите, что они оживают при первом назначении, но ОП показал пример с (очевидным) назначением раньше, чем в вашем примере, и проблема заключается в том, что указанная переменная таким образом не возникла. - person kaleidic; 31.07.2014
comment
@kaleidic Точно. Я немного озадачен количеством голосов за этот ответ, который не касается вопроса ОП. На самом деле, переменная экземпляра класса @hello действительно возникает в строке 2 примера кода, но проблема в том, что это не та переменная, на которую ссылается строка 4. Смотрите мой ответ ниже для получения дополнительной информации. - person Teemu Leisti; 25.12.2014
comment
Извините, вы действительно отвечаете на вопрос в конце, который я умудрился пропустить при первом чтении. - person Teemu Leisti; 26.12.2014
comment
мой вывод [:@hello] как это? - person kouty; 11.12.2016

Вам нужно добавить метод initialize:

class Hello
    def initialize
        @hello = "hello"
    end
    def display
        puts @hello
    end
end

h = Hello.new
h.display
person Nathan Kitchen    schedule 05.05.2009

Первый @hello в вашем коде называется переменной экземпляра класса.

Это переменная экземпляра объекта класса, на который указывает константа Hello. (и который является экземпляром класса Class.)

Технически, когда вы находитесь в пределах class, ваш self устанавливается на объект вашего текущего класса, а @variables относится к вашему текущему self. Мальчик, я хреново объясняю эти вещи.

Все это и многое другое вы можете прояснить для себя, посмотрев эта коллекция скринкастов по 5 долларов за штуку от The Pragmatic Programmers.

(Или вы можете попросить разъяснений здесь, и я постараюсь обновить.)

person kch    schedule 05.05.2009
comment
Хорошая статья разработка переменных экземпляра уровня класса. - person Alexander; 14.02.2014

в книге "Язык программирования 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.

person lfx_cool    schedule 11.11.2010

Я забыл, что в Ruby существует концепция «переменной экземпляра класса». В любом случае проблема ОП казалась загадочной и до сих пор не рассматривалась ни в одном из ответов, за исключением подсказки в ответе 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, как и ожидалось, то есть как инициализировано в верхней части класса. Однако в методах экземпляра 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 в исходной задаче: она объявлена ​​и инициализирована как переменная экземпляра класса, но когда метод экземпляра ссылается на переменную с таким именем, он фактически видит переменную экземпляра с то же имя -- имя, которое никогда не объявлялось, поэтому его значение равно нулю.

person Teemu Leisti    schedule 25.12.2014

Я бы также рекомендовал посмотреть переменные класса, которые имеют префикс "@@" - вот пример кода, чтобы показать вам, чем отличаются переменные класса и экземпляра:

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
person Steve Midgley    schedule 20.02.2011