web-dev-qa-db-fra.com

Quand les variables d'instance Ruby sont-elles définies?

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

h = Hello.new
h.display

J'ai créé la classe ci-dessus. Il n'imprime rien. Je pensais que la variable d'instance @hello a été définie lors de la déclaration de classe. Mais lorsque j'appelle la méthode d'affichage, la sortie est "nulle". Quelle est la bonne façon de procéder?

65
pez_dispenser

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

Vous ne pouvez pas simplement déclarer une variable d'instance.

Une des choses les plus importantes à savoir sur les variables d'instance dans Ruby, à part la notation avec un préfixe @, est que elles prennent vie la première fois qu'elles sont affecté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 répertorier 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 (Lecture) . Ces méthodes synthétiseront différentes méthodes d'accesseur pour la variable d'instance répertorié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 n'est toujours pas créée tant qu'elle n'est pas affectée à l'utilisation de la méthode Hello#hello= Synthétisée.

Un autre problème important, comme décrit 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 (self) dans la portée la plus externe 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.

Les méthodes internes self seront l'objet sur lequel la méthode est invoquée, donc 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).

91
sris

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
42
Nathan Kitchen

La première @hello dans votre code s'appelle une variable d'instance de classe.

Il s'agit d'une variable d'instance de l'objet classe vers laquelle pointe la constante Hello. (et qui est une instance de la classe Class.)

Techniquement, lorsque vous êtes dans l'étendue class, votre self est défini sur l'objet de votre classe actuelle et @variables se rapportent à votre self actuel. Je suis nul à expliquer ces choses.

Vous pouvez obtenir tout cela et bien plus de détails pour vous en regardant cette collection de screencasts de 5 $ chacun des programmeurs pragmatiques .

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

22
kch

il y a une description claire dans le livre "Le langage de programmation Ruby"), lisez-le sera très utile. Je le colle ici (à partir 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 un variable d'instance de classe.

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 ne sont que des variables d'instance d'objets de classe, nous pouvons utiliser attr, attr_reader et attr_accessor pour créer des méthodes d'accesseur pour elles.

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

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

10
lfx_cool

J'avais oublié qu'il y avait un concept de "variable d'instance de classe" dans Ruby. Dans tous les cas, le problème du PO semblait déroutant, et n'était pas vraiment abordé dans aucune des réponses jusqu'ici, à l'exception d'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 aborde ce point à la fin, mais je laisserai cette réponse de toute façon, comme je pense que l'exemple le code peut être utile pour comprendre le problème.)

Dans une classe Ruby, un nom de variable commençant par @ peut faire référence à l'une des deux variables: soit à une variable d'instance soit à une variable d'instance de classe, selon l'endroit dans la classe auquel il est fait référence. C'est un gotcha assez subtil.

Un exemple clarifiera le point. Voici un petit Ruby classe de test (tout le code testé 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

J'ai nommé les variables selon ce que je pensais qu'elles étaient, bien que cela ne soit pas 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

Le @@class_variable et @instance_variable se comportent toujours comme prévu: la première est définie au niveau de la classe et, qu'elle soit référencée dans une méthode de classe ou dans une méthode d'instance, elle contient la valeur qui lui est affectée en haut. Ce dernier n'obtient qu'une valeur dans un objet de 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 nommée avec imagination class_method affiche les valeurs de @@class_variable et les deux @class_instance_variables comme prévu, c'est-à-dire initialisé en haut de la classe. Cependant, dans les méthodes d'instance initialize et instance_method, différentes variables du même nom sont accessibles, c'est-à-dire , variables d'instance, pas les variables d'instance de classe.

Vous pouvez voir que l'affectation dans la méthode initialize n'a pas affecté la variable d'instance de classe @class_instance_variable_1, car l'appel ultérieur de class_method sort 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 (de manière trompeuse) @class_instance_variable_1. La valeur qui lui est attribuée, "wtf", est généré par les méthodes initialize et instance_method.

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

5
Teemu Leisti

Je recommanderais également de regarder les variables de classe qui sont préfixées avec "@@" - voici un exemple de code pour vous montrer comment les variables 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
1
Steve Midgley