ローカル変数のスコープ
rubyではローカル変数の参照は定義されたスコープ内のみです。
a = 1
def func
puts a
end
func #=> error (undefined local variable)
pythonでは子孫スコープからも参照できます。
a = 1
def func():
print(a)
func() #=> 1
さらに、ローカル変数は定義されているスコープが消滅しても、子孫スコープなどから参照されている限り消去されません。この性質を応用してインスタンス変数を持つクラスのようなものを作ることもできます。このクラスもどきのインスタンスメソッドもどきをクロージャーと言います。
def cls(): # クラスもどき
x = {} # インスタンス変数もどき
def a(): # クロージャー
x['a'] = 1
def b(): # クロージャー
x['b'] = 1
def g(): # クロージャー
return x
return a, b, g
# xのスコープは関数呼び出し終了とともに消滅する
a, b, g = cls()
a(); b();
print(g()) #=> {'a': 1, 'b': 1}
定数クロージャー
rubyでも定数ならクロージャーが存在します。例を挙げる前に、少しだけrubyの説明をします。
rubyでは定数と変数は全く別の手法で管理されています。変数のスコープは関数定義(do構文等含まず)やクラス定義で変化します。一方、定数のスコープはモジュール定義(クラス定義含む)でのみ変化します。そして定数の探索は、
ネストが存在する場合、この定数はそのネストの要素の中で順に探索される
(定数の自動読み込みと再読み込みより)
という様に行われます(但しオープンクラスの場合は探索しない)。つまり、定数は子孫スコープからも参照できます。
この性質を使うと、クロージャーを作ることが出来ます。
module A
X = 'x' # インスタンス変数もどき
module B # クラスもどき
def show # クロージャー
puts X
end
end
end
class C # インスタンスもどき
extend A::B
end
C.show #=> x
ローカル変数に見えるインスタンスメソッド
定義が見当たらないローカル変数への参照は、実は引数なしのインスタンスメソッド呼び出しかもしれません。本当によく出会います。
class C
def hoge # インスタンスメソッド
return "hoge"
end
def show
fuga = "fuga"
puts hoge # インスタンスメソッド呼び出し
puts fuga # ローカル変数参照
end
end
さらに、クラスマクロのattr_accessor、attr_reader、attr_writerを使えば、ローカル変数に見えるインスタンスメソッドを簡単に作れます。変数の実体は、引数のシンボルに@を前置した名前のインスタンス変数です。
class C
attr_accessor :hoge # インスタンス変数@hogeが実体
def initialize
self.hoge = "hoge" # インスタンスメソッドhoge=を呼び出し
end
def show
fuga = "fuga" # ローカル変数fugaを定義&初期化
puts hoge # インスタンスメソッドhogeを呼び出し
puts fuga # ローカル変数fugaを参照
end
end
C.new.methods.include?(:hoge) #=> true
C.new.methods.include?(:hoge=) #=> true
pythonの場合は、インスタンスメソッド呼び出しにselfが付いていたり、関数呼び出しの括弧が省略できなかったりするので、簡単に見分けられます。それよりも、インスタンスメソッド呼び出しとクラスメソッド呼び出しを区別するのが大変です。
class C:
def hoge(self):
return "hoge"
@classmethod
def piyo(cls):
return "piyo"
def show(self):
fuga = "fuga"
print(self.hoge()) # インスタンスメソッド呼び出し
print(fuga) # ローカル変数参照
print(self.piyo()) # クラスメソッド呼び出し
ローカル変数による上書き
(scivola様のコメントを元に追加しました)
先ほどの例でインスタンスメソッドhoge=を呼び出す際、selfをレシーバーとして明示しました。
class C
attr_accessor :hoge
def initialize
self.hoge = "hoge" # ここ
end
...
これは、selfを明示しないとローカル変数hogeの定義として扱われるからです。
class C
attr_accessor :hoge
def initialize
hoge = "hoge" # ローカル変数hogeの定義
end
def show
puts hoge
end
end
C.new.show #=> nil
同じことが、pythonでも起こり得ます。例えば、先ほど紹介したクラスもどきの場合、インスタンス変数もどきは更新できません。
def cls():
x = {} # インスタンス変数的振る舞いをする
def u():
x = 'updated' # 実はローカル変数xの定義
def g():
return x
return u, g
# xのスコープは関数呼び出し終了とともに消滅する
u, g = cls()
u();
print(g()) #=> {}
pythonではselfを省略できないので、インスタンス変数の更新の際に間違うことはないでしょう。
class C:
def __init__(self):
self.hoge = "not updated"
def wrong(self):
hoge = "updated" # 間違いに気付き易い
def correct(self):
self.hoge = "updated"
def show(self):
#print(hoge) #=> error (name 'hoge' is not defined)
print(self.hoge)
c = C()
c.wrong(); c.show(); #=> not updated
c.correct(); c.show(); #=> updated
include先のインスタンス変数を参照
module M
def show
puts @message
end
end
class C
include M
def initialize
@message = "accessible"
end
end
C.new.show #=> accessible
pythonでも似たことが出来ます。
class S:
def show(self):
print(self.message)
class C(S):
def __init__(self):
self.message = "accessible"
C().show() #=> accessible
関数を変数に代入
rubyでは関数呼び出しの括弧を省略できます。そのため、関数を変数に代入することは出来ません。
def func
return "called"
end
a = func # 関数funcの呼び出し
puts a #=> called
Object#methodインスタンスメソッドを使って、関数をMethodオブジェクトに変換すると、変数に代入できます。
def func
return "called"
end
a = method(:func) # Methodオブジェクトの代入
puts a #=> #<Method: main.func>
puts a.call #=> called
一方、pythonでは関数を変数に代入できます。
def func():
return "called"
a = func # 関数funcの代入
print(func) #=> <function func at 0x...>