Quando é que as variáveis ??de instância de Ruby prepare-se?
-
05-07-2019 - |
Pergunta
class Hello
@hello = "hello"
def display
puts @hello
end
end
h = Hello.new
h.display
Eu criei a classe acima. Ele não imprime nada de fora. Eu pensei que a variável de instância @hello foi definido durante a declaração de classe. Mas quando eu chamar o método de exibição a saída é 'nulo'. Qual é a maneira correta de fazer isso?
Solução
As variáveis ??de instância em Ruby pode ser um pouco confuso quando primeiro aprender Ruby, especialmente se você está acostumado a uma outra linguagem OO como Java.
Você não pode simplesmente declarar uma variável de instância.
Uma das coisas mais importantes para saber sobre as variáveis ??de instância em Ruby, além da notação com um prefixo @ sinal, é que que saltar para a vida a primeira vez que são atribuídos a .
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"]
Você pode usar o Object#instance_variables
método para listar todas as variáveis ??de instância de um objeto.
Você normalmente “declarar” e inicializar todas as variáveis ??de instância do método de inicialização. Outra maneira de claramente documento que variáveis ??de instância que deve estar disponível ao público é usar o attr_accessor
métodos Módulo (leitura / gravação), attr_writer
(escrever) e attr_reader
(leitura). Estes métodos irão sintetizar diferentes métodos de acesso para a variável exemplo listado.
class Hello
attr_accessor :hello
end
h = Hello.new
p h.instance_variables
h.hello = "hello"
p h.instance_variables
# Output
[]
["@hello"]
A variável de instância ainda não é criado até que seja atribuído a utilizar o método Hello#hello=
sintetizado.
Outra questão importante, como kch descrito, é que você precisa estar ciente dos diferentes contextos ativos quando declarar uma classe. Ao declarar uma classe a receptor padrão (auto) no escopo externo será o objeto que representa a classe em si. Daí o seu código vai primeiro criar uma variável de instância de classe ao atribuir a @hello
no nível de classe.
métodos dentro de auto será o objeto no qual o método é chamado, portanto, você está tentando imprimir o valor de uma variável de instância com o @hello
nome no objeto, que não existe ( nota que é perfeitamente legal ler uma variável de instância não existente).
Outras dicas
Você precisa adicionar um método initialize
:
class Hello
def initialize
@hello = "hello"
end
def display
puts @hello
end
end
h = Hello.new
h.display
O primeiro @hello
em seu código é chamado de variável de instância de classe.
É uma variável de instância do objeto de classe que os pontos constantes Hello
para. (E que é uma instância da classe Class
.)
Tecnicamente, quando você está dentro do escopo class
, seu self
está definido para o objeto de sua classe atual e @variables
pertencem ao seu self
atual. Boy eu chupar a explicar essas coisas.
Você pode obter tudo isso e muito mais clarificada para você assistindo este conjunto de US $ 5 cada screencasts de The Pragmatic Programmers .
(Ou você pode pedir esclarecimentos aqui e eu vou tentar atualização.)
há uma descrição clara no livro "A linguagem de programação Ruby", lê-lo será muito útil. I colá-lo aqui (a partir do capítulo 7.1.16):
Uma variável de instância usado dentro de uma definição de classe, mas um fora definição método de instância é uma variável de instância class .
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
......
Como as variáveis ??de instância de classe são apenas variáveis ??de instância de classe objetos, podemos usar attr, attr_reader e attr_accessor para criar métodos de acesso para eles.
class << self
attr_accessor :n, :totalX, :totalY
end
Com estes acessores definido, podemos nos referir aos nossos dados brutos como Point.n, Point.totalX e Point.totalY.
Eu tinha esquecido que havia um conceito de "classe variável de instância" em Ruby. Em qualquer caso, o problema do OP parecia intrigante, e não foi realmente abordada em nenhuma das respostas até então, com exceção de uma dica na resposta de kch: é um problema do escopo. (Adicionado em edit: Na verdade, a resposta de IRSs faz endereço este ponto no final, mas eu vou deixar esta resposta se de qualquer maneira, como eu acho que o código de exemplo pode ser útil para a compreensão do problema.)
Em uma classe Ruby, um nome de variável começando com @
pode se referir a um dos dois variáveis: ou um variável de instância ou instância de classe variável , dependendo de onde na classe é referido. Esta é uma pegadinha bastante sutil.
Um exemplo irá esclarecer o ponto. Aqui está uma pequena classe de teste Ruby (todo o código testado no 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
I chamado as variáveis ??de acordo com o que eu pensei que eles eram, embora isso acaba por não para sempre ser o caso:
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
O @@class_variable
e @instance_variable
sempre se comportam como você esperaria: o primeiro é definido no nível de classe, e se referida em um método de classe ou em um método de instância, que detém valor atribuído a ele no topo. Este último só recebe um valor em um objeto de T
classe, portanto, em um método de classe, ele se refere a uma variável desconhecida cujo valor é nil
.
O método de classe imaginativamente chamado saídas class_method
os valores de @@class_variable
e os dois @class_instance_variable
s como esperado, isto é, como inicializado no topo da classe. No entanto, na initialize
métodos de instância e instance_method
, diferentes variáveis ?? com o mesmo nome são acessados, ou seja, as variáveis ?? exemplo, não de classe variáveis ??de instância .
Você pode ver que a atribuição no método initialize
não afetou a @class_instance_variable_1
variável de instância de classe, porque a chamada mais tarde de class_method
gera o seu valor antigo, "WTF"
. Em vez disso, o método initialize
declarou uma nova variável de instância, um que é também chamado (erroneamente) @class_instance_variable_1
. O valor atribuído a ele, "wtf"
, é emitido por métodos initialize
e instance_method
.
O @class_instance_variable_2
variável no código de exemplo é equivalente a @hello
variável no problema original: é declarada e inicializada como uma variável de instância de classe, mas quando um método de instância refere-se a uma variável com esse nome, ele realmente vê um variável de instância com o mesmo nome -. uma que nunca foi declarado, por isso seu valor é nulo
Eu também recomendo olhando para variáveis ??de classe que são prefixados com "@@" - aqui está um código de exemplo para mostrar como classe e instância vars são 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