Accéder aux variables de classe Ruby avec class_eval et instance_eval
-
26-09-2019 - |
Question
J'ai ce qui suit:
class Test
@@a = 10
def show_a()
puts "a: #{@@a}"
end
class << self
@@b = '40'
def show_b
puts "b: #{@@b}"
end
end
end
Pourquoi le travail suivant:
Test.instance_eval{show_b}
b: 40
=> nil
Mais je ne peux pas accéder @@b
directement?
Test.instance_eval{ @@b }
NameError: uninitialized class variable @@b in Object
De même, le suivant fonctionne
t = Test.new
t.instance_eval{show_a}
a: 10
=> nil
Mais ce qui suit échoue
t.instance_eval{ @@a }
NameError: uninitialized class variable @@a in Object
Je ne comprends pas pourquoi je ne peux pas accéder aux variables de classe directement à partir du instance_eval
blocs.
La solution
Je viens de poser la même question à Matz lors de la fête Rubykaigi. J'étais à moitié dessinée, mais il était parfaitement sobre, donc vous pouvez le considérer comme la réponse définitive.
Anton a raison - la raison pour laquelle vous ne pouvez pas accéder aux variables de classe via instance_eval () est "simplement parce que". Même class_eval () partage le même problème (Matz lui-même n'était pas totalement sûr de class_eval () jusqu'à ce que je lui dise que je l'avais déjà essayé). Plus précisément: en termes d'étendue, les variables de classe ressemblent plus à des constantes que les variables d'instance, donc le changement de soi (comme instance_eval () et class_eval () ne fera pas de différence lorsqu'il s'agit d'y accéder.
En général, il pourrait être une bonne idée d'éviter complètement les variables de classe.
Autres conseils
ÉDITER: Le code ci-dessous a été testé avec 1.8.7 et 1.9.1 ... il semble que la situation soit à nouveau différente avec 1.9.2: /
La situation n'est en fait pas si simple. Il existe des différences de comportement selon que vous utilisez 1.8 ou 1.9 et si vous utilisez class_eval
ou instance_eval
.
Les exemples ci-dessous détaillent le comportement dans la plupart des situations.
J'ai également inclus le comportement des constantes, pour faire bonne mesure, car leur comportement est similaire, mais pas exactement la même chose que les variables de classe.
Variables de classe
class_eval
Dans Ruby 1.8:
class Hello
@@foo = :foo
end
Hello.class_eval { @@foo } #=> uninitialized class variable
class_eval
Dans Ruby 1.9:
Hello.class_eval { @@foo } #=> :foo
Donc les variables de classe sommes Regardant les yeux en 1.9 (mais pas en 1,8) lors de l'utilisation class_eval
instance_eval
Dans Ruby 1.8 et 1.9
Hello.instance_eval { @@foo } #=> uninitialized class variable
Hello.new.instance_eval { @@foo } #=> uninitialized class variable
Il semble que les variables de classe soient ne pas levé les yeux en 1.8 ou 1.9 Lors de l'utilisation instance_eval
Ce qui est également intéressant, c'est le cas pour constantes:
Constantes
class_eval
Dans Ruby 1.8
class Hello
Foo = :foo
end
Hello.class_eval { Foo } #=> uninitialized constant
class_eval
Dans Ruby 1.9
Hello.class_eval { Foo } #=> :foo
Donc, comme pour les variables de classe, les constantes sont recherchées en 1,9 mais ne pas en 1,8 pour class_eval
instance_eval
Dans Ruby 1.8
Hello.instance_eval { Foo } #=> uninitialized constant
Hello.new.instance_eval { Foo } #=> uninitialized constant
instance_eval
Dans Ruby 1.9
Hello.instance_eval { Foo } #=> uninitialized constant
Hello.new.instance_eval { Foo } #=> :foo
Il semble que la recherche constante n'est pas tout à fait analogue à la recherche de variables de classe pour Ruby 1.9. UN Hello
exemple Est-ce que avoir accès à la constante pendant que le Hello
La classe ne le fait pas.
Eh bien, la meilleure réponse est probablement "juste parce que": l'instance_eval en un mot crée une sorte de proc singleton qui est invoqué avec la liaison d'un objet donné. Je suis d'accord que cela semble un peu étrange, mais c'est ce que c'est.
Si vous exécutez instance_eval avec une chaîne, vous obtiendrez même un avertissement que votre méthode essaie d'accéder à la variable de classe:
irb(main):038:0> Test.new.instance_eval "@@a"
(eval):1: warning: class variable access from toplevel singleton method
NameError: (eval):1:in `irb_binding': uninitialized class variable ...
Ruby 2.1
C'est la manière la plus concise et le plus sémantique que j'ai trouvé pour accéder à une variable de classe:
class Hello
@@foo = :foo_value
end
Hello.class_variable_get :@@foo #=> :foo_value