PEP 553 – Eingebaute breakpoint()
- Autor:
- Barry Warsaw <barry at python.org>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 05-Sep-2017
- Python-Version:
- 3.7
- Post-History:
- 05-Sep-2017, 07-Sep-2017, 13-Sep-2017
- Resolution:
- Python-Dev Nachricht
Zusammenfassung
Dieses PEP schlägt die Hinzufügung einer neuen eingebauten Funktion namens breakpoint() vor, die an der Aufrufstelle einen Python-Debugger aufruft. Zusätzlich werden zwei neue Namen zum Modul sys hinzugefügt, um die Wahl des aufgerufenen Debuggers konfigurierbar zu machen.
Begründung
Python verfügt seit langem über einen großartigen Debugger in seiner Standardbibliothek namens pdb. Das Setzen eines Haltepunkts wird üblicherweise so geschrieben:
foo()
import pdb; pdb.set_trace()
bar()
Nachdem also foo() ausgeführt wurde und bevor bar() ausgeführt wird, wird Python in den Debugger eintreten. Dieses Idiom hat jedoch mehrere Nachteile.
- Es muss viel getippt werden (27 Zeichen).
- Es ist leicht, Tippfehler zu machen. Der Autor des PEP vertippt sich oft bei dieser Zeile, z. B. durch Weglassen des Semikolons oder Tippen eines Punktes anstelle eines Unterstrichs.
- Es bindet das Debugging direkt an die Wahl von pdb. Es könnte andere Debugging-Optionen geben, z. B. wenn Sie eine IDE oder eine andere Entwicklungsumgebung verwenden.
- Python-Linter (z. B. flake8 [Linters]) beschweren sich über diese Zeile, da sie zwei Anweisungen enthält. Das Aufteilen des Idioms in zwei Zeilen erschwert die Verwendung, da es mehr Möglichkeiten für Fehler bei der Bereinigung gibt. D. h., Sie vergessen möglicherweise, eine dieser Zeilen zu löschen, wenn Sie den Code nicht mehr debuggen müssen.
Python-Entwickler haben auch viele andere Debugger zur Auswahl, aber die Erinnerung an deren Aufruf kann problematisch sein. Zum Beispiel haben IDEs auch dann eine Benutzeroberfläche zum Setzen von Haltepunkten, wenn es immer noch bequemer sein kann, einfach den Code zu bearbeiten. Die APIs zum programmatischen Aufrufen des Debuggers sind inkonsistent, daher kann es schwierig sein, sich genau zu merken, was zu tippen ist.
Wir können all diese Probleme lösen, indem wir eine universelle API zum Aufrufen des Debuggers bereitstellen, wie in diesem PEP vorgeschlagen.
Vorschlag
Die JavaScript-Sprache bietet eine debugger-Anweisung [js-debugger], die an der Stelle, an der die Anweisung erscheint, den Debugger aufruft.
Dieses PEP schlägt eine neue eingebaute Funktion namens breakpoint() vor, die einen Python-Debugger an der Aufrufstelle aufruft. Das obige Beispiel würde also so geschrieben:
foo()
breakpoint()
bar()
Darüber hinaus schlägt dieses PEP zwei neue Namen für das Modul sys vor, nämlich sys.breakpointhook() und sys.__breakpointhook__. Standardmäßig implementiert sys.breakpointhook() das eigentliche Importieren und Aufrufen von pdb.set_trace() und kann auf eine andere Funktion gesetzt werden, um den Debugger zu ändern, den breakpoint() aufruft.
sys.__breakpointhook__ wird mit derselben Funktion initialisiert wie sys.breakpointhook(), sodass Sie sys.breakpointhook() immer einfach auf den Standardwert zurücksetzen können (z. B. durch sys.breakpointhook = sys.__breakpointhook__). Dies ist genau dasselbe, wie die vorhandenen sys.displayhook() / sys.__displayhook__ und sys.excepthook() / sys.__excepthook__ funktionieren [Hooks].
Die Signatur der eingebauten Funktion ist breakpoint(*args, **kws). Die positionsbezogenen und Schlüsselwortargumente werden direkt an sys.breakpointhook() weitergegeben und die Signaturen müssen übereinstimmen, sonst wird ein TypeError ausgelöst. Der Rückgabewert von sys.breakpointhook() wird an breakpoint() zurückgegeben und von dort zurückgegeben.
Die Begründung dafür basiert auf der Beobachtung, dass die zugrunde liegenden Debugger möglicherweise zusätzliche optionale Argumente akzeptieren. Zum Beispiel erlaubt IPython, einen String anzugeben, der beim Aufruf des Haltepunkts ausgegeben wird [IPython-Embed]. Ab Python 3.7 unterstützt das pdb-Modul auch ein optionales header-Argument [PDB-Header].
Umgebungsvariable
Die Standardimplementierung von sys.breakpointhook() konsultiert eine neue Umgebungsvariable namens PYTHONBREAKPOINT. Diese Umgebungsvariable kann verschiedene Werte haben:
PYTHONBREAKPOINT=0deaktiviert das Debugging. Insbesondere gibtsys.breakpointhook()mit diesem Wert sofortNonezurück.PYTHONBREAKPOINT=(d. h. der leere String). Dies ist dasselbe, als ob die Umgebungsvariable gar nicht gesetzt wäre. In diesem Fall wirdpdb.set_trace()wie gewohnt ausgeführt.PYTHONBREAKPOINT=some.importable.callable. In diesem Fall importiertsys.breakpointhook()das Modulsome.importableund holt dascallable-Objekt aus dem resultierenden Modul, das es dann aufruft. Der Wert kann ein String ohne Punkte sein, in diesem Fall benennt er ein eingebautes Callable, z. B.PYTHONBREAKPOINT=int. (Guido hat die Präferenz für normale Python-Punktnotationen geäußert, nicht für Setuptools-Entry-Point-Syntax [Syntax].)
Diese Umgebungsvariable ermöglicht es externen Prozessen, die Handhabung von Haltepunkten zu steuern. Einige Anwendungsfälle umfassen:
- Vollständige Deaktivierung aller versehentlichen
breakpoint()-Aufrufe, die in die Produktion gelangen. Dies könnte durch Setzen vonPYTHONBREAKPOINT=0in der Ausführungsumgebung erreicht werden. Ein weiterer Vorschlag von Gutachtern des PEP war, in diesem FallPYTHONBREAKPOINT=sys.exitzu setzen. - IDE-Integration mit spezialisierten Debuggern für eingebettete Ausführung. Die IDE würde das Programm in ihrer Debugging-Umgebung ausführen, wobei
PYTHONBREAKPOINTauf ihren internen Debugging-Hook gesetzt wäre.
PYTHONBREAKPOINT wird jedes Mal neu interpretiert, wenn sys.breakpointhook() erreicht wird. Dies ermöglicht es Prozessen, ihren Wert während der Ausführung eines Programms zu ändern und breakpoint() auf diese Änderungen reagieren zu lassen. Es handelt sich nicht um einen leistungskritischen Abschnitt, da das Eintreten in einen Debugger per Definition die Ausführung stoppt. Daher können Programme Folgendes tun:
os.environ['PYTHONBREAKPOINT'] = 'foo.bar.baz'
breakpoint() # Imports foo.bar and calls foo.bar.baz()
Das Überschreiben von sys.breakpointhook umgeht die Standardkonsultation von PYTHONBREAKPOINT. Es liegt am überschreibenden Code, PYTHONBREAKPOINT zu konsultieren, wenn er dies wünscht.
Wenn der Zugriff auf das PYTHONBREAKPOINT-Callable fehlschlägt (z. B. der Import schlägt fehl oder das resultierende Modul enthält das Callable nicht), wird eine RuntimeWarning ausgegeben und keine Breakpoint-Funktion aufgerufen.
Beachten Sie, dass wie bei allen anderen PYTHON*-Umgebungsvariablen PYTHONBREAKPOINT ignoriert wird, wenn der Interpreter mit -E gestartet wird. Das bedeutet, das Standardverhalten tritt ein (d. h. pdb.set_trace() wird ausgeführt). Es gab einige Diskussionen darüber, PYTHONBREAKPOINT=0 alternativ zu behandeln, wenn -E aktiv ist, aber die Meinungen waren uneinheitlich, so dass entschieden wurde, dass dies nicht speziell genug für einen Sonderfall ist.
Implementierung
Eine Pull-Anfrage mit der vorgeschlagenen Implementierung existiert [Impl].
Während die tatsächliche Implementierung in C erfolgt, sieht der Python-Pseudocode für diese Funktion ungefähr wie folgt aus:
# In builtins.
def breakpoint(*args, **kws):
import sys
missing = object()
hook = getattr(sys, 'breakpointhook', missing)
if hook is missing:
raise RuntimeError('lost sys.breakpointhook')
return hook(*args, **kws)
# In sys.
def breakpointhook(*args, **kws):
import importlib, os, warnings
hookname = os.getenv('PYTHONBREAKPOINT')
if hookname is None or len(hookname) == 0:
hookname = 'pdb.set_trace'
elif hookname == '0':
return None
modname, dot, funcname = hookname.rpartition('.')
if dot == '':
modname = 'builtins'
try:
module = importlib.import_module(modname)
hook = getattr(module, funcname)
except:
warnings.warn(
'Ignoring unimportable $PYTHONBREAKPOINT: {}'.format(
hookname),
RuntimeWarning)
return None
return hook(*args, **kws)
__breakpointhook__ = breakpointhook
Abgelehnte Alternativen
Ein neues Schlüsselwort
Ursprünglich zog der Autor ein neues Schlüsselwort oder eine Erweiterung eines bestehenden Schlüsselworts wie break here in Betracht. Dies wird aus mehreren Gründen abgelehnt.
- Ein brandneues Schlüsselwort würde ein
__future__erfordern, um es zu aktivieren, da fast jedes neue Schlüsselwort mit bestehendem Code in Konflikt geraten könnte. Dies negiert die Leichtigkeit, mit der Sie in den Debugger gelangen können. - Eine erweiterte Schlüsselwortkombination wie
break here, obwohl lesbarer und ohne__future__, würde die Schlüsselwort-Erweiterung an diese neue Funktion binden und nützlichere Erweiterungen verhindern, wie z. B. die in PEP 548 vorgeschlagenen. - Ein neues Schlüsselwort würde eine modifizierte Grammatik und wahrscheinlich einen neuen Bytecode erfordern. Beides macht die Implementierung komplexer. Eine neue eingebaute Funktion bricht keinen bestehenden Code (da jedes vorhandene globale Modul die eingebaute Funktion einfach überschatten würde) und ist recht einfach zu implementieren.
sys.breakpoint()
Warum nicht sys.breakpoint()? Die Anforderung eines Imports, um den Debugger aufzurufen, wird explizit abgelehnt, da sys nicht in jedem Modul importiert wird. Das erfordert einfach mehr Tippen und würde zu
import sys; sys.breakpoint()
was mehrere der Probleme erbt, die dieses PEP zu lösen versucht.
Versionshistorie
- 2019-10-13
- Fehlendes
return Nonein derexcept-Klausel zum Pseudocode hinzufügen.
- Fehlendes
- 2017-09-13
- Die Umgebungsvariable
PYTHONBREAKPOINTwird zu einer First-Class-Funktion.
- Die Umgebungsvariable
- 2017-09-07
debug()wurde inbreakpoint()umbenannt.- Signatur geändert zu
breakpoint(*args, **kws), die direkt ansys.breakpointhook()weitergegeben wird.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0553.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT