PEP 669 – Low Impact Monitoring für CPython
- Autor:
- Mark Shannon <mark at hotpy.org>
- Discussions-To:
- Discourse thread
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 18-Aug-2021
- Python-Version:
- 3.12
- Post-History:
- 07-Dez-2021, 10-Jan-2022
- Resolution:
- Discourse-Nachricht
Inhaltsverzeichnis
- Zusammenfassung
- Motivation
- Begründung
- Spezifikation
- Abwärtskompatibilität
- Sicherheitsimplikationen
- Implementierung
- Implementierung von Tools
- Abgelehnte Ideen
- Urheberrecht
Zusammenfassung
Die Verwendung eines Profilers oder Debuggers in CPython kann die Leistung erheblich beeinträchtigen. Verlangsamungen um eine Größenordnung sind üblich.
Dieser PEP schlägt eine API für die Überwachung von Python-Programmen vor, die auf CPython laufen, und die eine kostengünstige Überwachung ermöglicht.
Obwohl dieser PEP keine Implementierung spezifiziert, wird erwartet, dass er unter Verwendung des "Quickening"-Schritts von PEP 659 implementiert wird.
Ein sys.monitoring-Namensraum wird hinzugefügt, der die relevanten Funktionen und Konstanten enthält.
Motivation
Entwickler sollten keine unangemessenen Kosten für die Verwendung von Debuggern, Profilern und ähnlichen Tools tragen müssen.
C++- und Java-Entwickler erwarten, dass sie ein Programm mit voller Geschwindigkeit (oder sehr nahe daran) unter einem Debugger ausführen können. Python-Entwickler sollten das auch erwarten.
Begründung
Der von PEP 659 bereitgestellte "Quickening"-Mechanismus ermöglicht die dynamische Modifizierung von ausgeführtem Python-Bytecode. Diese Modifikationen haben geringe Kosten über die Teile des Codes hinaus, die modifiziert werden, und relativ geringe Kosten für die modifizierten Teile. Wir können dies nutzen, um einen effizienten Mechanismus für die Überwachung bereitzustellen, der in 3.10 oder früher nicht möglich war.
Durch die Verwendung von "Quickening" erwarten wir, dass Code, der unter einem Debugger auf 3.12 läuft, Code, der ohne Debugger auf 3.11 läuft, übertrifft. Das Profiling wird die Ausführung immer noch verlangsamen, aber viel weniger als in 3.11.
Spezifikation
Die Überwachung von Python-Programmen erfolgt durch Registrierung von Callback-Funktionen für Ereignisse und durch Aktivierung einer Reihe von Ereignissen.
Das Aktivieren von Ereignissen und das Registrieren von Callback-Funktionen sind voneinander unabhängig.
Sowohl die Registrierung von Callbacks als auch die Aktivierung von Ereignissen erfolgen pro Tool. Es ist möglich, mehrere Tools zu haben, die auf verschiedene Ereignissätze reagieren.
Beachten Sie, dass im Gegensatz zu sys.settrace() Ereignisse und Callbacks pro Interpreter und nicht pro Thread gelten.
Ereignisse
Während ein Code-Objekt ausgeführt wird, treten verschiedene Ereignisse auf, die für Tools von Interesse sein könnten. Durch die Aktivierung von Ereignissen und die Registrierung von Callback-Funktionen können Tools auf diese Ereignisse nach Belieben reagieren. Ereignisse können global oder für einzelne Code-Objekte festgelegt werden.
Für 3.12 unterstützt CPython die folgenden Ereignisse
- PY_START: Beginn einer Python-Funktion (tritt unmittelbar nach dem Aufruf auf, der Frame des Aufgerufenen befindet sich auf dem Stack)
- PY_RESUME: Wiederaufnahme einer Python-Funktion (für Generator- und Coroutine-Funktionen), außer bei throw()-Aufrufen.
- PY_THROW: Eine Python-Funktion wird durch einen throw()-Aufruf wieder aufgenommen.
- PY_RETURN: Rückgabe aus einer Python-Funktion (tritt unmittelbar vor der Rückgabe auf, der Frame des Aufgerufenen befindet sich auf dem Stack).
- PY_YIELD: Yield aus einer Python-Funktion (tritt unmittelbar vor dem Yield auf, der Frame des Aufgerufenen befindet sich auf dem Stack).
- PY_UNWIND: Verlassen einer Python-Funktion während der Exception-Unwinding.
- CALL: Ein Aufruf im Python-Code (Ereignis tritt vor dem Aufruf auf).
- C_RETURN: Rückkehr von einem beliebigen aufrufbaren Objekt, außer Python-Funktionen (Ereignis tritt nach der Rückkehr auf).
- C_RAISE: Exception, die von einem beliebigen aufrufbaren Objekt außer Python-Funktionen ausgelöst wird (Ereignis tritt nach dem Verlassen auf).
- RAISE: Eine Exception wird ausgelöst, außer denen, die ein
STOP_ITERATION-Ereignis verursachen. - EXCEPTION_HANDLED: Eine Exception wird behandelt.
- LINE: Eine Anweisung steht kurz vor der Ausführung, die eine andere Zeilennummer als die vorhergehende Anweisung hat.
- INSTRUCTION – Eine VM-Anweisung steht kurz vor der Ausführung.
- JUMP – Ein unbedingter Sprung im Kontrollflussgraphen wird gemacht.
- BRANCH – Ein bedingter Sprung wird genommen (oder nicht).
- STOP_ITERATION – Eine künstliche
StopIterationwird ausgelöst; siehe das STOP_ITERATION-Ereignis.
Zukünftig können weitere Ereignisse hinzugefügt werden.
Alle Ereignisse werden Attribute des events-Namensraums in sys.monitoring sein. Alle Ereignisse werden durch eine Potenz von zwei als Ganzzahl dargestellt, sodass sie mit dem |-Operator kombiniert werden können.
Ereignisse sind in drei Gruppen unterteilt
Lokale Ereignisse
Lokale Ereignisse sind mit der normalen Ausführung des Programms verbunden und treten an klar definierten Stellen auf. Alle lokalen Ereignisse können deaktiviert werden. Die lokalen Ereignisse sind
- PY_START
- PY_RESUME
- PY_RETURN
- PY_YIELD
- CALL
- LINE
- INSTRUCTION
- JUMP
- BRANCH
- STOP_ITERATION
Ancillary-Ereignisse
Ancillary-Ereignisse können wie andere Ereignisse überwacht werden, werden aber von einem anderen Ereignis gesteuert
- C_RAISE
- C_RETURN
Die Ereignisse C_RETURN und C_RAISE werden durch das Ereignis CALL gesteuert. C_RETURN- und C_RAISE-Ereignisse werden nur gesehen, wenn das entsprechende CALL-Ereignis überwacht wird.
Andere Ereignisse
Andere Ereignisse sind nicht unbedingt an eine bestimmte Stelle im Programm gebunden und können nicht einzeln deaktiviert werden.
Die anderen überwachebaren Ereignisse sind
- PY_THROW
- PY_UNWIND
- RAISE
- EXCEPTION_HANDLED
Das STOP_ITERATION-Ereignis
PEP 380 legt fest, dass eine StopIteration-Exception ausgelöst wird, wenn ein Wert aus einem Generator oder Coroutine zurückgegeben wird. Dies ist jedoch eine sehr ineffiziente Methode, einen Wert zurückzugeben, sodass einige Python-Implementierungen, insbesondere CPython 3.12+, keine Exception auslösen, es sei denn, sie wäre für anderen Code sichtbar.
Um Tools die Überwachung echter Exceptions zu ermöglichen, ohne Generatoren und Coroutinen zu verlangsamen, wird das Ereignis STOP_ITERATION bereitgestellt. STOP_ITERATION kann lokal deaktiviert werden, im Gegensatz zu RAISE.
Tool-Identifikatoren
Die VM kann bis zu 6 Tools gleichzeitig unterstützen. Vor der Registrierung oder Aktivierung von Ereignissen sollte ein Tool einen Bezeichner wählen. Bezeichner sind Ganzzahlen im Bereich von 0 bis 5.
sys.monitoring.use_tool_id(id, name:str) -> None
sys.monitoring.free_tool_id(id) -> None
sys.monitoring.get_tool(id) -> str | None
sys.monitoring.use_tool_id löst einen ValueError aus, wenn id in Gebrauch ist. sys.monitoring.get_tool gibt den Namen des Tools zurück, wenn id in Gebrauch ist, andernfalls gibt es None zurück.
Alle IDs werden von der VM in Bezug auf Ereignisse gleich behandelt, aber die folgenden IDs sind vordefiniert, um die Zusammenarbeit von Tools zu erleichtern
sys.monitoring.DEBUGGER_ID = 0
sys.monitoring.COVERAGE_ID = 1
sys.monitoring.PROFILER_ID = 2
sys.monitoring.OPTIMIZER_ID = 5
Es besteht keine Verpflichtung, eine ID festzulegen, noch hindert etwas ein Tool daran, eine ID zu verwenden, auch wenn sie bereits verwendet wird. Tools wird jedoch empfohlen, eine eindeutige ID zu verwenden und andere Tools zu respektieren.
Wenn beispielsweise ein Debugger angehängt wäre und DEBUGGER_ID verwendet würde, sollte er einen Fehler melden, anstatt einfach fortzufahren.
Die OPTIMIZER_ID ist für Tools wie Cinder oder PyTorch vorgesehen, die Python-Code optimieren möchten, aber entscheiden müssen, was sie in einer Weise optimieren, die von einem größeren Kontext abhängt.
Globale Ereignisse festlegen
Ereignisse können global gesteuert werden, indem die Menge der überwachten Ereignisse geändert wird
sys.monitoring.get_events(tool_id:int)Gibt dieintzurück, die alle aktiven Ereignisse darstellt.sys.monitoring.set_events(tool_id:int, event_set: int)Aktiviert alle Ereignisse, die inevent_setfestgelegt sind. Löst einenValueErroraus, wenntool_idnicht in Gebrauch ist.
Standardmäßig sind keine Ereignisse aktiv.
Ereignisse pro Code-Objekt
Ereignisse können auch auf Code-Objekt-Basis gesteuert werden
sys.monitoring.get_local_events(tool_id:int, code: CodeType)->intGibt alle lokalen Ereignisse fürcodezurücksys.monitoring.set_local_events(tool_id:int, code: CodeType, event_set: int)Aktiviert alle lokalen Ereignisse fürcode, die inevent_setfestgelegt sind. Löst einenValueErroraus, wenntool_idnicht in Gebrauch ist.
Lokale Ereignisse werden zu globalen Ereignissen hinzugefügt, maskieren sie aber nicht. Mit anderen Worten, alle globalen Ereignisse werden für ein Code-Objekt ausgelöst, unabhängig von den lokalen Ereignissen.
Callback-Funktionen registrieren
Um eine aufrufbare Funktion für Ereignisse zu registrieren, rufen Sie auf
sys.monitoring.register_callback(tool_id:int, event: int, func: Callable | None) -> Callable | None
Wenn ein anderer Callback für die gegebene tool_id und das gegebene event registriert war, wird dieser abgemeldet und zurückgegeben. Andernfalls gibt register_callback None zurück.
Funktionen können abgemeldet werden, indem sys.monitoring.register_callback(tool_id, event, None) aufgerufen wird.
Callback-Funktionen können jederzeit registriert und abgemeldet werden.
Die Registrierung oder Abmeldung einer Callback-Funktion löst ein sys.audit-Ereignis aus.
Argumente von Callback-Funktionen
Wenn ein aktives Ereignis auftritt, wird die registrierte Callback-Funktion aufgerufen. Verschiedene Ereignisse übergeben der Callback-Funktion unterschiedliche Argumente, wie folgt
PY_STARTundPY_RESUMEfunc(code: CodeType, instruction_offset: int) -> DISABLE | Any
PY_RETURNundPY_YIELDfunc(code: CodeType, instruction_offset: int, retval: object) -> DISABLE | AnyCALL,C_RAISEundC_RETURNfunc(code: CodeType, instruction_offset: int, callable: object, arg0: object | MISSING) -> DISABLE | AnyWenn es keine Argumente gibt, wird
arg0aufMISSINGgesetzt.RAISEundEXCEPTION_HANDLEDfunc(code: CodeType, instruction_offset: int, exception: BaseException) -> DISABLE | AnyLINE:func(code: CodeType, line_number: int) -> DISABLE | AnyBRANCH:func(code: CodeType, instruction_offset: int, destination_offset: int) -> DISABLE | AnyBeachten Sie, dass der
destination_offsetder Ort ist, an dem der Code als Nächstes ausgeführt wird. Für einen nicht genommenen Zweig ist dies der Offset der Anweisung nach dem Sprung.INSTRUCTION:func(code: CodeType, instruction_offset: int) -> DISABLE | Any
Wenn eine Callback-Funktion DISABLE zurückgibt, wird diese Funktion für das gegebene (code, instruction_offset) nicht mehr aufgerufen, bis sys.monitoring.restart_events() aufgerufen wird. Diese Funktion ist für Coverage- und andere Tools gedacht, die ein Ereignis nur einmal sehen möchten.
Beachten Sie, dass sys.monitoring.restart_events() nicht tool-spezifisch ist, sodass Tools darauf vorbereitet sein müssen, Ereignisse zu empfangen, die sie gewählt haben, um sie zu DEAKTIVIEREN.
Ereignisse in Callback-Funktionen
Ereignisse werden in Callback-Funktionen und deren Aufgerufenen für das Tool, das den Callback registriert hat, suspendiert.
Das bedeutet, dass andere Tools Ereignisse in den Callback-Funktionen anderer Tools sehen werden. Dies könnte nützlich sein, um ein Profiling-Tool zu debuggen, würde aber irreführende Profile ergeben, da das Debugging-Tool im Profil erscheinen würde.
Reihenfolge der Ereignisse
Wenn eine Anweisung mehrere Ereignisse auslöst, treten diese in folgender Reihenfolge auf
- LINE
- INSTRUCTION
- Alle anderen Ereignisse (nur eines dieser Ereignisse kann pro Anweisung auftreten)
Jedes Ereignis wird in aufsteigender Reihenfolge der ID an die Tools geliefert.
Die Gruppe „call“-Ereignisse
Die meisten Ereignisse sind unabhängig; das Festlegen oder Deaktivieren eines Ereignisses hat keine Auswirkungen auf die anderen. Die Ereignisse CALL, C_RAISE und C_RETURN bilden jedoch eine Gruppe. Wenn eines dieser Ereignisse festgelegt oder deaktiviert wird, werden alle Ereignisse in der Gruppe ebenfalls betroffen. Das Deaktivieren eines CALL-Ereignisses deaktiviert nicht die übereinstimmenden C_RAISE- oder C_RETURN-Ereignisse, deaktiviert aber alle nachfolgenden Ereignisse.
Attribute des sys.monitoring-Namensraums
def use_tool_id(id)->Nonedef free_tool_id(id)->Nonedef get_events(tool_id: int)->intdef set_events(tool_id: int, event_set: int)->Nonedef get_local_events(tool_id: int, code: CodeType)->intdef set_local_events(tool_id: int, code: CodeType, event_set: int)->Nonedef register_callback(tool_id: int, event: int, func: Callable)->Optional[Callable]def restart_events()->NoneDISABLE: objectMISSING: object
Zugriff auf „nur Debug“-Funktionen
Einige Funktionen der Standardbibliothek sind für normalen Code nicht zugänglich, aber für Debugger zugänglich. Zum Beispiel das Setzen lokaler Variablen oder die Zeilennummer.
Diese Funktionen werden für Callback-Funktionen verfügbar sein.
Abwärtskompatibilität
Dieser PEP ist größtenteils abwärtskompatibel.
Es gibt einige Kompatibilitätsprobleme mit PEP 523, da das Verhalten von PEP 523-Plugins außerhalb der Kontrolle der VM liegt. Es liegt an PEP 523-Plugins, sicherzustellen, dass sie die Semantik dieses PEPs respektieren. Einfache Plugins, die den Zustand der VM nicht ändern und die Ausführung an _PyEval_EvalFrameDefault() delegieren, sollten weiterhin funktionieren.
sys.settrace() und sys.setprofile() verhalten sich so, als wären sie die Tools 6 bzw. 7, und können daher zusammen mit diesem PEP verwendet werden.
Das bedeutet, dass sys.settrace() und sys.setprofile() möglicherweise nicht korrekt mit allen PEP 523-Plugins funktionieren. Einfache PEP 523-Plugins, wie oben beschrieben, sollten jedoch in Ordnung sein.
Performance
Wenn keine Ereignisse aktiv sind, sollte dieser PEP einen kleinen positiven Einfluss auf die Leistung haben. Experimente zeigen eine Beschleunigung von 1 bis 2 % durch die direkte Nichtunterstützung von sys.settrace().
Die Leistung von sys.settrace() wird ungefähr gleich bleiben. Die Leistung von sys.setprofile() sollte besser sein. Tools, die auf sys.settrace() und sys.setprofile() angewiesen sind, können jedoch mit der von diesem PEP bereitgestellten API erheblich beschleunigt werden.
Wenn eine kleine Menge von Ereignissen aktiv ist, z. B. für einen Debugger, dann wird der Overhead von Callbacks um Größenordnungen geringer sein als bei sys.settrace() und viel günstiger als die Verwendung von PEP 523.
Coverage-Tools können mit sehr geringen Kosten implementiert werden, indem DISABLE in allen Callbacks zurückgegeben wird.
Für stark instrumentierten Code, z. B. unter Verwendung von LINE, wird die Leistung besser sein als bei sys.settrace, aber nicht wesentlich, da die Leistung von der Zeit dominiert wird, die in Callbacks verbracht wird.
Für optimierende virtuelle Maschinen, wie zukünftige Versionen von CPython (und PyPy, falls sie diese API unterstützen möchten), können Änderungen am Satz aktiver Ereignisse mitten in einem lang laufenden Programm recht teuer sein und möglicherweise Hunderte von Millisekunden dauern, da sie Deoptimierungen auslösen. Sobald eine solche Deoptimierung stattgefunden hat, sollte sich die Leistung erholen, da die VM den instrumentierten Code neu optimieren kann.
Im Allgemeinen können diese Operationen als schnell betrachtet werden
def get_events(tool_id: int)->intdef get_local_events(tool_id: int, code: CodeType)->intdef register_callback(tool_id: int, event: int, func: Callable)->Optional[Callable]def get_tool(tool_id) -> str | None
Diese Operationen sind langsamer, aber nicht besonders
def set_local_events(tool_id: int, code: CodeType, event_set: int)->None
Und diese Operationen sollten als langsam angesehen werden
def use_tool_id(id, name:str)->Nonedef free_tool_id(id)->Nonedef set_events(tool_id: int, event_set: int)->Nonedef restart_events()->None
Wie langsam die langsamen Operationen sind, hängt davon ab, wann sie auftreten. Wenn sie früh im Programm ausgeführt werden, bevor Module geladen sind, sollten sie relativ kostengünstig sein.
Speicherverbrauch
Wenn er nicht verwendet wird, hat dieser PEP eine vernachlässigbare Auswirkung auf den Speicherverbrauch.
Die Speichernutzung ist stark vom Implementierungsdetail abhängig. Wir gehen jedoch davon aus, dass für 3.12 der zusätzliche Speicherverbrauch pro Code-Objekt **ungefähr** wie folgt ist
| Ereignisse | |||
|---|---|---|---|
| Werkzeuge | Andere | LINE | INSTRUCTION |
| Eins | Keine | ≈40% | ≈80% |
| Zwei oder mehr | ≈40% | ≈120% | ≈200% |
Sicherheitsimplikationen
Die Ermöglichung der Modifizierung von laufendem Code birgt einige Sicherheitsimplikationen, aber nicht mehr als die Fähigkeit, neuen Code zu generieren und aufzurufen.
Alle neuen Funktionen, die oben aufgeführt sind, lösen Audit-Hooks aus.
Implementierung
Dies skizziert die vorgeschlagene Implementierung für CPython 3.12. Die tatsächliche Implementierung für spätere Versionen von CPython und andere Python-Implementierungen kann erheblich abweichen.
Die vorgeschlagene Implementierung dieses PEP wird auf dem "Quickening"-Schritt von CPython 3.11 aufgebaut, wie in PEP 659 beschrieben. Die Instrumentierung funktioniert ähnlich wie das "Quickening", Bytecodes werden bei Bedarf durch instrumentierte ersetzt.
Wenn beispielsweise das CALL-Ereignis aktiviert ist, werden alle Aufrufanweisungen durch eine INSTRUMENTED_CALL-Anweisung ersetzt.
Beachten Sie, dass dies die Spezialisierung beeinträchtigt, was zu einer gewissen Leistungseinbuße zusätzlich zum Overhead des Aufrufs der registrierten aufrufbaren Funktion führt.
Wenn sich der Satz aktiver Ereignisse ändert, aktualisiert die VM sofort alle Code-Objekte, die sich im Call-Stack eines Threads befinden. Sie richtet auch Fallstricke ein, um sicherzustellen, dass alle Code-Objekte beim Aufruf korrekt instrumentiert werden. Daher sollte die Änderung des Satzes aktiver Ereignisse so selten wie möglich erfolgen, da dies ein recht teurer Vorgang sein könnte.
Andere Ereignisse, wie z. B. RAISE, können kostengünstig ein- oder ausgeschaltet werden, da sie nicht auf Code-Instrumentierung basieren, sondern auf Laufzeitprüfungen, wenn das zugrunde liegende Ereignis auftritt.
Der genaue Satz von Ereignissen, die eine Instrumentierung erfordern, ist eine Implementierungsdetails, aber für das aktuelle Design werden die folgenden Ereignisse eine Instrumentierung erfordern
- PY_START
- PY_RESUME
- PY_RETURN
- PY_YIELD
- CALL
- LINE
- INSTRUCTION
- JUMP
- BRANCH
Jeder instrumentierte Bytecode benötigt zusätzliche 8 Bit Informationen, um mitzuteilen, für welches Tool die Instrumentierung gilt. LINE- und INSTRUCTION-Ereignisse erfordern zusätzliche Informationen, da sie die ursprüngliche Anweisung oder sogar die instrumentierte Anweisung speichern müssen, wenn sie sich mit anderer Instrumentierung überschneiden.
Implementierung von Tools
Die Philosophie dieses PEP ist, dass es für externe Überwachungstools möglich sein sollte, hohe Leistung zu erzielen, nicht, dass es für sie einfach sein sollte, dies zu tun.
Die Umwandlung von Ereignissen in für Benutzer aussagekräftige Daten ist die Verantwortung des Tools.
Alle Ereignisse haben Kosten, und Tools sollten versuchen, den Satz von Ereignissen zu verwenden, die am seltensten ausgelöst werden und dennoch die notwendigen Informationen liefern.
Debugger
Einfügen von Breakpoints
Breakpoints können durch Festlegen von Ereignissen pro Code-Objekt eingefügt werden, entweder LINE oder INSTRUCTION, und durch Zurückgeben von DISABLE für alle Ereignisse, die keinem Breakpoint entsprechen.
Schrittweise Ausführung
Debugger bieten normalerweise die Möglichkeit, die Ausführung schrittweise zu verfolgen, entweder eine Anweisung oder eine Zeile.
Ähnlich wie bei Breakpoints kann die schrittweise Ausführung durch Festlegen von Ereignissen pro Code-Objekt implementiert werden. Sobald die normale Ausführung fortgesetzt werden soll, können die lokalen Ereignisse aufgehoben werden.
Anhängen
Debugger können die Ereignisse PY_START und PY_RESUME verwenden, um informiert zu werden, wann ein Code-Objekt zum ersten Mal angetroffen wird, sodass alle notwendigen Breakpoints eingefügt werden können.
Coverage-Tools
Coverage-Tools müssen verfolgen, welche Teile des Kontrollflusses ausgeführt wurden. Dazu müssen sie sich für die PY_-Ereignisse sowie für JUMP und BRANCH registrieren.
Diese Informationen können dann nach Abschluss der Ausführung in einen zeilenbasierten Bericht umgewandelt werden.
Profiler
Einfache Profiler müssen Informationen über Aufrufe sammeln. Dazu sollten sich Profiler für die folgenden Ereignisse registrieren
- PY_START
- PY_RESUME
- PY_THROW
- PY_RETURN
- PY_YIELD
- PY_UNWIND
- CALL
- C_RAISE
- C_RETURN
Zeilenbasierte Profiler
Zeilenbasierte Profiler können die Ereignisse LINE und JUMP verwenden. Implementierer von Profilern sollten sich bewusst sein, dass die Instrumentierung von LINE-Ereignissen erhebliche Auswirkungen auf die Leistung haben wird.
Hinweis
Instrumentierende Profiler haben einen erheblichen Overhead und verzerren die Profilergebnisse. Sofern keine exakten Aufrufzahlen benötigt werden, sollten statistische Profiler verwendet werden.
Abgelehnte Ideen
Eine Entwurfsversion dieses PEP schlug vor, den Benutzer für die Einfügung der Überwachungsanweisungen verantwortlich zu machen, anstatt die VM dies tun zu lassen. Dies legt jedoch zu viel Last auf die Tools und würde das Anhängen eines Debuggers fast unmöglich machen.
Eine frühere Version dieses PEP schlug vor, Ereignisse als enums zu speichern
class Event(enum.IntFlag):
PY_START = ...
Dies würde jedoch die Überwachung von Code verhindern, bevor das enum-Modul geladen war, und könnte unnötigen Overhead verursachen.
Urheberrecht
Dieses Dokument wird in die Public Domain oder unter die CC0-1.0-Universal-Lizenz gestellt, je nachdem, welche Lizenz permissiver ist.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0669.rst
Zuletzt geändert: 2025-02-01 07:28:42 GMT