PEP 3104 – Zugriff auf Namen in äußeren Gültigkeitsbereichen
- Autor:
- Ka-Ping Yee <ping at zesty.ca>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 12. Okt. 2006
- Python-Version:
- 3.0
- Post-History:
Zusammenfassung
In den meisten Sprachen, die verschachtelte Gültigkeitsbereiche unterstützen, kann Code auf jeden Namen im nächstgelegenen umschließenden Gültigkeitsbereich zugreifen oder diesen neu binden (zuweisen). Derzeit kann Python-Code auf einen Namen in jedem umschließenden Gültigkeitsbereich zugreifen, aber nur Namen in zwei Gültigkeitsbereichen neu binden: dem lokalen Gültigkeitsbereich (durch einfache Zuweisung) oder dem Modul-Globalen-Gültigkeitsbereich (mittels einer global-Deklaration).
Diese Einschränkung wurde viele Male auf der Python-Dev-Mailingliste und anderswo angesprochen und hat zu ausgedehnten Diskussionen und vielen Vorschlägen zur Beseitigung dieser Einschränkung geführt. Dieses PEP fasst die verschiedenen vorgeschlagenen Alternativen zusammen, zusammen mit den für jede genannten Vor- und Nachteile.
Begründung
Vor Version 2.1 ähnelte Pythons Behandlung von Gültigkeitsbereichen der von Standard-C: Innerhalb einer Datei gab es nur zwei Ebenen von Gültigkeitsbereichen, global und lokal. In C ist dies eine natürliche Folge der Tatsache, dass Funktionsdefinitionen nicht verschachtelt werden können. Aber in Python, obwohl Funktionen normalerweise auf der obersten Ebene definiert werden, kann eine Funktionsdefinition überall ausgeführt werden. Dies gab Python das syntaktische Aussehen einer verschachtelten Gültigkeitsbereichsregel, aber nicht die Semantik, und führte zu Inkonsistenzen, die für einige Programmierer überraschend waren – zum Beispiel würde eine rekursive Funktion, die auf der obersten Ebene funktionierte, aufhören zu funktionieren, wenn sie in eine andere Funktion verschoben würde, da der Name der rekursiven Funktion in ihrem eigenen Gültigkeitsbereich nicht mehr sichtbar wäre. Dies verletzt die Intuition, dass eine Funktion sich konsistent verhalten sollte, wenn sie in verschiedenen Kontexten platziert wird. Hier ist ein Beispiel
def enclosing_function():
def factorial(n):
if n < 2:
return 1
return n * factorial(n - 1) # fails with NameError
print factorial(5)
Python 2.1 näherte sich statisch verschachtelten Gültigkeitsbereichen an, indem es die Namen sichtbar machte, die in allen umschließenden Gültigkeitsbereichen gebunden sind (siehe PEP 227). Diese Änderung bewirkt, dass das obige Codebeispiel wie erwartet funktioniert. Da jedoch jede Zuweisung an einen Namen diesen Namen implizit als lokal deklariert, ist es unmöglich, einen Namen in einem äußeren Gültigkeitsbereich neu zu binden (es sei denn, eine global-Deklaration erzwingt, dass der Name global ist). Somit funktioniert der folgende Code, der eine Zahl anzeigen soll, die durch Klicken auf Schaltflächen erhöht und verringert werden kann, nicht wie jemand, der mit lexikalischer Gültigkeitsbereichsregel vertraut ist, erwarten würde
def make_scoreboard(frame, score=0):
label = Label(frame)
label.pack()
for i in [-10, -1, 1, 10]:
def increment(step=i):
score = score + step # fails with UnboundLocalError
label['text'] = score
button = Button(frame, text='%+d' % i, command=increment)
button.pack()
return label
Pythons Syntax bietet keine Möglichkeit anzuzeigen, dass der Name score, der in increment erwähnt wird, sich auf die Variable score bezieht, die in make_scoreboard gebunden ist, und nicht auf eine lokale Variable in increment. Benutzer und Entwickler von Python haben Interesse bekundet, diese Einschränkung zu beseitigen, damit Python die volle Flexibilität des Algol-artigen Gültigkeitsbereichsmodells erhalten kann, das in vielen Programmiersprachen, einschließlich JavaScript, Perl, Ruby, Scheme, Smalltalk, C mit GNU-Erweiterungen und C# 2.0, Standard ist.
Es wurde argumentiert, dass eine solche Funktion nicht notwendig sei, da eine neu bindbare äußere Variable durch Einpacken in ein mutierbares Objekt simuliert werden kann
class Namespace:
pass
def make_scoreboard(frame, score=0):
ns = Namespace()
ns.score = 0
label = Label(frame)
label.pack()
for i in [-10, -1, 1, 10]:
def increment(step=i):
ns.score = ns.score + step
label['text'] = ns.score
button = Button(frame, text='%+d' % i, command=increment)
button.pack()
return label
Dieser Workaround hebt jedoch nur die Mängel bestehender Gültigkeitsbereiche hervor: Der Zweck einer Funktion ist es, Code in ihrem eigenen Namensraum zu kapseln, daher erscheint es bedauerlich, dass der Programmierer zusätzliche Namensräume erstellen muss, um fehlende Funktionalität in den bestehenden lokalen Gültigkeitsbereichen auszugleichen, und dann entscheiden muss, ob jeder Name im realen oder simulierten Gültigkeitsbereich angesiedelt werden soll.
Ein weiterer häufiger Einwand ist, dass die gewünschte Funktionalität auch als Klasse implementiert werden kann, wenn auch etwas umständlicher. Eine Erwiderung auf diesen Einwand ist, dass die Existenz eines anderen Implementierungsstils kein Grund ist, ein unterstütztes Programmierkonstrukt (verschachtelte Gültigkeitsbereiche) funktional unvollständig zu lassen. Python wird manchmal als „Multi-Paradigma-Sprache“ bezeichnet, da es aus seiner Unterstützung und seiner anmutigen Integration mehrerer Programmierparadigmen so viel Stärke, praktische Flexibilität und pädagogische Kraft schöpft.
Ein Vorschlag für die Syntax von Gültigkeitsbereichen erschien bereits 1994 auf Python-Dev [1], lange bevor PEP 227 zur Unterstützung von verschachtelten Gültigkeitsbereichen angenommen wurde. Damals war Guidos Antwort
Dies ist gefährlich nahe an der Einführung von CSNS [klassische statische verschachtelte Gültigkeitsbereiche]. *Wenn* Sie dies tun würden, scheinen Ihre vorgeschlagenen Semantiken für Gültigkeitsbereiche in Ordnung. Ich glaube immer noch, dass es nicht genug Bedarf für CSNS gibt, um diese Art von Konstrukt zu rechtfertigen …
Nach PEP 227 ist die „Diskussion über die Neubindung äußerer Namen“ oft genug auf Python-Dev wieder aufgetaucht, dass sie zu einem vertrauten Ereignis geworden ist, das seit mindestens 2003 in seiner jetzigen Form wiederkehrt [2]. Obwohl bisher keine der in diesen Diskussionen vorgeschlagenen Sprachänderungen übernommen wurde, hat Guido anerkannt, dass eine Sprachänderung eine Überlegung wert ist [12].
Andere Sprachen
Zur Hintergrundinformation beschreibt dieser Abschnitt, wie einige andere Sprachen verschachtelte Gültigkeitsbereiche und Neubindungen behandeln.
JavaScript, Perl, Scheme, Smalltalk, GNU C, C# 2.0
Diese Sprachen verwenden Variablendeklarationen zur Angabe des Gültigkeitsbereichs. In JavaScript wird eine lexikalisch gültige Variable mit dem Schlüsselwort var deklariert; nicht deklarierte Variablennamen werden als global angenommen. In Perl wird eine lexikalisch gültige Variable mit dem Schlüsselwort my deklariert; nicht deklarierte Variablennamen werden als global angenommen. In Scheme müssen alle Variablen deklariert werden (mit define oder let, oder als formale Parameter). In Smalltalk kann jeder Block damit beginnen, eine Liste von lokalen Variablennamen zwischen vertikalen Strichen zu deklarieren. C und C# erfordern Typdeklarationen für alle Variablen. In all diesen Fällen gehört die Variable zu dem Gültigkeitsbereich, der die Deklaration enthält.
Ruby (ab 1.8)
Ruby ist ein lehrreiches Beispiel, da es die einzige andere derzeit beliebte Sprache zu sein scheint, die wie Python versucht, statisch verschachtelte Gültigkeitsbereiche zu unterstützen, ohne Variablendeklarationen zu verlangen, und daher eine ungewöhnliche Lösung finden muss. Funktionen in Ruby können andere Funktionsdefinitionen enthalten und auch Codeblöcke mit geschweiften Klammern. Blöcke haben Zugriff auf äußere Variablen, verschachtelte Funktionen jedoch nicht. Innerhalb eines Blocks impliziert eine Zuweisung an einen Namen eine Deklaration einer lokalen Variablen nur dann, wenn sie keinen Namen überschatten würde, der bereits in einem äußeren Gültigkeitsbereich gebunden ist; andernfalls wird die Zuweisung als Neubindung des äußeren Namens interpretiert. Rubys Syntax und Regeln für Gültigkeitsbereiche wurden ebenfalls sehr ausführlich diskutiert, und Änderungen scheinen in Ruby 2.0 wahrscheinlich [28].
Übersicht der Vorschläge
Es gab viele verschiedene Vorschläge auf Python-Dev für Möglichkeiten, Namen in äußeren Gültigkeitsbereichen neu zu binden. Sie fallen alle in zwei Kategorien: neue Syntax in dem Gültigkeitsbereich, in dem der Name gebunden ist, oder neue Syntax in dem Gültigkeitsbereich, in dem der Name verwendet wird.
Neue Syntax in der Bindungs-(äußeren)-Gültigkeitsbereich
Deklaration zur Überschreibung des Gültigkeitsbereichs
Die Vorschläge in dieser Kategorie schlagen alle eine neue Art von Deklarationsanweisung vor, ähnlich dem var von JavaScript. Einige mögliche Schlüsselwörter wurden zu diesem Zweck vorgeschlagen
In all diesen Vorschlägen würde eine Deklaration wie var x in einem bestimmten Gültigkeitsbereich S dazu führen, dass alle Referenzen auf x in Gültigkeitsbereichen, die in S verschachtelt sind, sich auf das in S gebundene x beziehen.
Der Hauptnachteil dieser Kategorie von Vorschlägen ist, dass die Bedeutung einer Funktionsdefinition kontextabhängig würde. Das Verschieben einer Funktionsdefinition in einen anderen Block könnte dazu führen, dass lokale Namensreferenzen in der Funktion nicht-lokal werden, aufgrund von Deklarationen im umschließenden Block. Für Blöcke in Ruby 1.8 ist dies tatsächlich der Fall; im folgenden Beispiel haben die beiden Setter unterschiedliche Auswirkungen, obwohl sie identisch aussehen
setter1 = proc { | x | y = x } # y is local here
y = 13
setter2 = proc { | x | y = x } # y is nonlocal here
setter1.call(99)
puts y # prints 13
setter2.call(77)
puts y # prints 77
Beachten Sie, dass dieses Vorschlagsmodell zwar Deklarationen in JavaScript und Perl ähnelt, die Auswirkung auf die Sprache jedoch anders ist, da in diesen Sprachen nicht deklarierte Variablen standardmäßig global sind, während in Python nicht deklarierte Variablen standardmäßig lokal sind. Daher kann das Verschieben einer Funktion in einen anderen Block in JavaScript oder Perl nur den Gültigkeitsbereich einer zuvor globalen Namensreferenz verringern, während er in Python mit diesem Vorschlag den Gültigkeitsbereich einer zuvor lokalen Namensreferenz erweitern könnte.
Erforderliche Variablendeklaration
Ein radikalerer Vorschlag [21] schlägt vor, Pythons Konvention zur Vermutung des Gültigkeitsbereichs vollständig abzuschaffen und zu verlangen, dass alle Namen in dem Gültigkeitsbereich deklariert werden, in dem sie gebunden werden sollen, ähnlich wie in Scheme. Mit diesem Vorschlag würden var x = 3 sowohl x als dem lokalen Gültigkeitsbereich zugehörig deklarieren und binden, während x = 3 das vorhandene sichtbare x neu binden würde. In einem Kontext ohne umschließenden Gültigkeitsbereich mit einer var x-Deklaration wäre die Anweisung x = 3 statisch als illegal bestimmt.
Dieser Vorschlag ergibt ein einfaches und konsistentes Modell, wäre aber mit bestehendem Python-Code inkompatibel.
Neue Syntax im referenzierenden (inneren) Gültigkeitsbereich
Es gibt drei Arten von Vorschlägen in dieser Kategorie.
Ausdruck für äußere Referenz
Diese Art von Vorschlag schlägt eine neue Möglichkeit vor, auf eine Variable in einem äußeren Gültigkeitsbereich zu verweisen, wenn die Variable in einem Ausdruck verwendet wird. Eine dafür vorgeschlagene Syntax ist .x [7], die sich auf x beziehen würde, ohne eine lokale Bindung dafür zu erstellen. Ein Bedenken bei diesem Vorschlag ist, dass in vielen Kontexten x und .x austauschbar verwendet werden könnten, was den Leser verwirren würde [31]. Eine eng verwandte Idee ist die Verwendung mehrerer Punkte, um die Anzahl der aufsteigenden Gültigkeitsbereichsebenen anzugeben [8], aber die meisten halten dies für zu fehleranfällig [17].
Neubindungsoperator
Dieser Vorschlag schlägt einen neuen zuweisungsähnlichen Operator vor, der einen Namen neu bindet, ohne den Namen als lokal zu deklarieren [2]. Während die Anweisung x = 3 sowohl x als lokale Variable deklariert und an 3 bindet, würde die Anweisung x := 3 die bestehende Bindung von x ändern, ohne sie als lokal zu deklarieren.
Dies ist eine einfache Lösung, wurde aber gemäß PEP 3099 abgelehnt (vielleicht, weil sie zu leicht übersehen oder mit = verwechselt werden könnte).
Deklaration zur Überschreibung des Gültigkeitsbereichs
Die Vorschläge in dieser Kategorie schlagen eine neue Art von Deklarationsanweisung im inneren Gültigkeitsbereich vor, die verhindert, dass ein Name lokal wird. Diese Anweisung wäre in ihrer Natur ähnlich der global-Anweisung, aber anstatt den Namen auf eine Bindung im obersten Modul-Gültigkeitsbereich verweisen zu lassen, würde sie den Namen auf die Bindung im nächstgelegenen umschließenden Gültigkeitsbereich verweisen lassen.
Dieser Ansatz ist attraktiv wegen seiner Parallele zu einem vertrauten Python-Konstrukt und weil er Kontextunabhängigkeit für Funktionsdefinitionen beibehält.
Dieser Ansatz hat auch Vorteile aus Sicht der Sicherheit und des Debuggings. Das resultierende Python würde nicht nur die Funktionalität anderer Sprachen mit verschachtelten Gültigkeitsbereichen abdecken, sondern dies auch mit einer Syntax tun, die für die defensive Programmierung wohl noch besser geeignet ist. In den meisten anderen Sprachen reduziert eine Deklaration den Gültigkeitsbereich eines bestehenden Namens, sodass das versehentliche Weglassen der Deklaration weiterreichende (d. h. gefährlichere) Auswirkungen als erwartet haben könnte. In Python mit diesem Vorschlag ist die zusätzliche Anstrengung, die Deklaration hinzuzufügen, an das erhöhte Risiko nicht-lokaler Auswirkungen angepasst (d. h. der Weg des geringsten Widerstands ist der sicherere Weg).
Viele Schreibweisen wurden für eine solche Deklaration vorgeschlagen
scoped x[1]global x in f[3] (explizite Angabe des Gültigkeitsbereichs)free x[5]outer x[6]use x[9]global x[10] (Änderung der Bedeutung vonglobal)nonlocal x[11]global x outer[18]global in x[18]not global x[18]extern x[20]ref x[22]refer x[22]share x[22]sharing x[22]common x[22]using x[22]borrow x[22]reuse x[23]scope f x[25] (explizite Angabe des Gültigkeitsbereichs)
Die am häufigsten diskutierten Optionen scheinen outer, global und nonlocal zu sein. outer wird bereits als Variablenname und Attributname in der Standardbibliothek verwendet. Das Wort global hat eine widersprüchliche Bedeutung, da „globale Variable“ im Allgemeinen als eine Variable mit Gültigkeitsbereich auf oberster Ebene verstanden wird [27]. In C bedeutet das Schlüsselwort extern, dass ein Name auf eine Variable in einer anderen Kompilierungseinheit verweist. Während nonlocal etwas lang und weniger angenehm klingt als einige der anderen Optionen, hat es genau die richtige Bedeutung: Es deklariert einen Namen als nicht lokal.
Vorgeschlagene Lösung
Die von diesem PEP vorgeschlagene Lösung ist die Hinzufügung einer Deklaration zur Überschreibung des Gültigkeitsbereichs im referenzierenden (inneren) Gültigkeitsbereich. Guido hat seine Präferenz für diese Kategorie von Lösungen auf Python-Dev geäußert [14] und nonlocal als Schlüsselwort befürwortet [19].
Die vorgeschlagene Deklaration
nonlocal x
verhindert, dass x im aktuellen Gültigkeitsbereich zu einem lokalen Namen wird. Alle Vorkommen von x im aktuellen Gültigkeitsbereich beziehen sich auf das in einem äußeren umschließenden Gültigkeitsbereich gebundene x. Wie bei global sind mehrere Namen zulässig
nonlocal x, y, z
Wenn keine vorhandene Bindung in einem umschließenden Gültigkeitsbereich existiert, löst der Compiler einen `SyntaxError` aus. (Es mag eine Überdehnung sein, dies als Syntaxfehler zu bezeichnen, aber bisher wird `SyntaxError` für alle Fehler zur Kompilierungszeit verwendet, einschließlich beispielsweise `__future__` import mit einem unbekannten Feature-Namen.) Guido hat gesagt, dass diese Art von Deklaration in Abwesenheit einer äußeren Bindung als Fehler betrachtet werden sollte [16].
Wenn eine nonlocal-Deklaration mit dem Namen eines formalen Parameters im lokalen Gültigkeitsbereich kollidiert, löst der Compiler einen `SyntaxError` aus.
Eine Kurzform ist ebenfalls zulässig, bei der nonlocal einer Zuweisung oder einer erweiterten Zuweisung vorangestellt wird
nonlocal x = 3
Das Obige hat exakt die gleiche Bedeutung wie nonlocal x; x = 3. (Guido unterstützt eine ähnliche Form der global-Anweisung [24].)
Auf der linken Seite der Kurzform sind nur Bezeichner erlaubt, keine Zielausdrücke wie x[0]. Ansonsten sind alle Formen der Zuweisung erlaubt. Die vorgeschlagene Grammatik der nonlocal-Anweisung ist
nonlocal_stmt ::=
"nonlocal" identifier ("," identifier)*
["=" (target_list "=")+ expression_list]
| "nonlocal" identifier augop expression_list
Der Grund für die Zulassung all dieser Zuweisungsformen liegt darin, dass sie das Verständnis der nonlocal-Anweisung vereinfacht. Die Trennung der Kurzform in eine Deklaration und eine Zuweisung reicht aus, um zu verstehen, was sie bedeutet und ob sie gültig ist.
Abwärtskompatibilität
Dieses PEP zielt auf Python 3000 ab, wie von Guido vorgeschlagen [19]. Andere haben jedoch angemerkt, dass einige in diesem PEP berücksichtigte Optionen kleine Änderungen sein könnten, die in Python 2.x machbar sind [26], in diesem Fall könnte dieses PEP möglicherweise zu einem PEP der 2.x-Reihe werden.
Als (sehr grobe) Messung der Auswirkungen der Einführung eines neuen Schlüsselworts ist hier die Anzahl der Male, die einige der vorgeschlagenen Schlüsselwörter als Bezeichner in der Standardbibliothek vorkommen, gemäß einer Analyse des Python SVN-Repositorys vom 5. November 2006
nonlocal 0
use 2
using 3
reuse 4
free 8
outer 147
global erscheint 214 Mal als bestehendes Schlüsselwort. Als Maß für die Auswirkung der Verwendung von global als Schlüsselwort für den äußeren Gültigkeitsbereich gibt es 18 Dateien in der Standardbibliothek, die als Ergebnis einer solchen Änderung fehlschlagen würden (da eine Funktion eine Variable als global deklariert, bevor diese Variable im globalen Gültigkeitsbereich eingeführt wurde)
cgi.py
dummy_thread.py
mhlib.py
mimetypes.py
idlelib/PyShell.py
idlelib/run.py
msilib/__init__.py
test/inspect_fodder.py
test/test_compiler.py
test/test_decimal.py
test/test_descr.py
test/test_dummy_threading.py
test/test_fileinput.py
test/test_global.py (not counted: this tests the keyword itself)
test/test_grammar.py (not counted: this tests the keyword itself)
test/test_itertools.py
test/test_multifile.py
test/test_scope.py (not counted: this tests the keyword itself)
test/test_threaded_import.py
test/test_threadsignals.py
test/test_warnings.py
Referenzen
[15] Explicit Lexical Scoping (pre-PEP?) (Guido van Rossum) https://mail.python.org/pipermail/python-dev/2006-July/066995.html
Danksagungen
Die in diesem PEP genannten Ideen und Vorschläge stammen aus unzähligen Python-Dev-Beiträgen. Danke an Jim Jewett, Mike Orr, Jason Orendorff und Christian Tanzer für die Vorschläge spezifischer Bearbeitungen dieses PEP.
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-3104.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT