class Hello
@hello = "hello"
def display
puts @hello
end
end
h = Hello.new
h.display
上記のクラスを作成しました。何も出力しません。クラス宣言中にインスタンス変数@helloが設定されていると思いました。しかし、表示メソッドを呼び出すと、出力は「nil」になります。これを行う正しい方法は何ですか?
Rubyのインスタンス変数は、特に別のOO Javaのような言語に慣れている場合は、Rubyを初めて学習するときに少し混乱するかもしれません。
インスタンス変数を単純に宣言することはできません。
Rubyのインスタンス変数について知っておくべき最も重要なことの1つは、@記号プレフィックスの表記法とは別に、それらが最初に割り当てられたときに命を吹き込みますです。
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"]
メソッドObject#instance_variables
を使用して、オブジェクトのすべてのインスタンス変数をリストできます。
通常、initializeメソッドですべてのインスタンス変数を「宣言」して初期化します。どのインスタンス変数を公開する必要があるかを明確に文書化する別の方法は、モジュールメソッドattr_accessor
(読み取り/書き込み)、attr_writer
(書き込み)、attr_reader
(読み取り)を使用することです。これらのメソッドは、リストされたインスタンス変数に対して異なるアクセサーメソッドを合成します。
class Hello
attr_accessor :hello
end
h = Hello.new
p h.instance_variables
h.hello = "hello"
p h.instance_variables
# Output
[]
["@hello"]
インスタンス変数は、合成されたHello#hello=
メソッドの使用に割り当てられるまで作成されません。
説明したkchのような別の重要な問題は、クラスを宣言するときにアクティブなさまざまなコンテキストを認識する必要があるということです。クラスを宣言するとき、最も外側のスコープのdefaultレシーバー(self)は、クラス自体を表すオブジェクトになります。したがって、クラスレベルで@hello
に割り当てる場合、コードは最初にクラスインスタンス変数を作成します。
内部メソッドselfは、メソッドが呼び出されるオブジェクトになるため、@hello
という名前のインスタンス変数の値を出力しようとしています。存在しないオブジェクト(存在しないインスタンス変数を読み取ることは完全に合法であることに注意してください)。
initialize
メソッドを追加する必要があります。
class Hello
def initialize
@hello = "hello"
end
def display
puts @hello
end
end
h = Hello.new
h.display
コードの最初の@hello
は、クラスインスタンス変数と呼ばれます。
これは、定数Hello
が指すクラスオブジェクトのインスタンス変数です。 (クラスClass
のインスタンスです。)
技術的には、class
スコープ内にいるとき、self
は現在のクラスのオブジェクトに設定され、@variables
は現在のself
に関係します。私はこれらのことを説明するのが嫌です。
The Pragmatic Programmersからの各$ 5スクリーンキャストのコレクション を見ることで、これらすべてをより明確にすることができます。
(または、ここで説明を求めることができます。更新を試みます。)
「Rubyプログラミング言語」という本に明確な説明があります。読んでください。非常に役立ちます。ここに貼り付けます(7.1.16章から):
クラス定義の内部で、インスタンスメソッド定義の外部で使用されるインスタンス変数は、クラスインスタンス変数です。
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
......
クラスインスタンス変数はクラスオブジェクトの単なるインスタンス変数であるため、attr、attr_reader、およびattr_accessorを使用して、それらのアクセサメソッドを作成できます。
class << self
attr_accessor :n, :totalX, :totalY
end
これらのアクセサーを定義すると、生データをPoint.n、Point.totalX、Point.totalYとして参照できます。
Rubyには「クラスインスタンス変数」という概念があったことを忘れていました。いずれにせよ、OPの問題は不可解であるように思われ、kchの回答のヒントを除いて、これまでの回答のいずれでも実際に対処されていませんでした:それはスコープの問題です。 (編集時に追加:実際には、srisの回答doesはこの点に最後に対処しますが、例のようにこの回答をそのままにしておきますコードは問題を理解するのに役立つかもしれません。)
Rubyクラスでは、@
で始まる変数名は、two変数のいずれかを参照できます。 インスタンス変数またはクラスインスタンス変数のいずれかになります(クラスのどこで参照されているかによります)これはかなり微妙な落とし穴です。
例でポイントを明確にします。ここに少しRubyテストクラス(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
変数は、私が思っていたとおりに名前を付けましたが、必ずしもそうではないことがわかりました。
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
@@class_variable
および@instance_variable
は常に期待どおりに動作します。前者はクラスレベルで定義され、クラスメソッドまたはインスタンスメソッドで参照されるかどうかにかかわらず、それに割り当てられた値を保持しますトップ。後者はクラスT
のオブジェクトでのみ値を取得するため、クラスメソッドでは、値がnil
である未知の変数を参照します。
想像上のclass_method
という名前のクラスメソッドは、@@class_variable
と2つの@class_instance_variable
sの値を期待どおりに、つまりクラスの最上部で初期化されて出力します。ただし、インスタンスメソッドinitialize
およびinstance_method
では、different変数の同じ名前がアクセスされます。つまり、クラスインスタンス変数ではなくインスタンス変数です。
後の@class_instance_variable_1
の呼び出しで古い値class_method
が出力されるため、initialize
メソッドでの割り当てはクラスインスタンス変数"WTF"
に影響しなかったことがわかります。代わりに、メソッドinitialize
新しいインスタンス変数を宣言しましたalsoという名前の(誤解を招く)@class_instance_variable_1
。割り当てられた値"wtf"
は、メソッドinitialize
およびinstance_method
によって出力されます。
サンプルコードの変数@class_instance_variable_2
は、元の問題の変数@hello
と同等です。クラスインスタンス変数として宣言および初期化されますが、インスタンスメソッドがその名前の変数を参照する場合、実際には同じ名前のインスタンス変数-宣言されていない変数なので、その値はnilです。
「@@」が前に付いたクラス変数を確認することもお勧めします。クラス変数とインスタンス変数の違いを示すサンプルコードを次に示します。
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