¿Cuándo se establecen las variables de instancia de Ruby?
-
05-07-2019 - |
Pregunta
class Hello
@hello = "hello"
def display
puts @hello
end
end
h = Hello.new
h.display
He creado la clase anterior. No imprime nada. Pensé que la variable de instancia @hello se estableció durante la declaración de clase. Pero cuando llamo al método de visualización, la salida es 'nil'. ¿Cuál es la forma correcta de hacer esto?
Solución
Las variables de instancia en ruby ??pueden ser un poco confusas cuando aprendes Ruby por primera vez, especialmente si estás acostumbrado a otro lenguaje OO como Java.
No puedes simplemente declarar una variable de instancia.
Una de las cosas más importantes que debe saber acerca de las variables de instancia en ruby, aparte de la notación con el prefijo de signo @, es que cobran vida la primera vez que se las asigna .
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"]
Puede utilizar el método Object # instance_variables
para enumerar todas las variables de instancia de un objeto.
Normalmente, "declara" e inicializa todas las variables de instancia en el método de inicialización. Otra forma de documentar claramente qué variables de instancia deberían estar disponibles públicamente es usar los métodos del Módulo attr_accessor
(lectura / escritura), attr_writer
(escribir) y attr_reader (leído). Estos métodos sintetizarán diferentes métodos de acceso para la variable de instancia listada.
class Hello
attr_accessor :hello
end
h = Hello.new
p h.instance_variables
h.hello = "hello"
p h.instance_variables
# Output
[]
["@hello"]
La variable de instancia aún no se crea hasta que se asigna para usar el método sintetizado Hello # hello =
.
Otro problema importante, como lo describe kch, es que debe ser consciente de los diferentes contextos activos al declarar una clase. Al declarar una clase, el receptor predeterminado (self) en el ámbito más externo será el objeto que representa la clase en sí. Por lo tanto, su código creará primero una variable de instancia de clase cuando se asigne a @hello
en el nivel de clase.
Dentro de los métodos self será el objeto sobre el que se invoca el método, por lo tanto, está intentando imprimir el valor de una variable de instancia con el nombre @hello
en el objeto, que no existe (tenga en cuenta que es perfectamente legal leer una variable de instancia no existente).
Otros consejos
Debe agregar un método de initialize
:
class Hello
def initialize
@hello = "hello"
end
def display
puts @hello
end
end
h = Hello.new
h.display
El primer @hello
en su código se llama una variable de instancia de clase.
Es una variable de instancia del objeto de clase a la que apunta la constante Hello
. (y que es una instancia de la clase Class
.)
Técnicamente, cuando estás dentro del ámbito de class
, tu self
se establece en el objeto de tu clase actual y @variables
pertenece a tu self
actual. Chico, apesto en explicar estas cosas.
Puedes aclarar todo esto y mucho más viendo esta colección de $ 5, cada una de las escenas de The Pragmatic Programmers .
(O puede solicitar aclaraciones aquí y trataré de actualizar).
hay una descripción clara en el libro " El lenguaje de programación ruby ??" ;, leerlo será muy útil. Lo pego aquí (del capítulo 7.1.16):
Una variable de instancia utilizada dentro de una definición de clase pero fuera de una la definición del método de instancia es una variable de instancia de clase .
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
......
Porque las variables de instancia de clase son solo variables de instancia de clase objetos, podemos usar attr, attr_reader, y attr_accessor para crear métodos de acceso para ellos.
class << self
attr_accessor :n, :totalX, :totalY
end
Con estos accesores definidos, podemos referirnos a nuestros datos en bruto como Point.n, Point.totalX y Point.totalY.
Olvidé que había una " variable de instancia de clase " concepto en ruby. En cualquier caso, el problema del OP parecía desconcertante, y en realidad no se abordó en ninguna de las respuestas anteriores, excepto por un indicio en la respuesta de kch: es un problema de alcance. (Agregado en la edición: en realidad, la respuesta de sris hace aborda este punto al final, pero de todos modos dejaré que esta respuesta sea válida, ya que creo que el código de ejemplo podría ser útil para comprender el problema). / p>
En una clase de Ruby, un nombre de variable que comienza con @
puede referirse a una de dos variables: ya sea a una variable de instancia o a una variable de instancia de clase , según el lugar al que se haga referencia en la clase. Este es un gotcha bastante sutil.
Un ejemplo aclarará el punto. Aquí hay una pequeña clase de prueba de Ruby (todo el código probado en 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
Nombré las variables según lo que pensé que eran, aunque eso no siempre es así:
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
El @@ class_variable
y @instance_variable
siempre se comportan como cabría esperar: el primero se define en el nivel de clase, y si se hace referencia en un método de clase o en un método de instancia, mantiene el valor asignado en la parte superior. Este último solo obtiene un valor en un objeto de la clase T
, por lo que en un método de clase, se refiere a una variable desconocida cuyo valor es nil
.
El método de clase llamado imaginativamente class_method
genera los valores de @@ class_variable
y los dos @class_instance_variable
s como se esperaba, es decir, como inicializado en la parte superior de la clase. Sin embargo, en los métodos de instancia initialize
y instance_method
, se accede a diferentes variables del mismo nombre , es decir, variables de instancia, no variables de instancia de clase .
Puede ver que la asignación en el método initialize
no afectó a la variable de instancia de clase @ class_instance_variable_1
, porque la última llamada de class_method
genera su valor anterior, " WTF "
. En su lugar, el método initialize
declaró una nueva variable de instancia, una que también es también llamada (de manera engañosa) @ class_instance_variable_1
. El valor asignado, " wtf "
, se genera mediante los métodos initialize
y instance_method
.
La variable @ class_instance_variable_2
en el código de ejemplo es equivalente a la variable @hello
en el problema original: se declara y se inicializa como una variable de instancia de clase, pero cuando una instancia el método se refiere a una variable de ese nombre, en realidad ve una variable de instancia con el mismo nombre , una que nunca se declaró, por lo que su valor es nulo.
También recomendaría mirar las variables de clase que tienen el prefijo " @@ " - Aquí hay algunos ejemplos de código para mostrar cómo las vars de clase e instancia son diferentes:
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