PEP 737 – C API zur Formatierung eines vollqualifizierten Typnamens
- Autor:
- Victor Stinner <vstinner at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 29. Nov. 2023
- Python-Version:
- 3.13
- Post-History:
- 29. Nov. 2023
- Resolution:
- Discourse-Nachricht
Inhaltsverzeichnis
- Zusammenfassung
- Begründung
- Spezifikation
- Implementierung
- Abwärtskompatibilität
- Abgelehnte Ideen
- Hinzufügen des Attributs type.__fully_qualified_name__
- Hinzufügen der Methode type.__format__()
- Änderung von str(type)
- Hinzufügen des !t-Formatierers, um einen Objekttyp zu erhalten
- Hinzufügen von Formaten zu str % args
- Andere Möglichkeiten zur Formatierung von Typnamen in C
- Verwenden Sie das Format %T mit Py_TYPE(): Übergabe eines Typs
- Andere vorgeschlagene APIs zum Abrufen eines vollqualifizierten Typnamens
- Einschließen des __main__-Moduls in den vollqualifizierten Typnamen
- Diskussionen
- Urheberrecht
Zusammenfassung
Hinzufügen neuer praktischer C-APIs zur Formatierung eines vollqualifizierten Typnamens. Typnamen nicht mehr unterschiedlich formatieren, je nachdem, wie Typen implementiert sind.
Empfehlen Sie die Verwendung des vollqualifizierten Typnamens in Fehlermeldungen und in __repr__()-Methoden in neuem C-Code. Empfehlen Sie, Typnamen in neuem C-Code nicht abzuschneiden.
Fügen Sie die Formate %T, %#T, %N und %#N zu PyUnicode_FromFormat() hinzu, um die vollqualifizierten Namen eines Objekttyps bzw. eines Typs zu formatieren.
Machen Sie C-Code sicherer, indem Sie ausgeliehene Referenzen vermeiden, die zu Abstürzen führen können. Die neue C-API ist mit der begrenzten C-API kompatibel.
Begründung
Standardbibliothek
In der Python-Standardbibliothek ist die Formatierung eines Typnamens oder des Typnamens eines Objekts eine häufige Operation zur Formatierung einer Fehlermeldung und zur Implementierung einer __repr__()-Methode. Es gibt verschiedene Möglichkeiten, einen Typnamen zu formatieren, die unterschiedliche Ausgaben liefern.
Beispiel mit dem Typ datetime.timedelta
- Der kurze Typname (
type.__name__) und der qualifizierte Typname (type.__qualname__) sind'timedelta'. - Das Typmodul (
type.__module__) ist'datetime'. - Der vollqualifizierte Typname ist
'datetime.timedelta'. - Die Typdarstellung (
repr(type)) enthält den vollqualifizierten Namen:<class 'datetime.timedelta'>.
Python-Code
In Python erhält type.__name__ den kurzen Typnamen, während f"{type.__module__}.{type.__qualname__}" den „vollqualifizierten Namen“ des Typs formatiert. Normalerweise werden type(obj) oder obj.__class__ verwendet, um den Typ des Objekts obj zu erhalten. Manchmal wird der Typname in Anführungszeichen gesetzt.
Beispiele
raise TypeError("str erwartet, nicht %s" % type(value).__name__)raise TypeError("kann %s nicht serialisieren" % self.__class__.__name__)name = "%s.%s" % (obj.__module__, obj.__qualname__)
Qualifizierte Namen wurden in Python 3.3 durch PEP 3155 „Qualified name for classes and functions“ zu Typen hinzugefügt (type.__qualname__).
C-Code
In C ist die gebräuchlichste Methode zur Formatierung eines Typnamens der Zugriff auf das Mitglied PyTypeObject.tp_name des Typs. Beispiel
PyErr_Format(PyExc_TypeError, "globals must be a dict, not %.100s",
Py_TYPE(globals)->tp_name);
Der „vollqualifizierte Typname“ wird an wenigen Stellen verwendet: PyErr_Display(), die Implementierung von type.__repr__() und die Implementierung von sys.unraisablehook.
Die Verwendung von Py_TYPE(obj)->tp_name wird bevorzugt, da sie bequemer ist als der Aufruf von PyType_GetQualName(), der ein Py_DECREF() erfordert. Außerdem wurde PyType_GetQualName() erst kürzlich, in Python 3.11, hinzugefügt.
Einige Funktionen verwenden %R (repr(type)) zur Formatierung eines Typnamens, die Ausgabe enthält den vollqualifizierten Typnamen. Beispiel
PyErr_Format(PyExc_TypeError,
"calling %R should have returned an instance "
"of BaseException, not %R",
type, Py_TYPE(value));
Verwendung von PyTypeObject.tp_name ist inkonsistent mit Python
Das Mitglied PyTypeObject.tp_name unterscheidet sich je nach Typimplementierung
- Statische Typen und Heap-Typen in C: *tp_name* ist der vollqualifizierte Typname.
- Python-Klasse: *tp_name* ist der kurze Typname (
type.__name__).
Die Verwendung von Py_TYPE(obj)->tp_name zur Formatierung eines Objekttyppnamens ergibt also eine andere Ausgabe, je nachdem, ob ein Typ in C oder in Python implementiert ist.
Dies widerspricht den Prinzipien von PEP 399 „Pure Python/C Accelerator Module Compatibility Requirements“, die empfehlen, dass sich Code gleich verhält, wenn er in Python oder in C geschrieben ist.
Beispiel
$ python3.12
>>> import _datetime; c_obj = _datetime.date(1970, 1, 1)
>>> import _pydatetime; py_obj = _pydatetime.date(1970, 1, 1)
>>> my_list = list(range(3))
>>> my_list[c_obj] # C type
TypeError: list indices must be integers or slices, not datetime.date
>>> my_list[py_obj] # Python type
TypeError: list indices must be integers or slices, not date
Die Fehlermeldung enthält den vollqualifizierten Typnamen (datetime.date), wenn der Typ in C implementiert ist, oder den kurzen Typnamen (date), wenn der Typ in Python implementiert ist.
Begrenzte C-API
Der Code Py_TYPE(obj)->tp_name kann nicht mit der begrenzten C-API verwendet werden, da die Mitglieder von PyTypeObject von der begrenzten C-API ausgeschlossen sind.
Der Typname sollte mit den Funktionen PyType_GetName(), PyType_GetQualName() und PyType_GetModule() gelesen werden, die weniger bequem zu verwenden sind.
Abschneiden von Typnamen in C
1998, als die Funktion PyErr_Format() hinzugefügt wurde, verwendete die Implementierung einen festen Puffer von 500 Bytes. Die Funktion hatte folgenden Kommentar
/* Caller is responsible for limiting the format */
2001 wurde die Funktion geändert, um einen dynamischen Puffer auf dem Heap zuzuweisen. Zu spät, die Praxis des Abschneidens von Typnamen, wie die Verwendung des Formats %.100s, wurde bereits zur Gewohnheit, und Entwickler vergaßen, warum Typnamen abgeschnitten werden. In Python werden Typnamen nicht abgeschnitten.
Das Abschneiden von Typnamen in C, aber nicht in Python, widerspricht den Prinzipien von PEP 399 „Pure Python/C Accelerator Module Compatibility Requirements“, die empfehlen, dass sich Code gleich verhält, wenn er in Python oder in C geschrieben ist.
Siehe das Issue: Replace %.100s by %s in PyErr_Format(): the arbitrary limit of 500 bytes is outdated (2011).
Spezifikation
- Hinzufügen der Funktion
PyType_GetFullyQualifiedName(). - Hinzufügen der Funktion
PyType_GetModuleName(). - Hinzufügen von Formaten zu
PyUnicode_FromFormat(). - Empfehlen Sie die Verwendung des vollqualifizierten Typnamens in Fehlermeldungen und in
__repr__()-Methoden in neuem C-Code. - Empfehlen Sie, Typnamen in neuem C-Code nicht abzuschneiden.
Hinzufügen der Funktion PyType_GetFullyQualifiedName()
Fügen Sie die Funktion PyType_GetFullyQualifiedName() hinzu, um den vollqualifizierten Namen eines Typs zu erhalten: ähnlich wie f"{type.__module__}.{type.__qualname__}", oder type.__qualname__, wenn type.__module__ kein String ist oder gleich "builtins" oder "__main__" ist.
API
PyObject* PyType_GetFullyQualifiedName(PyTypeObject *type)
Bei Erfolg wird eine neue Referenz auf den String zurückgegeben. Im Fehlerfall wird eine Ausnahme ausgelöst und NULL zurückgegeben.
Hinzufügen der Funktion PyType_GetModuleName()
Fügen Sie die Funktion PyType_GetModuleName() hinzu, um den Modulnamen eines Typs (type.__module__ String) zu erhalten. API
PyObject* PyType_GetModuleName(PyTypeObject *type)
Bei Erfolg wird eine neue Referenz auf den String zurückgegeben. Im Fehlerfall wird eine Ausnahme ausgelöst und NULL zurückgegeben.
Hinzufügen von Formaten zu PyUnicode_FromFormat()
Fügen Sie die folgenden Formate zu PyUnicode_FromFormat() hinzu
%Nformatiert den **vollqualifizierten Namen** eines **Typs**, ähnlich wiePyType_GetFullyQualifiedName(type); **N** steht für den Typ**n**amen.%Tformatiert den **vollqualifizierten Typnamen** eines Objekt**t**yps, ähnlich wiePyType_GetFullyQualifiedName(Py_TYPE(obj)); **T** steht für Objek**t**yp.%#Nund%#T: Die alternative Form verwendet den Doppelpunkt-Separator (:) anstelle des Punkt-Separators (.) zwischen dem Modulnamen und dem qualifizierten Namen.
Beispielsweise kann der vorhandene Code, der *tp_name* verwendet
PyErr_Format(PyExc_TypeError,
"__format__ must return a str, not %.200s",
Py_TYPE(result)->tp_name);
mit dem %T-Format ersetzt werden
PyErr_Format(PyExc_TypeError,
"__format__ must return a str, not %T", result);
Vorteile des aktualisierten Codes
- Sicherer C-Code: Vermeiden Sie
Py_TYPE(), das eine ausgeliehene Referenz zurückgibt. - Das Mitglied
PyTypeObject.tp_namewird nicht mehr explizit gelesen: Der Code wird mit der begrenzten C-API kompatibel. - Der formatierte Typname hängt nicht mehr von der Typimplementierung ab.
- Der Typname wird nicht mehr abgeschnitten.
Hinweis: Das Format %T wird von time.strftime() verwendet, aber nicht von printf().
Zusammenfassung der Formate
| C-Objekt | C-Typ | Format |
|---|---|---|
%T |
%N |
Typ **vollqualifizierter** Name. |
%#T |
%#N |
Typ **vollqualifizierter** Name, **Doppelpunkt**-Separator. |
Empfehlen Sie die Verwendung des vollqualifizierten Typnamens
Der vollqualifizierte Typname wird in Fehlermeldungen und in __repr__()-Methoden in neuem C-Code empfohlen.
In nicht trivialen Anwendungen ist es wahrscheinlich, dass zwei Typen mit demselben kurzen Namen in zwei verschiedenen Modulen definiert sind, insbesondere bei generischen Namen. Die Verwendung des vollqualifizierten Namens hilft, den Typ eindeutig zu identifizieren.
Empfehlen Sie, Typnamen nicht abzuschneiden
Typnamen sollten in neuem C-Code nicht abgeschnitten werden. Zum Beispiel sollte das Format %.100s vermieden werden: Verwenden Sie stattdessen das Format %s (oder %T-Format in C).
Implementierung
- Pull-Request: Add type.__fully_qualified_name__ attribute.
- Pull-Request: Add %T format to PyUnicode_FromFormat().
Abwärtskompatibilität
Die in diesem PEP vorgeschlagenen Änderungen sind abwärtskompatibel.
Das Hinzufügen neuer C-APIs hat keine Auswirkungen auf die Abwärtskompatibilität. Bestehende C-APIs bleiben unverändert. Es werden keine Python-APIs geändert.
Der Ersatz des kurzen Typnamens durch den vollqualifizierten Typnamen wird nur in neuem C-Code empfohlen. Das Nicht-Abschneiden von Typnamen wird ebenfalls nur in neuem C-Code empfohlen. Bestehender Code sollte unverändert bleiben und ist somit abwärtskompatibel. Es gibt keine Empfehlung für Python-Code.
Abgelehnte Ideen
Hinzufügen des Attributs type.__fully_qualified_name__
Fügen Sie das schreibgeschützte Attribut type.__fully_qualified_name__ hinzu, den vollqualifizierten Namen eines Typs: ähnlich wie f"{type.__module__}.{type.__qualname__}", oder type.__qualname__, wenn type.__module__ kein String ist oder gleich "builtins" oder "__main__" ist.
Die type.__repr__()-Methode bleibt unverändert, sie lässt das Modul nur weg, wenn das Modul gleich "builtins" ist.
Diese Änderung wurde vom Steering Council abgelehnt
Wir erkennen die Nützlichkeit der von PEP vorgeschlagenen C-API-Änderungen und würden diese Änderungen wahrscheinlich so übernehmen.Wir sehen weniger Rechtfertigung für die Änderungen auf Python-Ebene. Insbesondere hinterfragen wir die Notwendigkeit von
__fully_qualified_name__.
Thomas Wouters fügte hinzu
Wenn es tatsächlich den Wunsch gibt, Typen exakt so zu formatieren, wie es die C-API tut, wäre eine Hilfsfunktion meiner persönlichen Meinung nach sinnvoller alstype.__format__, aber ich denke, das SC könnte mit einigen konkreten Anwendungsfällen überzeugt werden.
Hinzufügen der Methode type.__format__()
Fügen Sie die Methode type.__format__() mit den folgenden Formaten hinzu
Nformatiert den **vollqualifizierten Namen** des Typs (type.__fully_qualified_name__);Nsteht für **N**ame.#N(alternative Form) formatiert den **vollqualifizierten Namen** des Typs unter Verwendung des Doppelpunkt-Separators (:) anstelle des Punkt-Separators (.) zwischen dem Modulnamen und dem qualifizierten Namen.
Beispiele mit f-Strings
>>> import datetime
>>> f"{datetime.timedelta:N}" # fully qualified name
'datetime.timedelta'
>>> f"{datetime.timedelta:#N}" # fully qualified name, colon separator
'datetime:timedelta'
Der Doppelpunkt-Separator (:), der vom Format #N verwendet wird, eliminiert Rätselraten, wenn Sie den Namen importieren möchten, siehe pkgutil.resolve_name(), die Befehlszeilenschnittstelle python -m inspect und setuptools-Einstiegspunkte.
Diese Änderung wurde vom Steering Council abgelehnt.
Änderung von str(type)
Die Methode type.__str__() kann geändert werden, um einen Typnamen unterschiedlich zu formatieren. Zum Beispiel kann sie den vollqualifizierten Typnamen zurückgeben.
Das Problem ist, dass es sich um eine abwärts inkompatible Änderung handelt. Zum Beispiel müssten die Module enum, functools, optparse, pdb und xmlrpc.server der Standardbibliothek aktualisiert werden. Auch die Tests test_dataclasses, test_descrtut und test_cmd_line_script müssten aktualisiert werden.
Siehe den Pull-Request: type(str) returns the fully qualified name.
Hinzufügen des !t-Formatierers, um einen Objekttyp zu erhalten
Verwenden Sie f"{obj!t:T}", um type(obj).__fully_qualified_name__ zu formatieren, ähnlich wie f"{type(obj):T}".
Als der !t-Formatierer 2018 vorgeschlagen wurde, war Eric Smith stark dagegen; Eric ist der Autor des f-string PEP 498 „Literal String Interpolation“.
Hinzufügen von Formaten zu str % args
Es wurde vorgeschlagen, Formate hinzuzufügen, um einen Typnamen in str % arg zu formatieren. Zum Beispiel die Hinzufügung des Formats %T, um einen vollqualifizierten Typnamen zu formatieren.
Heutzutage werden f-Strings für neuen Code bevorzugt.
Andere Möglichkeiten zur Formatierung von Typnamen in C
Die Funktion printf() unterstützt mehrere Größenmodifikatoren: hh (char), h (short), l (long), ll (long long), z (size_t), t (ptrdiff_t) und j (intmax_t). Die Funktion PyUnicode_FromFormat() unterstützt die meisten davon.
Vorgeschlagene Formate unter Verwendung von h und hh Längenmodifikatoren
%hhTformatierttype.__name__.%hTformatierttype.__qualname__.%Tformatierttype.__fully_qualified_name__.
Längenmodifikatoren werden verwendet, um den C-Typ des Arguments anzugeben, nicht um zu ändern, wie ein Argument formatiert wird. Die alternative Form (#) ändert, wie ein Argument formatiert wird. Hier ist der C-Typ des Arguments immer PyObject*.
Andere vorgeschlagene Formate
%Q%t.%lTformatierttype.__fully_qualified_name__.%Tnformatierttype.__name__.%Tqformatierttype.__qualname__.%Tfformatierttype.__fully_qualified_name__.
Mehr Optionen zur Formatierung von Typnamen können zu Inkonsistenzen zwischen verschiedenen Modulen führen und die API fehleranfälliger machen.
Bezüglich des Formats %t verwendet printf() jetzt t als Längenmodifikator für ptrdiff_t-Argumente.
Die folgenden APIs, die zur Formatierung eines Typs verwendet werden sollen
| C API | Python API | Format |
|---|---|---|
PyType_GetName() |
type.__name__ |
Typ **kurzer** Name. |
PyType_GetQualName() |
type.__qualname__ |
Typ **qualifizierter** Name. |
PyType_GetModuleName() |
type.__module__ |
Typ **Modul**name. |
Verwenden Sie das Format %T mit Py_TYPE(): Übergabe eines Typs
Es wurde vorgeschlagen, einen Typ an das Format %T zu übergeben, wie
PyErr_Format(PyExc_TypeError, "object type name: %T", Py_TYPE(obj));
Die Funktion Py_TYPE() gibt eine ausgeliehene Referenz zurück. Nur zur Formatierung eines Fehlers scheint die Verwendung einer ausgeliehenen Referenz auf einen Typ sicher zu sein. In der Praxis kann dies zu Abstürzen führen. Beispiel
import gc
import my_cext
class ClassA:
pass
def create_object():
class ClassB:
def __repr__(self):
self.__class__ = ClassA
gc.collect()
return "ClassB repr"
return ClassB()
obj = create_object()
my_cext.func(obj)
wobei my_cext.func() eine C-Funktion ist, die
PyErr_Format(PyExc_ValueError,
"Unexpected value %R of type %T",
obj, Py_TYPE(obj));
PyErr_Format() mit einer ausgeliehenen Referenz auf ClassB aufgerufen wird. Wenn repr(obj) vom Format %R aufgerufen wird, wird die letzte Referenz auf ClassB entfernt und die Klasse freigegeben. Wenn das Format %T verarbeitet wird, ist Py_TYPE(obj) bereits ein verwaister Zeiger und Python stürzt ab.
Andere vorgeschlagene APIs zum Abrufen eines vollqualifizierten Typnamens
- Fügen Sie das Attribut
type.__fullyqualname__hinzu: Name ohne Unterstrich zwischen Wörtern. Mehrere Dunder, darunter einige der zuletzt hinzugefügten, enthalten einen Unterstrich im Wort:__class_getitem__,__release_buffer__,__type_params__,__init_subclass__und__text_signature__. - Fügen Sie das Attribut
type.__fqn__hinzu: FQN steht für **F**ully **Q**ualified **N**ame. - Fügen Sie die Methode
type.fully_qualified_name()hinzu. Methoden, die zutypehinzugefügt werden, werden von allen Typen geerbt und können daher bestehenden Code beeinflussen. - Fügen Sie eine Funktion zum
inspect-Modul hinzu. Sie müssen dasinspect-Modul importieren, um es zu verwenden.
Einschließen des __main__-Moduls in den vollqualifizierten Typnamen
Formatieren Sie type.__fully_qualified_name__ als f"{type.__module__}.{type.__qualname__}" oder type.__qualname__, wenn type.__module__ kein String ist oder gleich "builtins" ist. Behandeln Sie das Modul __main__ nicht anders: Schließen Sie es in den Namen ein.
Bestehender Code wie type.__repr__(), die Module collections.abc und unittest formatieren einen Typnamen mit f'{obj.__module__}.{obj.__qualname__}' und lassen den Modulteil nur weg, wenn das Modul gleich builtins ist.
Nur die Module traceback und pdb lassen das Modul auch weg, wenn es gleich "builtins" oder "__main__" ist.
Das Attribut type.__fully_qualified_name__ lässt das Modul __main__ weg, um kürzere Namen für einen häufigen Fall zu erzeugen: Typen, die in einem Skript definiert sind, das mit python script.py ausgeführt wird. Zum Debuggen kann die Funktion repr() für einen Typ verwendet werden, sie schließt das Modul __main__ in den Typnamen ein. Oder verwenden Sie das Format f"{type.__module__}.{type.__qualname__}", um den Modulnamen immer einzuschließen, auch für das Modul "builtins".
Beispiel-Skript
class MyType:
pass
print(f"name: {MyType.__fully_qualified_name__}")
print(f"repr: {repr(MyType)}")
Ausgabe
name: MyType
repr: <class '__main__.MyType'>
Diskussionen
- Diskussion: PEP 737 – Unify type name formatting (2023).
- Diskussion: Enhance type name formatting when raising an exception: add %T format in C, and add type.__fullyqualname__ (2023).
- Issue: PyUnicode_FromFormat(): Add %T format to format the type name of an object (2023).
- Issue: C API: Investigate how the PyTypeObject members can be removed from the public C API (2023).
- python-dev Thread: bpo-34595: How to format a type name? (2018).
- Issue: PyUnicode_FromFormat(): add %T format for an object type name (2018).
- Issue: Replace %.100s by %s in PyErr_Format(): the arbitrary limit of 500 bytes is outdated (2011).
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-0737.rst
Zuletzt geändert: 2024-06-01 20:53:34 GMT