2009-05-05 7 views
61
class Hello 
@hello = "hello" 
    def display 
     puts @hello 
    end 
end 

h = Hello.new 
h.display 

J'ai créé la classe ci-dessus. Cela n'imprime rien. Je pensais que la variable d'instance @hello était définie lors de la déclaration de classe. Mais quand j'appelle la méthode d'affichage, la sortie est 'nulle'. Quelle est la bonne façon de faire cela?Quand les variables d'instance Ruby sont-elles définies?

Répondre

88

Les variables d'instance en rubis peut être un peu déroutant lors de la première apprentissage Ruby, surtout si vous êtes habitué à un autre langage OO comme Java .

Vous ne pouvez pas simplement déclarer une variable d'instance. L'une des choses les plus importantes à connaître sur les variables d'instance dans ruby, à l'exception de la notation avec un préfixe @, est que elles prennent leur source la première fois qu'elles sont assignées à.

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"] 

Vous pouvez utiliser la méthode Object#instance_variables pour lister toutes les variables d'instance d'un objet.

Normalement, vous "déclarez" et initialisez toutes les variables d'instance dans la méthode initialize. Une autre façon de documenter clairement les variables d'instance qui devraient être accessibles au public est d'utiliser les méthodes du module attr_accessor (lecture/écriture), attr_writer (écriture) et attr_reader (lire). Ces méthodes vont synthétiser différentes méthodes d'accès pour la variable d'instance listée.

class Hello 
    attr_accessor :hello 
end 

h = Hello.new 
p h.instance_variables 

h.hello = "hello" 
p h.instance_variables 

# Output 
[] 
["@hello"] 

La variable d'instance est toujours pas créé jusqu'à ce qu'il soit affecté à l'aide de la méthode Hello#hello= synthétisé.

Un autre problème important, comme décrit par kch, est que vous devez être conscient des différents contextes actifs lors de la déclaration d'une classe. Lors de la déclaration d'une classe, le récepteur par défaut (auto-) dans la portée la plus extérieure sera l'objet qui représente la classe elle-même. Par conséquent, votre code créera d'abord une variable d'instance de classe lors de l'affectation à @hello au niveau de la classe.

méthodes intérieur auto fera l'objet sur lequel la méthode est appelée, d'où vous essayez d'imprimer la valeur d'une variable d'instance avec le nom @hello dans l'objet, qui n'existe pas (notez qu'il est parfaitement légal de lire une variable d'instance non existante).

+2

Vous dites "ils entrent dans la vie la première fois qu'ils sont assignés" mais l'OP a montré un exemple avec affectation (apparente) plus tôt que dans votre exemple, et le problème rencontré est que cette variable n'est pas née dans la vie. – kaleidic

+1

@kaleidic Exactement. Je suis un peu perplexe face au nombre de votes positifs sur cette réponse qui ne répond pas à la question du PO. En fait, la variable d'instance de classe '@ hello' * naît dans la ligne 2 du code d'exemple, mais le problème est que ce n'est pas la variable à laquelle la ligne 4 fait référence. Voir ma réponse ci-dessous pour plus de détails. –

+0

Désolé, vous répondez réellement à la question à la fin, ce que j'ai réussi à manquer en première lecture. –

42

Vous devez ajouter une méthode initialize:

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

h = Hello.new 
h.display 
21

Le premier @hello dans votre code est appelé une variable d'instance de classe.

C'est une variable d'instance de l'objet de classe sur laquelle pointe la constante Hello. (Et qui est une instance de la classe Class.)

Techniquement, lorsque vous êtes dans le champ class, votre self est réglé sur l'objet de votre classe actuelle et @variables solutionne votre self actuelle. Boy, je crains d'expliquer ces choses.

Vous pouvez obtenir tout cela et beaucoup plus précisé en regardant this collection of $5-each screencasts from The Pragmatic Programmers.

(Ou vous pouvez demander des éclaircissements ici et je vais essayer de mettre à jour.)

+0

Un bon [article] (http://www.railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/) élaborant des variables d'instance de niveau classe. –

9

il y a une description claire dans le livre "Le langage de programmation ruby", lire ce sera très utile. Je colle ici (du chapitre 7.1.16):

Une variable d'instance utilisée dans une définition de classe, mais en dehors d'une définition de méthode d'instance est une instance de classe variables.

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 

......

Parce que les variables d'instance de classe sont des variables seulement d'instance d'objets de classe , nous pouvons utiliser attr, attr_reader et attr_accessor pour créer méthodes accesseurs pour les .

class << self 
    attr_accessor :n, :totalX, :totalY 
end 

Avec ces accesseurs définis, nous pouvons nous référer à nos données brutes comme Point.n, Point.totalX et Point.totalY.

1

Je recommande aussi regarder des variables de classe qui sont préfixées avec « @@ » - est ici un exemple de code pour vous montrer comment vars de classe et d'instance sont différentes:

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 
4

J'avais oublié qu'il y était un concept de "variable d'instance de classe" dans Ruby. En tout cas, le problème de l'OP semblait déroutant, et n'était pas vraiment abordé dans aucune des réponses jusqu'à présent, sauf pour un indice dans la réponse de kch: c'est un problème de portée. (Ajouté sur edit: En fait, la réponse de sris adresse ce point à la fin, mais je laisserai cette réponse de toute façon, car je pense que l'exemple de code pourrait être utile pour comprendre le problème.)

Dans un classe Ruby, un nom de variable commençant par @ peut se référer à l'un des deux variables soit à une variable instance ou à une instance de classe variables, selon l'endroit où dans la classe il est appelé. C'est un gotcha assez subtile.

Un exemple clarifiera le point. Voici une petite classe de test Ruby (tous code testé RIR):

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 

que je peux nommer les variables d'après ce que je pensais qu'ils étaient, mais qui se révèle ne pas être toujours le cas:

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 

Les @@class_variable et @instance_variable se comportent toujours comme vous vous y attendez: le premier est défini au niveau de la classe et, qu'il soit référencé dans une méthode de classe ou dans une méthode d'instance, il contient la valeur qui lui est assignée en haut. Ce dernier n'a de valeur que dans un objet de la classe T, donc dans une méthode de classe, il fait référence à une variable inconnue dont la valeur est nil.

La méthode de classe imaginative nommée class_method génère les valeurs de @@class_variable et les deux @class_instance_variable s comme prévu, c'est-à-dire, comme initialisé en haut de la classe. Cependant, dans les méthodes d'instance initialize et instance_method, différentes variablesdu même nom sont accessibles, c'est variables d'instance, et non pas des variables d'instance de classe.

Vous pouvez voir que l'affectation dans la méthode initialize n'a pas affecté l'instance de classe variable de @class_instance_variable_1, parce que l'appel ultérieur de class_method émet son ancienne valeur, "WTF". Au lieu de cela, la méthode initializea déclaré une nouvelle variable d'instance, celle qui est également nommée (à tort) @class_instance_variable_1. La valeur qui lui est affectée, "wtf", est sortie par les méthodes initialize et instance_method.

La @class_instance_variable_2 variable dans le code exemple est équivalent à la variable @hello dans le problème d'origine: il est déclaré et initialisé comme une variable d'instance de classe, mais quand une méthode d'instance fait référence à une variable de ce nom, il voit effectivement une instance variable du même nom - une qui n'a jamais été déclarée, sa valeur est donc nulle.

+0

seule cette réponse a du sens :) – InQusitive

+0

@InQusitive Content d'être utile! –

Questions connexes