Hatena::ブログ(Diary)

saito’s blog RSSフィード

2010-12-05

PythonとRubyの変数のスコープのまとめ

僕が普段使用するPythonRubyの変数のスコープについてまとめてみました。

Python

1. if文やfor文などの制御構造はスコープを作らない。

次のプログラムでは、if文の内側と外側のスコープは共通なので、if文内でaが上書きされてa=1が出力されます。

a = 0
if True:
  a = 1
print "a = %d" % a # a = 1

2. 関数定義とクラス定義では新しいスコープが作られる。

関数定義やクラス定義では新しいスコープが作られるので、関数定義の内側と外側で同名の変数が存在しても、両者は区別されます。

次のプログラムでは、関数foo内で変数aに代入を行っていますが、この場合fooの内側の変数と外側の変数は別物なので、外側の変数が上書きされることはありません。

a = 0

def foo():
  a = 1
  print "a = %d" % a # a = 1

foo()
print "a = %d" % a # a = 0

3. 内側のスコープから、外側のスコープの変数を参照することができる。

関数定義やクラス定義では新しいスコープが作成されますが、内側のスコープで同名の変数への代入を行わない限り、外側のスコープの変数を参照することが出来ます。

a = 0

class Foo(object):
  print "a = %d" % a # a = 0

def foo():
  print "a = %d" % a 

foo() # a = 0

Pythonでは関数内で関数を定義したり関数を変数に代入したりすることができるので、関数のローカル変数を参照するような関数を作成することが出来ます。

これをクロージャと呼びます。

def make_x_printer(x):
  def x_printer():
    print x # x はmake_x_printerのローカル変数だが、x_printerからも参照可能。
  return x_printer
a_printer = make_x_printer("a")
b_printer = make_x_printer("b")
a_printer()
b_printer()

4. 変数の作成は代入の時点ではなく、スコープの先頭で行われる。

スコープ内で変数への代入が行われると、内部的にはスコープの先頭で変数が作成されるようです。

次のプログラムでは関数foo内でaへの代入が行われているので、fooの先頭でaが作成されます。

そのため、外側のaはこの時点で参照できなくなっています。

さらにprint文が実行される時点ではまだaが未定義なので、UnboundLocalErrorが発生してしまいます。

a = 0

def foo():
  print "a = %d" % a
  a = 1

foo() # UnboundLocalError

Pythonの場合、この変数への代入が変数宣言を兼ねるという仕様のおかげで、現在のスコープと外側のスコープで変数名が衝突しても、外側のスコープの変数が誤って破壊される心配はありません。

ただし、クロージャで値を変更するようなプログラムを書く場合に、この仕様が邪魔になることがあります。

Pythonでは次のプログラムは正しく動作しません。

def make_counter():
  i = 0
  def counter():
    i+=1        
    return i
  return counter

c = make_counter()
print c()

i+=1という式はi=i+1と等価、すなわちiへの代入が存在するので関数counterのスコープでiという変数が新しく作成されます。

しかし、i=i+1の右辺が評価される時点ではiは初期化されていないので、UnboundLocalErrorが発生してしまいます。

Python2.xでは、次のプログラムのように、再代入を避けることによって、この問題を解決することができます。

def make_counter():
  i = [0]
  def counter():
    i[0] +=1
    return i[0]
  return counter

c = make_counter()
print c() # 1
print c() # 2

Python3.xではnonlocal文を使うことで、外側のスコープの変数への再代入を行うことができます。

nonlocal文を使えと先程の例をよりきれいに書くことが出来ます。

def make_counter():
  i = 0
  def counter():
    nonlocal i
    i+=1        
    return i
  return counter

c = make_counter()
print( c() ) 
print( c() )

Ruby

1. if文やwhile文などの制御構造はスコープを作らない。

Pythonと同様に、if文などの制御構文では新たなスコープは作成されません。

a = 0
if true
  a = 1
end
puts "a = #{a}" # a = 1

2. メソッド定義とクラス定義では新しいスコープが作られる。

Rubyの場合も、メソッド定義やクラス定義で新たなスコープが作成されます。

次のプログラムでは、fooの内側のaと外側のaは区別されるので、fooを実行しても外側のaが上書きされることはありません。

a = 0

def foo
  a = 1
  puts "a = #{a}" # a = 1
end

foo
puts "a = #{a}" # a = 0

3. メソッド定義やクラス定義の内側からは外側のスコープの変数を参照することが出来ない。

Rubyの場合、Pythonとは異なり、メソッド定義やクラス定義の中からは、外側のスコープの変数を参照することが出来ません。

次のプログラムの場合、トップレベルのaは(Pythonプログラマにとって)グローバル変数のようにも見えますが、Rubyの場合はこれはグローバル変数ではないので、メソッドfooの内部から変数aを参照することが出来ません。

a = 0

def foo
  puts "a = #{a}"
end

foo() # NameError

グローバル変数や定数の場合は、メソッド定義の内部からも外側のスコープの変数を参照することが出来ます。

$a = 0
A  = 1

def foo
  puts "$a = #{$a}"
  puts "A  = #{A}"
end

foo()

5. ブロックは新たなスコープを作る。ブロック内で定義された変数は、ブロックの中だけで有効。

ブロック内で作成された変数は、ブロックの中だけで有効です。

def foo
  yield
end

foo do
  a = 1
end
puts "a = #{a}" # NameError

6. ブロックの中からブロックの外で定義された変数にアクセスできる。さらに再代入も可能。

ブロックは新たなスコープを作りますが、ブロックの外側の変数も当然参照することが出来ます。

また、Pythonの場合と異なり、外側のスコープの同名の変数に再代入を行うことも可能です。

def foo
  yield
end

a = 0
foo do
  puts "a = #{a}" # a = 0
  a = 1
  puts "a = #{a}" # a = 1
end
puts "a = #{a}" # a = 1

ブロック内からブロック外の変数の再代入も行えるので、Python2.xに比べてクロージャを簡単につくることが出来ます。

def make_counter
  i = 0
  counter = lambda do
    i+=1
  end
  counter
end

c = make_counter
puts c.call
puts c.call

ブロックの外側と内側で意図しない変数名の衝突が起きても、変数の値が上書きされてしまうので、メソッドを極力短くして、変数のスコープを短く保つことが重要だと思います。


7. メソッド定義やクラス定義で外部の変数を参照したい場合、define_methodやClass.newを使う。

メソッド内で外側のスコープの変数(グローバル変数や定数を除く)を参照したい場合、通常のメソッド定義文やクラス定義文の代わりに、define_methodやClass.newを使います。

動的にクラスやメソッドを作成したい場合に使うテクニックです。

a = 0

Foo = Class.new do
  define_method(:foo) do
    puts "a = #{a}"
  end  
end

f = Foo.new
f.foo # a = 0

まとめ

PythonRubyはともに変数宣言を行わない動的言語ですが、こうして比べてみると変数のスコープはかなり異なることが分かります。

JavaScriptPerlLispなどは明示的な変数宣言を持つ言語なので、こうした言語との違いをまとめてみるのも面白いと思います。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/saitodevel01/20101205/1291582273