54

このメッセージは多くの例で少し長くなりますが、Python 2.7 での変数と属性ルックアップの完全なストーリーをよりよく理解するのに役立つことを願っています。

コードブロック(モジュール、クラス定義、関数定義など)と変数バインディング(代入、引数の宣言、クラスと関数の宣言、for ループなど)

ドットなしで呼び出すことができる名前には変数という用語を使用し、オブジェクト名で修飾する必要がある名前には属性という用語を使用しています (オブジェクト obj の属性 x の obj.x など)。

Python には、すべてのコード ブロックに対して 3 つのスコープがありますが、関数は次のとおりです。

  • ローカル
  • グローバル
  • ビルトイン

関数専用の Python には 4 つのブロックがあります (PEP 227 によると)。

  • ローカル
  • 囲み関数
  • グローバル
  • ビルトイン

変数をバインドしてブロック内で見つけるためのルールは非常に単純です。

  • ブロック内のオブジェクトに変数をバインドすると、変数がグローバルに宣言されていない限り (その場合、変数はグローバル スコープに属します)、この変数はこのブロックに対してローカルになります。
  • 変数への参照は、すべてのブロックに対してルール LGB (ローカル、グローバル、ビルトイン) を使用して検索されますが、関数は
  • 変数への参照は、関数に対してのみルール LEGB (ローカル、エンクロージング、グローバル、ビルトイン) を使用して検索されます。

このルールを検証し、多くの特殊なケースを示す例を教えてください。それぞれの例について、私の理解を示します。間違っている場合は修正してください。最後の例については、結果がわかりません。

例 1:

x = "x in module"
class A():
    print "A: "  + x                    #x in module
    x = "x in class A"
    print locals()
    class B():
        print "B: " + x                 #x in module
        x = "x in class B"
        print locals()
        def f(self):
            print "f: " + x             #x in module
            self.x = "self.x in f"
            print x, self.x
            print locals()

>>>A.B().f()
A: x in module
{'x': 'x in class A', '__module__': '__main__'}
B: x in module
{'x': 'x in class B', '__module__': '__main__'}
f: x in module
x in module self.x in f
{'self': <__main__.B instance at 0x00000000026FC9C8>}

クラスのネストされたスコープはなく (ルール LGB)、クラス内の関数は、修飾名 (この例では self.x) を使用しないとクラスの属性にアクセスできません。これは PEP227 で詳しく説明されています。

例 2:

z = "z in module"
def f():
    z = "z in f()"
    class C():
        z = "z in C"
        def g(self):
            print z
            print C.z
    C().g()
f()
>>> 
z in f()
z in C

ここで、関数内の変数は LEGB ルールを使用して検索されますが、クラスがパスにある場合、クラスの引数はスキップされます。繰り返しますが、これは PEP 227 が説明していることです。

例 3:

var = 0
def func():
    print var
    var = 1
>>> func()

Traceback (most recent call last):
  File "<pyshell#102>", line 1, in <module>
func()
  File "C:/Users/aa/Desktop/test2.py", line 25, in func
print var
UnboundLocalError: local variable 'var' referenced before assignment

Python などの動的言語では、すべてが動的に解決されることが期待されます。しかし、これは関数には当てはまりません。ローカル変数はコンパイル時に決定されます。PEP 227 および http://docs.python.org/2.7/reference/executionmodel.htmlは、この動作をこのように説明しています

「名前バインディング操作がコード ブロック内のどこかで発生した場合、ブロック内の名前のすべての使用は、現在のブロックへの参照として扱われます。」

例 4:

x = "x in module"
class A():
    print "A: " + x
    x = "x in A"
    print "A: " + x
    print locals()
    del x
    print locals()
    print "A: " + x
>>> 
A: x in module
A: x in A
{'x': 'x in A', '__module__': '__main__'}
{'__module__': '__main__'}
A: x in module

しかし、PEP227 のこのステートメントは、「コード ブロック内の任意の場所で名前バインディング操作が発生した場合、ブロック内の名前のすべての使用は、現在のブロックへの参照として扱われます。」コードブロックがクラスの場合は間違っています。また、クラスについては、コンパイル時ではなく、実行時にクラスの名前空間を使用してローカルの名前バインディングを行うようです。その点で、PEP227 と Python doc の実行モデルは誤解を招きやすく、一部が間違っています。

例 5:

x = 'x in module'
def  f2():
    x = 'x in f2'
    def myfunc():
        x = 'x in myfunc'
        class MyClass(object):
            x = x
            print x
        return MyClass
    myfunc()
f2()
>>> 
x in module

このコードの私の理解は次のとおりです。命令 x = x は、最初に式の右手 x が参照しているオブジェクトを調べます。その場合、オブジェクトはクラス内でローカルに検索され、次にルール LGB に従って、文字列 'x in module' であるグローバル スコープで検索されます。次に、MyClass へのローカル属性 x がクラス ディクショナリに作成され、文字列オブジェクトを指します。

例 6:

ここで、説明できない例を示します。これは例 5 に非常によく似ています。ローカルの MyClass 属性を x から y に変更しているだけです。

x = 'x in module'
def  f2():
    x = 'x in f2'
    def myfunc():
        x = 'x in myfunc'
        class MyClass(object):
            y = x
            print y
        return MyClass
    myfunc()
f2()
>>>
x in myfunc

その場合、MyClass の x 参照が最も内側の関数で検索されるのはなぜですか?

4

3 に答える 3

20

一言で言えば、例 5 と例 6 の違いは、例 5 では変数xも同じスコープで割り当てられているのに対し、例 6 では割り当てられていないことです。これは、歴史的な理由から理解できる違いを引き起こします。

これにより UnboundLocalError が発生します。

x = "foo"
def f():
    print x
    x = 5
f()

「foo」を出力する代わりに。最初は奇妙に思えるかもしれませんが、少し意味があります: 関数 f() は変数xをローカルで定義します。たとえそれが出力の後であったとしてもx、同じ関数内での参照はそのローカル変数でなければなりません。少なくとも、グローバル変数の名前を誤ってローカルで再利用し、グローバル変数とローカル変数の両方を使用しようとしている場合に、奇妙な驚きを回避するという点で意味があります。これは、変数を見るだけで、それがどの変数を意味するかを静的に知ることができることを意味するため、良い考えです。たとえば、print xがローカル変数を参照していることがわかります (そのため、UnboundLocalError が発生する可能性があります)。

x = "foo"
def f():
    if some_condition:
        x = 42
    print x
f()

さて、このルールはクラス レベルのスコープでは機能しません。そこではx = x、グローバル変数xをクラス レベルのスコープにキャプチャして、式が機能するようにしたいと考えています。これは、クラス レベルのスコープが上記の基本的な規則に従わないことをx意味します。このスコープ内で外部変数を参照するのか、それともローカルに定義されたものを参照するのかを知ることはできませんx--- たとえば:

class X:
    x = x     # we want to read the global x and assign it locally
    bar = x   # but here we want to read the local x of the previous line

class Y:
    if some_condition:
        x = 42
    print x     # may refer to either the local x, or some global x

class Z:
    for i in range(2):
        print x    # prints the global x the 1st time, and 42 the 2nd time
        x = 42

そのため、クラス スコープでは、別の規則が使用されます。通常は UnboundLocalError が発生する場所であり、その場合にのみ発生します --- 代わりに、モジュール globals を検索します。それだけです。ネストされたスコープのチェーンには従いません。

なぜだめですか?「歴史的な理由から」というより良い説明があるとは思えません。より技術的な用語では、変数xがクラス スコープでローカルに定義されている (代入されているため)と同時に、親スコープからレキシカルにネストされた変数として渡される必要がある (読み取られるため)と考えることができます。ローカルスコープで検索するバイトコードとは異なるバイトコードを使用して実装することは可能でLOAD_NAMEあり、見つからない場合はネストされたスコープの参照を使用するようにフォールバックします。

編集: http://bugs.python.org/issue532860を参照してくれた wilberforce に感謝します。最終的に修正する必要があると思われる場合は、提案された新しいバイトコードで議論を再開する機会があるかもしれません (バグレポートでは、サポートを終了することを検討してx = xいますが、既存のコードがあまりにも多く壊れることを恐れて閉じられました。ここで提案するのはx = x、より多くの場合に機能することです)。または、別の細かい点を見逃している可能性があります...

EDIT2: CPython は現在の 3.4 トランクで正確にそれを行ったようです: http://bugs.python.org/issue17853 ... かどうか? 彼らは少し異なる理由でバイトコードを導入し、体系的に使用していません...

于 2013-11-27T18:58:29.263 に答える
7

簡単に言うと、これは Python のスコーピングのコーナー ケースであり、少し一貫性がありませんが、下位互換性のために保持する必要があります (正しい答えがどうあるべきかが明確ではないため)。PEP 227 が実装されていたときの Python メーリング リストでの元の議論の多くと、この動作が修正されたバグのいくつかを見ることができます。

モジュールを使用して違いがある理由disを突き止めることができます。これにより、コードオブジェクトの内部を調べて、コードの一部がコンパイルされたバイトコードを確認できます。私は Python 2.6 を使用しているため、この詳細は少し異なる可能性がありますが、同じ動作が見られるため、おそらく 2.7 に十分近いと思います。

ネストされMyClassたそれぞれを初期化するコードは、最上位関数の属性を介して取得できるコード オブジェクト内に存在します。(例 5 と例 6 の関数の名前f1f2それぞれ と に変更しています。)

コード オブジェクトにはco_consts、コード オブジェクトを含むタプルがあり、このタプルには、作成myfunc時に実行されるコードが含まれています。MyClass

In [20]: f1.func_code.co_consts
Out[20]: (None,
 'x in f2',
 <code object myfunc at 0x1773e40, file "<ipython-input-3-6d9550a9ea41>", line 4>)
In [21]: myfunc1_code = f1.func_code.co_consts[2]
In [22]: MyClass1_code = myfunc1_code.co_consts[3]
In [23]: myfunc2_code = f2.func_code.co_consts[2]
In [24]: MyClass2_code = myfunc2_code.co_consts[3]

次に、次を使用してバイトコードでそれらの違いを確認できますdis.dis

In [25]: from dis import dis
In [26]: dis(MyClass1_code)
  6           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  7           6 LOAD_NAME                2 (x)
              9 STORE_NAME               2 (x)

  8          12 LOAD_NAME                2 (x)
             15 PRINT_ITEM          
             16 PRINT_NEWLINE       
             17 LOAD_LOCALS         
             18 RETURN_VALUE        

In [27]: dis(MyClass2_code)
  6           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  7           6 LOAD_DEREF               0 (x)
              9 STORE_NAME               2 (y)

  8          12 LOAD_NAME                2 (y)
             15 PRINT_ITEM          
             16 PRINT_NEWLINE       
             17 LOAD_LOCALS         
             18 RETURN_VALUE        

したがって、唯一の違いは、 inMyClass1はopxを使用してロードされるのに対して、 in は を使用してロードされることです。囲んでいるスコープで名前を検索するため、「x in myfunc」を取得します。ネストされたスコープには従わない -またはにバインドされた名前が表示されないため、モジュール レベルのバインディングを取得します。LOAD_NAMEMyClass2LOAD_DEREFLOAD_DEREFLOAD_NAMExmyfuncf1

次に問題は、なぜ 2 つのバージョンのコードがMyClass2 つの異なるオペコードにコンパイルされるのかということです。f1バインディングでは、クラス スコープでシャドウイングが行われますが、バインディングxf2は新しい名前が付けられます。MyClassスコープがクラスではなくネストされた関数である場合、 in のy = xf2は同じようにコンパイルされますが、x = xinf1は a になります。LOAD_FASTこれは、コンパイラが がx関数にバインドされていることを認識しているためです。したがって、を使用しLOAD_FASTてローカル変数を取得する必要があります。 . UnboundLocalErrorこれは、呼び出されたときに失敗します。

In [28]:  x = 'x in module'
def  f3():
    x = 'x in f2'
    def myfunc():
        x = 'x in myfunc'
        def MyFunc():
            x = x
            print x
        return MyFunc()
    myfunc()
f3()
---------------------------------------------------------------------------
Traceback (most recent call last)
<ipython-input-29-9f04105d64cc> in <module>()
      9         return MyFunc()
     10     myfunc()
---> 11 f3()

<ipython-input-29-9f04105d64cc> in f3()
      8             print x
      9         return MyFunc()
---> 10     myfunc()
     11 f3()

<ipython-input-29-9f04105d64cc> in myfunc()
      7             x = x
      8             print x
----> 9         return MyFunc()
     10     myfunc()
     11 f3()

<ipython-input-29-9f04105d64cc> in MyFunc()
      5         x = 'x in myfunc'
      6         def MyFunc():
----> 7             x = x
      8             print x
      9         return MyFunc()

UnboundLocalError: local variable 'x' referenced before assignment

MyFunc関数が次を使用するため、これは失敗しますLOAD_FAST

In [31]: myfunc_code = f3.func_code.co_consts[2]
MyFunc_code = myfunc_code.co_consts[2]
In [33]: dis(MyFunc_code)
  7           0 LOAD_FAST                0 (x)
              3 STORE_FAST               0 (x)

  8           6 LOAD_FAST                0 (x)
              9 PRINT_ITEM          
             10 PRINT_NEWLINE       
             11 LOAD_CONST               0 (None)
             14 RETURN_VALUE        

(余談ですが、スコーピングがクラス本体のコードと関数内のコードと相互作用する方法に違いがあることは大きな驚きではありません。クラス レベルでのバインディングはメソッドでは使用できないため、これがわかります。メソッド スコープは、ネストされた関数と同じようにクラス スコープ内にネストされていません. クラスを介して、またはself.(インスタンス レベルのバインディングも存在しない場合はクラスにフォールバックします) を使用して、明示的に到達する必要があります。 .)

于 2013-11-27T19:18:37.370 に答える