web-dev-qa-db-ja.com

変数への参照がPython

このメッセージは少し長く、多くの例がありますが、私や他の人がPython 2.7。

コードブロック(モジュール、クラス定義、関数定義など)にPEP 227( http://www.python.org/dev/peps/pep-0227/ )の用語を使用しています。)および変数バインディング(割り当て、引数宣言、クラスおよび関数宣言、forループなど)

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

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

  • 地元
  • グローバル
  • ビルトイン

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

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

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

  • 変数がグローバルとして宣言されていない限り(その場合、変数はグローバルスコープに属します)、変数をブロック内のオブジェクトにバインドすると、この変数はこのブロックに対してローカルになります。
  • 変数への参照は、すべてのブロックに対してルール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ルールを使用して検索されますが、クラスがパスにある場合、クラス引数はスキップされます。ここでも、これがPEP227が説明していることです。

例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のような動的言語では、すべてが動的に解決されることが期待されます。ただし、これは関数には当てはまりません。ローカル変数はコンパイル時に決定されます。PEP227および 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ドキュメントの実行モデルは誤解を招きやすく、一部は間違っています。

例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')で検索されます。次に、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参照が最も内側の関数で検索されるのはなぜですか?

50
user3022222

つまり、例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が発生します---その場合のみ---代わりにモジュールグローバルで検索します。それだけです。ネストされたスコープのチェーンには従いません。

何故なの?私は実際、「歴史的な理由から」というより良い説明があるのではないかと疑っています。より専門的に言えば、変数xはクラススコープでローカルに定義されている(割り当てられているため)およびはから渡される必要があると考えることができます。字句的にネストされた変数としての親スコープ(読み取られるため)。ローカルスコープで検索するLOAD_NAMEとは異なるバイトコードを使用して実装することは可能であり、見つからない場合はネストされたスコープの参照の使用にフォールバックします。

編集:http://bugs.python.org/issue53286 を参照してくれたwilberforceに感謝します。結局修正する必要があると思われる場合は、提案された新しいバイトコードでディスカッションを再開する機会があるかもしれません(バグレポートではx = xのサポートを終了することを検討していますが、既存のコードが壊れすぎることを恐れて終了しました;代わりに、ここで提案しているのは、より多くの場合にx = xを機能させることです)。または私は別の細かい点を見逃しているかもしれません...

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

18
Armin Rigo

理想的な世界では、あなたは正しいでしょうし、あなたが見つけた矛盾のいくつかは間違っているでしょう。ただし、CPythonはいくつかのシナリオ、特に関数ローカルを最適化しました。これらの最適化は、コンパイラと評価ループがどのように相互作用し、歴史的な先例と一緒になって、混乱を招きます。

Pythonはコードをバイトコードに変換し、それらはインタープリターループによって解釈されます。名前にアクセスするための「通常の」オペコードは_LOAD_NAME_で、辞書の場合と同じように変数名を検索します。 _LOAD_NAME_は最初に名前をローカルとして検索し、それが失敗した場合はグローバルを検索します。 _LOAD_NAME_は、名前が見つからない場合にNameError例外をスローします。

ネストされたスコープの場合、現在のスコープ外の名前の検索はクロージャを使用して実装されます。名前が割り当てられていないが、ネストされた(グローバルではない)スコープで使用できる場合、そのような値はクロージャとして処理されます。これが必要なのは、親スコープが特定の名前に対して異なる時間に異なる値を保持できるためです。親関数を2回呼び出すと、クロージャ値が異なる可能性があります。したがって、Pythonには、その状況に対応する_LOAD_CLOSURE_、_MAKE_CLOSURE_、および_LOAD_DEREF_オペコードがあります。最初の2つのオペコードは、ネストされたスコープのクロージャの読み込みと作成に使用されます。 、および_LOAD_DEREF_は、ネストされたスコープが必要とするときに、閉じられた値をロードします。

現在、_LOAD_NAME_は比較的遅いです。 2つの辞書を参照します。つまり、最初にキーをハッシュして、いくつかの同等性テストを実行する必要があります(名前がインターンされていない場合)。名前がローカルでない場合は、グローバルに対してこれを再度実行する必要があります。何万回も呼び出される可能性のある関数の場合、これは面倒な作業になる可能性があります。したがって、関数ローカルには特別なオペコードがあります。ローカル名のロードは、_LOAD_FAST_によって実装されます。これは、ローカル変数を検索しますインデックスによって特別なローカル名配列で。これははるかに高速ですが、コンパイラは最初に名前がグローバルではなくローカルであるかどうかを確認する必要があります。グローバル名を引き続き検索できるようにするために、別のオペコード_LOAD_GLOBAL_が使用されます。コンパイラは、この場合を明示的に最適化して、特別なオペコードを生成します。 _LOAD_FAST_は、名前の値がまだない場合、UnboundLocalError例外をスローします。

一方、クラス定義本体は関数のように扱われますが、この最適化ステップは実行されません。クラス定義は、それほど頻繁に呼び出されることを意図したものではありません。ほとんどのモジュールは、インポート時にクラスを作成しますonce。クラススコープもネスト時にカウントされないため、ルールはより単純です。その結果、スコープを少し混ぜ始めると、クラス定義本体は関数のようには機能しません。

したがって、非関数スコープの場合、_LOAD_NAME_と_LOAD_DEREF_は、それぞれローカルとグローバル、およびクロージャーに使用されます。関数の場合、代わりに_LOAD_FAST_、_LOAD_GLOBAL_、および_LOAD_DEREF_が使用されます。

クラス本体はPythonがclass行を実行するとすぐに実行されることに注意してください!したがって、例1では、_class B_内の_class A_が_class A_とすぐに実行されます。モジュールをインポートするときに実行されます。例2では、​​Cは、前ではなく、f()が呼び出されるまで実行されません。

あなたの例を見てみましょう:

  1. クラス_A.B_をクラスAにネストしました。クラス本体はネストされたスコープを形成しないため、クラスAの実行時に_A.B_クラス本体が実行されても、コンパイラは_LOAD_NAME_を使用してxを検索します。 A.B().f()function(メソッドとしてB()インスタンスにバインドされている)であるため、_LOAD_GLOBAL_を使用してxをロードします。ここでは属性アクセスを無視します。これは非常に明確に定義された名前パターンです。

  2. ここで、f().C.zはクラススコープにあるため、関数f().C().g()Cスコープをスキップし、代わりに_LOAD_DEREF_を使用してf()スコープを調べます。

  3. ここで、varは、スコープ内で割り当てるため、コンパイラーによってローカルであると判断されました。関数が最適化されているため、_LOAD_FAST_を使用してローカルを検索し、例外がスローされます。

  4. 今、物事は少し奇妙になります。 _class A_はクラススコープで実行されるため、_LOAD_NAME_が使用されています。スコープのローカルディクショナリから_A.x_が削除されたため、xへの2回目のアクセスにより、代わりにグローバルxが検出されます。 _LOAD_NAME_は最初にローカルを検索しましたが、そこで見つかりませんでした。グローバルルックアップにフォールバックしました。

    はい、これはドキュメントと矛盾しているようです。 Python-the-languageとCPython-実装はここで少し衝突しています。しかし、あなたは動的言語で可能で実用的なものの限界を押し広げています。 xが_LOAD_NAME_のローカルである必要があるかどうかを確認することは可能ですが、ほとんどの開発者が遭遇することのないコーナーケースには貴重な実行時間がかかります。

  5. 今、あなたはコンパイラを混乱させています。クラススコープで_x = x_を使用したため、スコープ外の名前からlocalを設定しています。コンパイラーは、xがここでローカルである(ユーザーが割り当てた)ことを検出するため、スコープ名である可能性があるとは見なしません。これは最適化された関数本体ではないため、コンパイラーは、このスコープ内のxへのすべての参照に_LOAD_NAME_を使用します。

    クラス定義を実行するとき、_x = x_は最初にxを検索する必要があるため、_LOAD_NAME_を使用して実行します。 xが定義されておらず、_LOAD_NAME_がローカルを見つけられないため、globalxが見つかります。結果の値はローカルとして保存され、happensxという名前になります。 _print x_は再び_LOAD_NAME_を使用し、新しいローカルx値を見つけます。

  6. ここでは、コンパイラを混乱させませんでした。ローカルyを作成していますが、xはローカルではないため、コンパイラはそれを親関数f2().myfunc()からのスコープ名として認識します。 xはクロージャから_LOAD_DEREF_で検索され、yに格納されます。

私の意見では修正する価値はありませんが、5と6の間の混乱はバグとして見ることができます。それは確かにそのように提出されました。Pythonバグトラッカーの issue 53286 を参照してください、それは10年以上前から存在しています。

コンパイラは、例5の最初の割り当てで、xalso localの場合でも、スコープ名xをチェックできます。または_LOAD_NAME_は、名前がローカルであるかどうかをチェックできます。実際には、パフォーマンスを犠牲にして、ローカルが見つからなかった場合はUnboundLocalErrorをスローします。これが関数スコープ内にあった場合、たとえば5では_LOAD_FAST_が使用され、UnboundLocalErrorがすぐにスローされます。

ただし、参照されているバグが示すように、歴史的な理由から、動作は保持されます。このバグが修正されれば壊れてしまうコードが今日そこにあるでしょう。

21
Martijn Pieters

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

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        

したがって、唯一の違いは、MyClass1ではxLOAD_NAME opを使用してロードされるのに対し、MyClass2ではLOAD_DEREFを使用してロードされることです。 LOAD_DEREFは、囲んでいるスコープで名前を検索するため、「x inmyfunc」を取得します。 LOAD_NAMEはネストされたスコープに従いません-xまたはf1でバインドされたmyfunc名を確認できないため、モジュールレベルのバインドを取得します。

次に、問題は、2つのバージョンのMyClassのコードが2つの異なるオペコードにコンパイルされるのはなぜですか。 f1では、バインディングはクラススコープのxをシャドウイングしていますが、f2では新しい名前をバインディングしています。 MyClassスコープがクラスではなくネストされた関数である場合、y = xf2行は同じようにコンパイルされますが、x = xf1は次のようになります。 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.を使用して明示的にメソッドスコープに到達する必要があります(これも存在しない場合はクラスにフォールバックしますインスタンスレベルのバインディング))。

6
babbageclunk