レキシカルスコープとダイナミックスコープ

Pythonはレキシカルスコープ(静的スコープ)である。Pythonは動的スコープの機能はない。

>>> x = 99
>>> def fooA():
...   print x
...
>>> def fooB():
...   x = 1
...   fooA()
...
>>> def fooC():
...   x = 2
...   fooA()
... 
>>> fooB()
99
>>> fooC()
99

fooB関数の中でfooA関数を呼び出しているが、fooB関数で定義されているxは参照されない。xの参照は、構文から静的に検索されるので、グローバル変数xを参照している。


ここで、

>>> fooB()
1
>>> fooC()
2

のように動的な挙動にした場合は、以下のようにする。

>>> x = 99
>>> def fooA(x=x):
...   print x
...
>>> def fooB():
...   x = 1
...   fooA(x)
...
>>> def fooC():
...   x = 2
...   fooA(x)
...
>>> def fooD():
...   x = 3
...   fooA()
...
>>> fooB()
1
>>> fooC()
2
>>> fooD()
99

次のようなケースではエラーとなる。

>>> x = 99
>>> def foo():
...   print x
...   x = 1
...
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment

Python には宣言文がなく、コードブロックのどこで名前束縛操作を行ってもかまいません。あるコードブロックにおけるローカル変数は、ブロック全体から名前束縛操作が行われている部分を走査して決定します。

>>> import dis
>>> dis.dis(foo)
  2           0 LOAD_FAST                0 (x)
              3 PRINT_ITEM
              4 PRINT_NEWLINE

  3           5 LOAD_CONST               1 (1)
              8 STORE_FAST               0 (x)
             11 LOAD_CONST               0 (None)
             14 RETURN_VALUE

foo関数の2行目がバイトコードで、LOAD_GLOBALでなくLOAD_FASTになっている。つまり、グローバル変数を参照しているのではなく、ローカル変数を参照している。しかし、2行目の参照した時点ではxの値が保存されていないのでUnboundLocalErrorとなる。


また、以下のように、外側のローカル変数を参照するには、関数でなくクロージャにならなくてはならない。

>>> def foo1():
...   x = 1
...   def bar():
...     print x
...     x = 2
...   bar()
...
>>> def foo2():
...   x = 1
...   def bar():
...     print x
...   bar()
...
>>> dis.dis(foo1)
  2           0 LOAD_CONST               1 (1)
              3 STORE_FAST               0 (x)

  3           6 LOAD_CONST               2 (<code object bar at 00D37CC8, file "<stdin>", line 3>)
              9 MAKE_FUNCTION            0
             12 STORE_FAST               1 (bar)

  6          15 LOAD_FAST                1 (bar)
             18 CALL_FUNCTION            0
             21 POP_TOP
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE
>>> dis.dis(foo2)
  2           0 LOAD_CONST               1 (1)
              3 STORE_DEREF              0 (x)

  3           6 LOAD_CLOSURE             0 (x)
              9 BUILD_TUPLE              1
             12 LOAD_CONST               2 (<code object bar at 00D4D920, file "<stdin>", line 3>)
             15 MAKE_CLOSURE             0
             18 STORE_FAST               0 (bar)

  5          21 LOAD_FAST                0 (bar)
             24 CALL_FUNCTION            0
             27 POP_TOP
             28 LOAD_CONST               0 (None)
             31 RETURN_VALUE

foo1内のbar関数は単なる関数だが、foo2内のbar関数はクロージャになっている。つまりバイトコード変換時に、ブロックごとに以下のような判断をしていると思われる。

  1. 参照されている変数がどこのスコープの変数かを特定して決め打ちする(途中で参照対象を変えない)
  2. 外側の関数内のローカル変数を参照している場合は、関数をクロージャにする

以上のことを静的に(構文上の位置で)決定している。