Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

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

Inhaltsverzeichnis

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=0 deaktiviert das Debugging. Insbesondere gibt sys.breakpointhook() mit diesem Wert sofort None zurück.
  • PYTHONBREAKPOINT= (d. h. der leere String). Dies ist dasselbe, als ob die Umgebungsvariable gar nicht gesetzt wäre. In diesem Fall wird pdb.set_trace() wie gewohnt ausgeführt.
  • PYTHONBREAKPOINT=some.importable.callable. In diesem Fall importiert sys.breakpointhook() das Modul some.importable und holt das callable-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 von PYTHONBREAKPOINT=0 in der Ausführungsumgebung erreicht werden. Ein weiterer Vorschlag von Gutachtern des PEP war, in diesem Fall PYTHONBREAKPOINT=sys.exit zu setzen.
  • IDE-Integration mit spezialisierten Debuggern für eingebettete Ausführung. Die IDE würde das Programm in ihrer Debugging-Umgebung ausführen, wobei PYTHONBREAKPOINT auf 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 None in der except-Klausel zum Pseudocode hinzufügen.
  • 2017-09-13
    • Die Umgebungsvariable PYTHONBREAKPOINT wird zu einer First-Class-Funktion.
  • 2017-09-07
    • debug() wurde in breakpoint() umbenannt.
    • Signatur geändert zu breakpoint(*args, **kws), die direkt an sys.breakpointhook() weitergegeben wird.

Referenzen


Quelle: https://github.com/python/peps/blob/main/peps/pep-0553.rst

Zuletzt geändert: 2025-02-01 08:55:40 GMT