Diese Nachricht ist mit vielen Beispielen etwas lang, aber ich hoffe, sie wird mir und anderen helfen, die gesamte Geschichte der Variablen- und Attributsuche in Python 2.7 besser zu verstehen.

Ich verwende die Bedingungen von PEP 227 (http://www.python.org/dev/peps/pep-0227/) für Codeblöcke (wie z Module, Klassendefinition, Funktionsdefinitionen usw.) und Variablenbindungen (wie Zuweisungen, Argumentdeklarationen, Klasse und Funktionsdeklaration für Schleifen usw.)

Ich verwende die Begriffe Variablen für Namen, die ohne Punkt aufgerufen werden können, und Attribute für Namen, die mit einem Objektnamen qualifiziert werden müssen (z. B. obj.x für das Attribut x des Objekts obj).

In Python gibt es drei Bereiche für alle Codeblöcke, aber die Funktionen:

  • Lokal
  • Global
  • Eingebaut

In Python gibt es vier Blöcke nur für die Funktionen (gemäß PEP 227):

  • Lokal
  • Einschließende Funktionen
  • Global
  • Eingebaut

Die Regel für eine Variable, an die sie gebunden und in einem Block gefunden werden soll, ist ganz einfach:

  • Jede Bindung einer Variablen an ein Objekt in einem Block macht diese Variable lokal für diesen Block, es sei denn, die Variable wird als global deklariert (in diesem Fall gehört die Variable zum globalen Bereich).
  • Ein Verweis auf eine Variable wird mit der Regel LGB (lokal, global, integriert) für alle Blöcke außer den Funktionen nachgeschlagen
  • Ein Verweis auf eine Variable wird mit der Regel LEGB (lokal, einschließend, global, eingebaut) nur für die Funktionen nachgeschlagen.

Lassen Sie mich Beispiele nehmen, die diese Regel validieren und viele Sonderfälle zeigen. Für jedes Beispiel werde ich mein Verständnis geben. Bitte korrigieren Sie mich, wenn ich falsch liege. Für das letzte Beispiel verstehe ich das Ergebnis nicht.

Beispiel 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>}

Es gibt keinen verschachtelten Bereich für die Klassen (Regel LGB) und eine Funktion in einer Klasse kann nicht auf die Attribute der Klasse zugreifen, ohne einen qualifizierten Namen zu verwenden (in diesem Beispiel self.x). Dies ist in PEP227 gut beschrieben.

Beispiel 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

Hier werden Variablen in Funktionen mit der LEGB-Regel nachgeschlagen. Wenn sich jedoch eine Klasse im Pfad befindet, werden die Klassenargumente übersprungen. Auch hier erklärt PEP 227 dies.

Beispiel 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

Wir erwarten mit einer dynamischen Sprache wie Python, dass alles ist dynamisch gelöst. Dies ist jedoch bei Funktionen nicht der Fall. Lokal Variablen werden zur Kompilierungszeit bestimmt. PEP 227 und http://docs.python.org/2.7/reference/executionmodel.html beschreiben dies Verhalten auf diese Weise

"Wenn eine Namensbindungsoperation irgendwo innerhalb eines Codeblocks auftritt, werden alle Verwendungen des Namens innerhalb des Blocks als Verweise auf den aktuellen Block behandelt."

Beispiel 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

Wir sehen hier jedoch, dass diese Anweisung in PEP227 "Wenn eine Namensbindungsoperation irgendwo innerhalb eines Codeblocks auftritt, werden alle Verwendungen des Namens innerhalb des Blocks als Verweise auf den aktuellen Block behandelt." ist falsch, wenn der Codeblock eine Klasse ist. Darüber hinaus scheint es für Klassen so zu sein, dass die lokale Namensbindung nicht zur Kompilierungszeit erfolgt, sondern während der Ausführung unter Verwendung des Klassennamensraums. In dieser Hinsicht sind PEP227 und das Ausführungsmodell im Python-Dokument irreführend und teilweise falsch.

Beispiel 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

Mein Verständnis dieses Codes ist das folgende. Die Anweisung x = x sucht zuerst das Objekt, auf das sich die rechte Hand x des Ausdrucks bezieht. In diesem Fall wird das Objekt lokal in der Klasse nachgeschlagen. Nach der Regel LGB wird es im globalen Bereich nachgeschlagen, der Zeichenfolge 'x in module'. Anschließend wird im Klassenwörterbuch ein lokales Attribut x für MyClass erstellt und auf das Zeichenfolgenobjekt verwiesen.

Beispiel 6:

Hier ist ein Beispiel, das ich nicht erklären kann. Es ist sehr nahe an Beispiel 5, ich ändere nur das lokale MyClass-Attribut von x in 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

Warum wird in diesem Fall die x-Referenz in MyClass in der innersten Funktion nachgeschlagen?

51
user3022222 27 Nov. 2013 im 19:30

2 Antworten

Beste Antwort

Mit zwei Worten, der Unterschied zwischen Beispiel 5 und Beispiel 6 besteht darin, dass in Beispiel 5 die Variable x ebenfalls im selben Bereich zugewiesen wird, während dies in Beispiel 6 nicht der Fall ist. Dies löst einen Unterschied aus, der aus historischen Gründen verstanden werden kann .

Dies löst UnboundLocalError aus:

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

Anstatt "foo" zu drucken. Es macht ein bisschen Sinn, auch wenn es auf den ersten Blick seltsam erscheint: Die Funktion f () definiert die Variable x lokal, auch wenn sie sich nach dem Druck befindet, und damit jeden Verweis auf x in der Dieselbe Funktion muss für diese lokale Variable gelten. Zumindest ist es insofern sinnvoll, als es seltsame Überraschungen vermeidet, wenn Sie versehentlich den Namen einer globalen Variablen lokal wiederverwendet haben und versuchen, sowohl die globale als auch die lokale Variable zu verwenden. Dies ist eine gute Idee, da dies bedeutet, dass wir statisch erkennen können, indem wir uns eine Variable ansehen, welche Variable es bedeutet. Zum Beispiel wissen wir, dass print x hier auf die lokale Variable verweist (und daher möglicherweise UnboundLocalError auslöst):

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

Diese Regel funktioniert jetzt nicht für Bereiche auf Klassenebene: Dort möchten wir, dass Ausdrücke wie x = x funktionieren und die globale Variable x im Bereich auf Klassenebene erfasst werden. Dies bedeutet, dass Bereiche auf Klassenebene nicht der obigen Grundregel folgen: Wir können nicht wissen, ob x in diesem Bereich auf eine äußere Variable oder auf das lokal definierte x --- für verweist Beispiel:

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

In Klassenbereichen wird daher eine andere Regel verwendet: Wo normalerweise UnboundLocalError ausgelöst wird - und nur in diesem Fall - wird stattdessen in den Modulglobalen nachgeschlagen. Das ist alles: Es folgt nicht der Kette verschachtelter Bereiche.

Warum nicht? Ich bezweifle tatsächlich, dass es eine bessere Erklärung gibt als "aus historischen Gründen". In technischer Hinsicht könnte man davon ausgehen, dass die Variable x sowohl lokal im Klassenbereich definiert ist (weil sie zugewiesen ist) als auch aus dem übergeordneten Bereich als lexikalisch übergeben werden sollte verschachtelte Variable (weil sie gelesen wird). Es wäre möglich, es zu implementieren, indem ein anderer Bytecode als LOAD_NAME verwendet wird, der im lokalen Bereich nachschlägt und auf die Referenz des verschachtelten Bereichs zurückgreift, wenn sie nicht gefunden wird.

BEARBEITEN: dankt wilberforce für den Verweis auf http://bugs.python.org/issue532860. Wir haben möglicherweise die Möglichkeit, eine Diskussion mit dem vorgeschlagenen neuen Bytecode zu reaktivieren, wenn wir der Meinung sind, dass er doch behoben werden sollte (der Fehlerbericht erwägt, die Unterstützung für x = x zu beenden, wurde jedoch aus Angst, zu viel vorhandenen Code zu beschädigen, geschlossen ; stattdessen schlage ich hier vor, x = x in mehr Fällen zum Laufen zu bringen). Oder ich vermisse einen weiteren schönen Punkt ...

EDIT2: CPython hat anscheinend genau das im aktuellen 3.4-Trunk getan: http://bugs.python.org/ issue17853... oder nicht? Sie haben den Bytecode aus einem etwas anderen Grund eingeführt und verwenden ihn nicht systematisch ...

19
Armin Rigo 13 Dez. 2013 im 21:10

Kurz gesagt, dies ist ein Eckfall von Pythons Scoping, der etwas inkonsistent ist, aber aus Gründen der Abwärtskompatibilität beibehalten werden muss (und weil nicht so klar ist, wie die richtige Antwort lauten sollte). Sie können viele der Originaldiskussionen auf der Python-Mailingliste sehen, wenn PEP 227 wurde implementiert, und einige im Fehler, für den dieses Verhalten behoben wurde.

Mit dem Modul dis können wir herausfinden, warum es einen Unterschied gibt Codeobjekte, um den Bytecode anzuzeigen, zu dem ein Code kompiliert wurde. Ich bin auf Python 2.6, daher können die Details etwas anders sein - aber ich sehe das gleiche Verhalten, daher denke ich, dass es wahrscheinlich nahe genug an 2.7 liegt.

Der Code, der jedes verschachtelte MyClass initialisiert, befindet sich in einem Codeobjekt, auf das Sie über die Attribute der Funktionen der obersten Ebene zugreifen können. (Ich benenne die Funktionen von Beispiel 5 und Beispiel 6 in f1 bzw. f2 um.)

Das Codeobjekt verfügt über ein co_consts -Tupel, das das myfunc -Codeobjekt enthält, das wiederum den Code enthält, der ausgeführt wird, wenn MyClass erstellt wird:

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]

Dann können Sie den Unterschied zwischen ihnen im Bytecode mit dis.dis sehen:

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        

Der einzige Unterschied besteht also darin, dass in MyClass1 x mit der Option LOAD_NAME geladen wird, während in MyClass2 mit LOAD_DEREF geladen wird. LOAD_DEREF sucht einen Namen in einem umschließenden Bereich, sodass er 'x in myfunc' erhält. LOAD_NAME folgt keinen verschachtelten Bereichen. Da die in myfunc oder f1 gebundenen x Namen nicht angezeigt werden, wird die Bindung auf Modulebene abgerufen.

Dann stellt sich die Frage, warum der Code der beiden Versionen von MyClass zu zwei verschiedenen Opcodes kompiliert wird. In f1 schattiert die Bindung x im Klassenbereich, während in f2 ein neuer Name gebunden wird. Wenn die MyClass Bereiche verschachtelte Funktionen anstelle von Klassen wären, würde die Zeile y = x in f2 gleich kompiliert, aber die Zeile x = x in f1 wäre a LOAD_FAST - Dies liegt daran, dass der Compiler wissen würde, dass x in der Funktion gebunden ist. Daher sollte er LOAD_FAST verwenden, um eine lokale Variable abzurufen. Dies würde mit einem UnboundLocalError fehlschlagen, wenn es aufgerufen wird.

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

Dies schlägt fehl, weil die Funktion MyFunc dann LOAD_FAST verwendet:

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        

(Abgesehen davon ist es keine große Überraschung, dass es einen Unterschied in der Interaktion von Scoping mit Code im Klassenkörper und Code in einer Funktion geben sollte. Sie können dies feststellen, da Bindungen auf Klassenebene in Methoden nicht verfügbar sind. Methodenbereiche sind nicht wie verschachtelte Funktionen im Klassenbereich verschachtelt. Sie müssen sie explizit über die Klasse oder mithilfe von self. erreichen (was auf die Klasse zurückgreift, wenn es keine gibt Bindung auf Instanzebene).)

7
babbageclunk 27 Nov. 2013 im 19:25