PEP 227 – Statically Nested Scopes
- Autor:
- Jeremy Hylton <jeremy at alum.mit.edu>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 01-Nov-2000
- Python-Version:
- 2.1
- Post-History:
Zusammenfassung
Dieses PEP beschreibt die Hinzufügung von statisch verschachtelten Geltungsbereichen (lexikalische Geltungsbereiche) für Python 2.2 und als Option auf Quellcode-Ebene für Python 2.1. Darüber hinaus wird Python 2.1 Warnungen für Konstrukte ausgeben, deren Bedeutung sich bei Aktivierung dieses Features ändern kann.
Die alte Sprachdefinition (2.0 und früher) definiert genau drei Namensräume, die zur Auflösung von Namen verwendet werden – den lokalen, den globalen und den integrierten Namensraum. Die Hinzufügung von verschachtelten Geltungsbereichen ermöglicht die Auflösung von ungebundenen lokalen Namen in den Namensräumen umschließender Funktionen.
Die sichtbarste Folge dieser Änderung ist, dass Lambdas (und andere verschachtelte Funktionen) auf Variablen zugreifen können, die im umgebenden Namensraum definiert sind. Derzeit müssen Lambdas oft Standardargumente verwenden, um explizit Bindungen im Namensraum des Lambdas zu erstellen.
Einleitung
Dieser Vorschlag ändert die Regeln für die Auflösung von freien Variablen in Python-Funktionen. Die neuen Semantiken für die Namensauflösung treten mit Python 2.2 in Kraft. Diese Semantiken sind auch in Python 2.1 durch Hinzufügen von „from __future__ import nested_scopes“ am Anfang eines Moduls verfügbar. (Siehe PEP 236.)
Die Python 2.0-Definition legt genau drei Namensräume fest, die für jeden Namen überprüft werden – den lokalen Namensraum, den globalen Namensraum und den integrierten Namensraum. Gemäß dieser Definition sind, wenn eine Funktion A innerhalb einer Funktion B definiert ist, die in B gebundenen Namen in A nicht sichtbar. Der Vorschlag ändert die Regeln so, dass in B gebundene Namen in A sichtbar sind (es sei denn, A enthält eine Namensbindung, die die Bindung in B verdeckt).
Diese Spezifikation führt Regeln für lexikalische Geltungsbereiche ein, die in Algol-ähnlichen Sprachen üblich sind. Die Kombination aus lexikalischer Geltungsbereich und bestehender Unterstützung für First-Class-Funktionen erinnert an Scheme.
Die geänderten Geltungsbereichsregeln beheben zwei Probleme – den begrenzten Nutzen von Lambda-Ausdrücken (und verschachtelten Funktionen im Allgemeinen) und die häufige Verwirrung bei neuen Benutzern, die mit anderen Sprachen vertraut sind, die verschachtelte lexikalische Geltungsbereiche unterstützen, z.B. die Unfähigkeit, rekursive Funktionen außer auf Modulebene zu definieren.
Der Lambda-Ausdruck ergibt eine unbenannte Funktion, die einen einzelnen Ausdruck auswertet. Er wird oft für Callback-Funktionen verwendet. Im folgenden Beispiel (geschrieben nach den Regeln von Python 2.0) muss jeder im Körper des Lambdas verwendete Name explizit als Standardargument an das Lambda übergeben werden.
from Tkinter import *
root = Tk()
Button(root, text="Click here",
command=lambda root=root: root.test.configure(text="..."))
Dieser Ansatz ist umständlich, insbesondere wenn mehrere Namen im Körper des Lambdas verwendet werden. Die lange Liste von Standardargumenten verdeckt den Zweck des Codes. Die vorgeschlagene Lösung implementiert den Standardargument-Ansatz in groben Zügen automatisch. Das Argument „root=root“ kann weggelassen werden.
Die neuen Semantiken für die Namensauflösung bewirken, dass einige Programme anders als unter Python 2.0 ausgeführt werden. In einigen Fällen schlagen Programme fehl. In anderen Fällen werden Namen, die zuvor über den globalen Namensraum aufgelöst wurden, über den lokalen Namensraum einer umschließenden Funktion aufgelöst. In Python 2.1 werden Warnungen für alle Anweisungen ausgegeben, die sich anders verhalten werden.
Spezifikation
Python ist eine statisch verzögerte Sprache mit Blockstruktur im traditionellen Sinne von Algol. Ein Codeblock oder -bereich, wie ein Modul, eine Klassendefinition oder ein Funktionskörper, ist die Grundeinheit eines Programms.
Namen verweisen auf Objekte. Namen werden durch Namensbindungsoperationen eingeführt. Jedes Vorkommen eines Namens im Programmtext bezieht sich auf die Bindung dieses Namens, die im innersten Funktionsblock, der die Verwendung enthält, hergestellt wurde.
Die Namensbindungsoperationen sind Argumentdeklaration, Zuweisung, Klassen- und Funktionsdefinition, Importanweisungen, for-Anweisungen und except-Klauseln. Jede Namensbindung erfolgt innerhalb eines Blocks, der durch eine Klassen- oder Funktionsdefinition definiert ist, oder auf Modulebene (der oberste Codeblock).
Wenn ein Name irgendwo innerhalb eines Codeblocks gebunden wird, werden alle Verwendungen dieses Namens innerhalb des Blocks als Referenzen auf den aktuellen Block behandelt. (Hinweis: Dies kann zu Fehlern führen, wenn ein Name innerhalb eines Blocks verwendet wird, bevor er gebunden ist.)
Wenn die globale Anweisung innerhalb eines Blocks vorkommt, beziehen sich alle Verwendungen des in der Anweisung angegebenen Namens auf die Bindung dieses Namens im obersten Namensraum. Namen werden im obersten Namensraum aufgelöst, indem der globale Namensraum durchsucht wird, d.h. der Namensraum des Moduls, das den Codeblock enthält, und der integrierte Namensraum, d.h. der Namensraum des Moduls __builtin__. Der globale Namensraum wird zuerst durchsucht. Wenn der Name dort nicht gefunden wird, wird der integrierte Namensraum durchsucht. Die globale Anweisung muss allen Verwendungen des Namens vorausgehen.
Wenn ein Name innerhalb eines Codeblocks verwendet wird, aber dort nicht gebunden und nicht als global deklariert ist, wird die Verwendung als Referenz auf den nächstgelegenen umschließenden Funktionsbereich behandelt. (Hinweis: Wenn ein Bereich innerhalb einer Klassendefinition enthalten ist, sind die Namensbindungen, die im Klassenblock auftreten, für verschachtelte Funktionen nicht sichtbar.)
Eine Klassendefinition ist eine ausführbare Anweisung, die Verwendungen und Definitionen von Namen enthalten kann. Diese Referenzen folgen den normalen Regeln für die Namensauflösung. Der Namensraum der Klassendefinition wird zum Attribut-Dictionary der Klasse.
Die folgenden Operationen sind Namensbindungsoperationen. Wenn sie innerhalb eines Blocks auftreten, führen sie neue lokale Namen im aktuellen Block ein, es sei denn, es gibt auch eine globale Deklaration.
Function definition: def name ...
Argument declaration: def f(...name...), lambda ...name...
Class definition: class name ...
Assignment statement: name = ...
Import statement: import name, import module as name,
from module import name
Implicit assignment: names are bound by for statements and except
clauses
Es gibt mehrere Fälle, in denen Python-Anweisungen in Verbindung mit verschachtelten Geltungsbereichen mit freien Variablen illegal sind.
Wenn eine Variable in einem umschließenden Bereich referenziert wird, ist es ein Fehler, den Namen zu löschen. Der Compiler löst eine SyntaxError für „del name“ aus.
Wenn die Wildcard-Form des Imports (import *) in einer Funktion verwendet wird und die Funktion einen verschachtelten Block mit freien Variablen enthält, löst der Compiler eine SyntaxError aus.
Wenn exec in einer Funktion verwendet wird und die Funktion einen verschachtelten Block mit freien Variablen enthält, löst der Compiler eine SyntaxError aus, es sei denn, exec gibt explizit den lokalen Namensraum für exec an. (Mit anderen Worten, „exec obj“ wäre illegal, aber „exec obj in ns“ wäre legal.)
Wenn ein Name, der in einem Funktionsbereich gebunden ist, auch der Name eines globalen Modulnamens oder eines Standard-Builtin-Namens ist und die Funktion einen verschachtelten Funktionsbereich enthält, der auf den Namen zugreift, gibt der Compiler eine Warnung aus. Die Namensauflösungsregeln führen unter Python 2.0 zu anderen Bindungen als unter Python 2.2. Die Warnung zeigt an, dass das Programm möglicherweise nicht mit allen Python-Versionen korrekt ausgeführt wird.
Diskussion
Die spezifizierten Regeln erlauben es, dass Namen, die in einer Funktion definiert sind, in jeder verschachtelten Funktion referenziert werden können, die mit dieser Funktion definiert wurde. Die Namensauflösungsregeln sind typisch für statisch verzögerte Sprachen, mit drei Hauptausnahmen:
- Namen im Klassenbereich sind nicht zugänglich.
- Die globale Anweisung schaltet die normalen Regeln ab.
- Variablen werden nicht deklariert.
Namen im Klassenbereich sind nicht zugänglich. Namen werden im innersten umschließenden Funktionsbereich aufgelöst. Wenn eine Klassendefinition in einer Kette von verschachtelten Bereichen auftritt, überspringt der Auflösungsprozess Klassendefinitionen. Diese Regel verhindert seltsame Wechselwirkungen zwischen Klassenattributen und Zugriff auf lokale Variablen. Wenn eine Namensbindungsoperation in einer Klassendefinition auftritt, erstellt sie ein Attribut für das resultierende Klassenobjekt. Um auf diese Variable in einer Methode oder in einer Funktion zuzugreifen, die in einer Methode verschachtelt ist, muss ein Attributreferenz verwendet werden, entweder über self oder über den Klassennamen.
Eine Alternative wäre gewesen, die Namensbindung im Klassenbereich exakt wie die Namensbindung im Funktionsbereich zu behandeln. Diese Regel würde es erlauben, auf Klassenattribute entweder über Attributreferenz oder einfachen Namen zuzugreifen. Diese Option wurde verworfen, da sie inkonsistent mit allen anderen Formen des Zugriffs auf Klassen- und Instanzattribute gewesen wäre, die immer Attributreferenzen verwenden. Code, der einfache Namen verwendet hätte, wäre unklar gewesen.
Die globale Anweisung schaltet die normalen Regeln ab. Unter dem Vorschlag hat die globale Anweisung genau die gleiche Wirkung wie für Python 2.0. Sie ist auch bemerkenswert, da sie es ermöglicht, dass Namensbindungsoperationen, die in einem Block ausgeführt werden, Bindungen in einem anderen Block (dem Modul) ändern.
Variablen werden nicht deklariert. Wenn eine Namensbindungsoperation irgendwo in einer Funktion auftritt, wird dieser Name als lokal für die Funktion behandelt und alle Referenzen beziehen sich auf die lokale Bindung. Wenn eine Referenz auftritt, bevor der Name gebunden ist, wird ein NameError ausgelöst. Die einzige Art der Deklaration ist die globale Anweisung, die es Programmen ermöglicht, mit veränderlichen globalen Variablen zu schreiben. Infolgedessen ist es nicht möglich, einen in einem umschließenden Bereich definierten Namen neu zu binden. Eine Zuweisungsoperation kann nur einen Namen im aktuellen Bereich oder im globalen Bereich binden. Das Fehlen von Deklarationen und die Unfähigkeit, Namen in umschließenden Bereichen neu zu binden, sind für lexikalisch verzögerte Sprachen ungewöhnlich; es gibt typischerweise einen Mechanismus zur Erstellung von Namensbindungen (z.B. lambda und let in Scheme) und einen Mechanismus zur Änderung der Bindungen (set! in Scheme).
Beispiele
Einige Beispiele sind enthalten, um die Funktionsweise der Regeln zu veranschaulichen.
>>> def make_adder(base):
... def adder(x):
... return base + x
... return adder
>>> add5 = make_adder(5)
>>> add5(6)
11
>>> def make_fact():
... def fact(n):
... if n == 1:
... return 1L
... else:
... return n * fact(n - 1)
... return fact
>>> fact = make_fact()
>>> fact(7)
5040L
>>> def make_wrapper(obj):
... class Wrapper:
... def __getattr__(self, attr):
... if attr[0] != '_':
... return getattr(obj, attr)
... else:
... raise AttributeError, attr
... return Wrapper()
>>> class Test:
... public = 2
... _private = 3
>>> w = make_wrapper(Test())
>>> w.public
2
>>> w._private
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: _private
Ein Beispiel von Tim Peters demonstriert die potenziellen Fallstricke verschachtelter Geltungsbereiche bei fehlenden Deklarationen
i = 6
def f(x):
def g():
print i
# ...
# skip to the next page
# ...
for i in x: # ah, i *is* local to f, so this is what g sees
pass
g()
Der Aufruf von g() bezieht sich auf die Variable i, die in f() durch die for-Schleife gebunden ist. Wenn g() aufgerufen wird, bevor die Schleife ausgeführt wird, wird ein NameError ausgelöst.
Abwärtskompatibilität
Es gibt zwei Arten von Kompatibilitätsproblemen, die durch verschachtelte Geltungsbereiche verursacht werden. In einem Fall verhält sich Code, der sich in früheren Versionen anders verhielt, aufgrund verschachtelter Geltungsbereiche anders. In den anderen Fällen interagieren bestimmte Konstrukte schlecht mit verschachtelten Geltungsbereichen und lösen zur Kompilierzeit SyntaxErrors aus.
Das folgende Beispiel von Skip Montanaro illustriert die erste Art von Problemen
x = 1
def f1():
x = 2
def inner():
print x
inner()
Nach den Python 2.0-Regeln bezieht sich die print-Anweisung innerhalb von inner() auf die globale Variable x und gibt 1 aus, wenn f1() aufgerufen wird. Nach den neuen Regeln bezieht sie sich auf den Namensraum von f1(), dem nächstgelegenen umschließenden Bereich mit einer Bindung.
Das Problem tritt nur auf, wenn eine globale Variable und eine lokale Variable denselben Namen haben und eine verschachtelte Funktion diesen Namen verwendet, um sich auf die globale Variable zu beziehen. Dies ist eine schlechte Programmierpraxis, da Leser die beiden unterschiedlichen Variablen leicht verwechseln werden. Ein Beispiel für dieses Problem wurde während der Implementierung von verschachtelten Geltungsbereichen in der Python-Standardbibliothek gefunden.
Um dieses Problem, das unwahrscheinlich oft auftritt, zu beheben, gibt der Compiler von Python 2.1 (wenn verschachtelte Geltungsbereiche nicht aktiviert sind) eine Warnung aus.
Das andere Kompatibilitätsproblem wird durch die Verwendung von import * und „exec“ in einem Funktionskörper verursacht, wenn diese Funktion einen verschachtelten Bereich enthält und der enthaltene Bereich freie Variablen hat. Zum Beispiel:
y = 1
def f():
exec "y = 'gotcha'" # or from module import *
def g():
return y
...
Zur Kompilierzeit kann der Compiler nicht feststellen, ob ein Exec, das auf den lokalen Namensraum zugreift, oder ein import * Namensbindungen einführt, die das globale y überschatten. Daher kann nicht festgestellt werden, ob sich die Referenz auf y in g() auf das globale oder auf einen lokalen Namen in f() beziehen sollte.
In der Diskussion auf der python-list argumentierten die Leute für beide möglichen Interpretationen. Einerseits dachten einige, dass sich die Referenz in g() auf ein lokales y beziehen sollte, falls eines existiert. Ein Problem bei dieser Interpretation ist, dass es für einen menschlichen Leser des Codes unmöglich ist, die Bindung von y durch einfache Inspektion zu bestimmen. Es scheint wahrscheinlich, subtile Fehler einzuführen. Die andere Interpretation ist, exec und import * als dynamische Features zu behandeln, die die statische Geltungsbereichsregelung nicht beeinträchtigen. Nach dieser Interpretation würden exec und import * lokale Namen einführen, aber diese Namen wären niemals für verschachtelte Geltungsbereiche sichtbar. Im spezifischen Beispiel würde der Code genau so funktionieren wie in früheren Versionen von Python.
Da jede Interpretation problematisch ist und die genaue Bedeutung unklar ist, löst der Compiler eine Ausnahme aus. Der Python 2.1-Compiler gibt eine Warnung aus, wenn keine verschachtelten Geltungsbereiche aktiviert sind.
Eine kurze Überprüfung von drei Python-Projekten (der Standardbibliothek, Zope und einer Beta-Version von PyXPCOM) ergab vier Rückwärtskompatibilitätsprobleme bei etwa 200.000 Zeilen Code. Es gab ein Beispiel für Fall #1 (subtile Verhaltensänderung) und zwei Beispiele für import * Probleme in der Standardbibliothek.
(Die Interpretation der import * und der exec-Einschränkung, die in Python 2.1a2 implementiert wurde, war wesentlich restriktiver und basierte auf einer Sprache im Referenzhandbuch, die noch nie durchgesetzt worden war. Diese Einschränkungen wurden nach der Veröffentlichung gelockert.)
Kompatibilität der C API
Die Implementierung bewirkt, dass mehrere Python C API-Funktionen geändert werden, einschließlich PyCode_New(). Infolgedessen müssen C-Erweiterungen möglicherweise aktualisiert werden, um korrekt mit Python 2.1 zu funktionieren.
locals() / vars()
Diese Funktionen geben ein Wörterbuch zurück, das die lokalen Variablen des aktuellen Bereichs enthält. Änderungen am Wörterbuch wirken sich nicht auf die Werte von Variablen aus. Unter den aktuellen Regeln ermöglicht die Verwendung von locals() und globals() dem Programm den Zugriff auf alle Namensräume, in denen Namen aufgelöst werden.
Eine analoge Funktion wird für verschachtelte Geltungsbereiche nicht bereitgestellt. Nach diesem Vorschlag ist es nicht möglich, auf alle sichtbaren Bereiche über ein Wörterbuch zuzugreifen.
Warnungen und Fehler
Der Compiler gibt in Python 2.1 Warnungen aus, um Programme zu identifizieren, die möglicherweise nicht korrekt kompiliert oder ausgeführt werden können. Unter Python 2.2 oder Python 2.1, wenn die Zukunftsweisung nested_scopes verwendet wird, was in diesem Abschnitt gemeinsam als „Zukunftssemantik“ bezeichnet wird, gibt der Compiler in einigen Fällen SyntaxErrors aus.
Die Warnungen gelten typischerweise, wenn eine Funktion, die eine verschachtelte Funktion mit freien Variablen enthält. Wenn beispielsweise die Funktion F die Funktion G enthält und G die integrierte Funktion len() verwendet, dann ist F eine Funktion, die eine verschachtelte Funktion (G) mit einer freien Variable (len) enthält. Das Label „free-in-nested“ wird verwendet, um diese Funktionen zu beschreiben.
import * im Funktionsbereich verwendet
Die Sprachreferenz legt fest, dass import * nur in einem Modulbereich auftreten darf. (Abschnitt 6.11) Die Implementierung von C Python unterstützt import * im Funktionsbereich.
Wenn import * im Körper einer Funktion mit freien Variablen in verschachtelten Bereichen verwendet wird, gibt der Compiler eine Warnung aus. Unter Zukunftssemantik löst der Compiler eine SyntaxError aus.
reines exec im Funktionsbereich
Die exec-Anweisung erlaubt zwei optionale Ausdrücke nach dem Schlüsselwort „in“, die die für locals und globals zu verwendenden Namensräume angeben. Eine exec-Anweisung, die beide diese Namensräume weglässt, ist ein reines exec.
Wenn ein reines exec im Körper einer Funktion mit freien Variablen in verschachtelten Bereichen verwendet wird, gibt der Compiler eine Warnung aus. Unter Zukunftssemantik löst der Compiler eine SyntaxError aus.
lokal überschattet global
Wenn eine Funktion mit freien Variablen in verschachtelten Bereichen eine Bindung für eine lokale Variable hat, die (1) in einer verschachtelten Funktion verwendet wird und (2) mit einer globalen Variable identisch ist, gibt der Compiler eine Warnung aus.
Umbenennen von Namen in umschließenden Bereichen
Es gibt technische Probleme, die die Unterstützung für das Umbenennen von Namen in umschließenden Bereichen erschweren, aber der Hauptgrund, warum dies im aktuellen Vorschlag nicht zulässig ist, ist, dass Guido dagegen ist. Seine Motivation: Es ist schwierig zu unterstützen, da es einen neuen Mechanismus erfordern würde, der es dem Programmierer ermöglicht zu spezifizieren, dass eine Zuweisung in einem Block den Namen in einem umschließenden Block neu binden soll; vermutlich würde eine Schlüsselwort oder eine spezielle Syntax (x := 3) dies ermöglichen. Da dies die Verwendung lokaler Variablen zur Speicherung von Zuständen fördern würde, die besser in einer Klasseninstanz gespeichert sind, lohnt es sich seiner Meinung nach nicht, eine neue Syntax dafür hinzuzufügen.
Die vorgeschlagenen Regeln ermöglichen es Programmierern, den Effekt des Umbenennens zu erzielen, wenn auch umständlich. Der Name, der effektiv von umschließenden Funktionen neu gebunden wird, ist an ein Containerobjekt gebunden. Anstelle einer Zuweisung verwendet das Programm die Modifikation des Containers, um den gewünschten Effekt zu erzielen.
def bank_account(initial_balance):
balance = [initial_balance]
def deposit(amount):
balance[0] = balance[0] + amount
return balance
def withdraw(amount):
balance[0] = balance[0] - amount
return balance
return deposit, withdraw
Die Unterstützung für das Umbenennen in verschachtelten Geltungsbereichen würde diesen Code klarer machen. Eine Klasse, die deposit() und withdraw() Methoden definiert und den Kontostand als Instanzvariable, wäre noch klarer. Da Klassen den gleichen Effekt auf geradlinigere Weise zu erzielen scheinen, werden sie bevorzugt.
Implementierung
Die Implementierung für C Python verwendet flache Closures [1]. Jeder ausgeführte def- oder lambda-Ausdruck erstellt eine Closure, wenn der Körper der Funktion oder eine enthaltene Funktion freie Variablen hat. Bei Verwendung von flachen Closures ist die Erstellung von Closures etwas teuer, aber die Suche ist günstig.
Die Implementierung fügt mehrere neue Opcodes und zwei neue Arten von Namen in Code-Objekten hinzu. Eine Variable kann entweder eine Zellvariable oder eine freie Variable für ein bestimmtes Code-Objekt sein. Eine Zellvariable wird von enthaltenden Geltungsbereichen referenziert; infolgedessen muss die Funktion, in der sie definiert ist, bei jeder Ausführung separaten Speicher dafür zuweisen. Eine freie Variable wird über die Closure einer Funktion referenziert.
Die Wahl von freien Closures basierte auf drei Faktoren. Erstens wird davon ausgegangen, dass verschachtelte Funktionen selten verwendet werden, tief verschachtelte (mehrere Verschachtelungsebenen) noch seltener. Zweitens sollte die Suche nach Namen in einem verschachtelten Bereich schnell sein. Drittens sollte die Verwendung von verschachtelten Bereichen, insbesondere wenn eine Funktion, die auf einen umschließenden Bereich zugreift, zurückgegeben wird, nicht verhindern, dass unreferenzierte Objekte vom Garbage Collector wiederhergestellt werden.
Referenzen
Urheberrecht
Quelle: https://github.com/python/peps/blob/main/peps/pep-0227.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT