annotationlib — Funktionalität zur Introspektion von Annotationen¶
Hinzugefügt in Version 3.14.
Quellcode: Lib/annotationlib.py
Das Modul annotationlib stellt Werkzeuge zur Introspektion von Annotationen auf Modulen, Klassen und Funktionen bereit.
Annotationen werden verzögert ausgewertet und enthalten oft Vorwärtsreferenzen auf Objekte, die bei der Erstellung der Annotation noch nicht definiert sind. Dieses Modul stellt eine Reihe von Low-Level-Werkzeugen bereit, die verwendet werden können, um Annotationen auf zuverlässige Weise abzurufen, selbst im Beisein von Vorwärtsreferenzen und anderen Randfällen.
Dieses Modul unterstützt das Abrufen von Annotationen in drei Hauptformaten (siehe Format), von denen jedes für unterschiedliche Anwendungsfälle am besten geeignet ist
VALUEwertet die Annotationen aus und gibt deren Wert zurück. Dies ist am einfachsten zu handhaben, kann aber Fehler verursachen, z. B. wenn die Annotationen Verweise auf undefinierte Namen enthalten.FORWARDREFgibtForwardRef-Objekte für Annotationen zurück, die nicht aufgelöst werden können, was es Ihnen ermöglicht, die Annotationen zu inspizieren, ohne sie auszuwerten. Dies ist nützlich, wenn Sie mit Annotationen arbeiten müssen, die nicht aufgelöste Vorwärtsreferenzen enthalten können.STRINGgibt die Annotationen als Zeichenkette zurück, ähnlich wie sie in der Quelldatei erscheinen würden. Dies ist nützlich für Dokumentationsgeneratoren, die Annotationen auf lesbare Weise anzeigen möchten.
Die Funktion get_annotations() ist der Haupteinstiegspunkt für das Abrufen von Annotationen. Gegeben eine Funktion, Klasse oder ein Modul, gibt sie ein Annotations-Dictionary im angeforderten Format zurück. Dieses Modul stellt auch Funktionalität für die direkte Arbeit mit der annotate function bereit, die zur Auswertung von Annotationen verwendet wird, wie z. B. get_annotate_from_class_namespace() und call_annotate_function(), sowie die Funktion call_evaluate_function() für die Arbeit mit evaluate functions.
Vorsicht
Die meisten Funktionen in diesem Modul können beliebigen Code ausführen. Weitere Informationen finden Sie im Abschnitt Sicherheit.
Siehe auch
PEP 649 schlug das aktuelle Modell für die Funktionsweise von Annotationen in Python vor.
PEP 749 erweiterte verschiedene Aspekte von PEP 649 und führte das Modul annotationlib ein.
Annotations Best Practices bietet Best Practices für die Arbeit mit Annotationen.
typing-extensions bietet einen Backport von get_annotations(), der auf früheren Python-Versionen funktioniert.
Annotation Semantics¶
Die Art und Weise, wie Annotationen ausgewertet werden, hat sich im Laufe der Geschichte von Python 3 geändert und hängt derzeit noch von einem future import ab. Es gab Ausführungsmodelle für Annotationen
Stock Semantics (Standard in Python 3.0 bis 3.13; siehe PEP 3107 und PEP 526): Annotationen werden eifrig ausgewertet, sobald sie im Quellcode angetroffen werden.
Stringified Annotations (verwendet mit
from __future__ import annotationsin Python 3.7 und neuer; siehe PEP 563): Annotationen werden nur als Strings gespeichert.Deferred Evaluation (Standard in Python 3.14 und neuer; siehe PEP 649 und PEP 749): Annotationen werden verzögert ausgewertet, erst wenn sie aufgerufen werden.
Als Beispiel betrachten Sie das folgende Programm
def func(a: Cls) -> None:
print(a)
class Cls: pass
print(func.__annotations__)
Dies wird wie folgt verarbeitet
Unter Stock Semantics (Python 3.13 und früher) wird ein
NameErrorin der Zeile ausgelöst, in derfuncdefiniert ist, daClszu diesem Zeitpunkt ein undefinierter Name ist.Unter stringifizierten Annotationen (wenn
from __future__ import annotationsverwendet wird) wird{'a': 'Cls', 'return': 'None'}ausgegeben.Unter verzögerter Auswertung (Python 3.14 und später) wird
{'a': <class 'Cls'>, 'return': None}ausgegeben.
Stock Semantics wurden verwendet, als Funktionsannotationen erstmals in Python 3.0 (durch PEP 3107) eingeführt wurden, da dies der einfachste und offensichtlichste Weg zur Implementierung von Annotationen war. Das gleiche Ausführungsmodell wurde verwendet, als Variablennamenannotationen in Python 3.6 (durch PEP 526) eingeführt wurden. Stock Semantics verursachten jedoch Probleme bei der Verwendung von Annotationen als Typ-Hints, wie z. B. die Notwendigkeit, auf Namen zu verweisen, die noch nicht definiert waren, als die Annotation angetroffen wurde. Zusätzlich gab es Leistungsprobleme bei der Auswertung von Annotationen zur Modulimportzeit. Daher wurde in Python 3.7 PEP 563 die Möglichkeit eingeführt, Annotationen mithilfe der Syntax from __future__ import annotations als Strings zu speichern. Der Plan war damals, dieses Verhalten schließlich zum Standard zu machen, aber ein Problem trat auf: stringifizierte Annotationen sind schwieriger zu verarbeiten für diejenigen, die Annotationen zur Laufzeit introspektieren. Ein alternativer Vorschlag, PEP 649, führte das dritte Ausführungsmodell, die verzögerte Auswertung, ein und wurde in Python 3.14 implementiert. Stringifizierte Annotationen werden weiterhin verwendet, wenn from __future__ import annotations vorhanden ist, aber dieses Verhalten wird schließlich entfernt werden.
Klassen¶
- class annotationlib.Format¶
Ein
IntEnum, das die Formate beschreibt, in denen Annotationen zurückgegeben werden können. Mitglieder der Enum oder ihre entsprechenden ganzzahligen Werte können anget_annotations()und andere Funktionen in diesem Modul sowie an__annotate__-Funktionen übergeben werden.- VALUE = 1¶
Werte sind das Ergebnis der Auswertung der Annotationsausdrücke.
- VALUE_WITH_FAKE_GLOBALS = 2¶
Spezieller Wert, der verwendet wird, um zu signalisieren, dass eine Annotate-Funktion in einer speziellen Umgebung mit gefälschten Globals ausgewertet wird. Wenn dieser Wert übergeben wird, sollten Annotate-Funktionen entweder denselben Wert wie für das Format
Format.VALUEzurückgeben oderNotImplementedErrorauslösen, um zu signalisieren, dass sie die Ausführung in dieser Umgebung nicht unterstützen. Dieses Format wird nur intern verwendet und sollte nicht an die Funktionen in diesem Modul übergeben werden.
- FORWARDREF = 3¶
Werte sind reale Annotationswerte (gemäß dem Format
Format.VALUE) für definierte Werte undForwardRef-Proxys für undefinierte Werte. Reale Objekte können Verweise aufForwardRef-Proxy-Objekte enthalten.
- STRING = 4¶
Werte sind der Text der Annotation, wie er im Quellcode erscheint, bis hin zu Modifikationen, die Whitespace-Normalisierungen und Optimierungen von konstanten Werten einschließen, aber nicht darauf beschränkt sind.
Die exakten Werte dieser Strings können sich in zukünftigen Versionen von Python ändern.
Hinzugefügt in Version 3.14.
- class annotationlib.ForwardRef¶
Ein Proxy-Objekt für Vorwärtsreferenzen in Annotationen.
Instanzen dieser Klasse werden zurückgegeben, wenn das Format
FORWARDREFverwendet wird und Annotationen einen Namen enthalten, der nicht aufgelöst werden kann. Dies kann passieren, wenn eine Vorwärtsreferenz in einer Annotation verwendet wird, z. B. wenn auf eine Klasse verwiesen wird, bevor sie definiert ist.- __forward_arg__¶
Eine Zeichenkette, die den Code enthält, der ausgewertet wurde, um die
ForwardRefzu erzeugen. Die Zeichenkette ist möglicherweise nicht exakt äquivalent zum ursprünglichen Quelltext.
- evaluate(*, owner=None, globals=None, locals=None, type_params=None, format=Format.VALUE)¶
Wertet die Vorwärtsreferenz aus und gibt ihren Wert zurück.
Wenn das Argument format
VALUE(Standard) ist, kann diese Methode eine Ausnahme auslösen, wie z. B.NameError, wenn die Vorwärtsreferenz auf einen Namen verweist, der nicht aufgelöst werden kann. Die Argumente für diese Methode können verwendet werden, um Bindungen für Namen bereitzustellen, die andernfalls undefiniert wären. Wenn das Argument formatFORWARDREFist, löst die Methode nie eine Ausnahme aus, kann aber eineForwardRef-Instanz zurückgeben. Wenn die Vorwärtsreferenzobjekt beispielsweise den Codelist[undefined]enthält, wobeiundefinedein nicht definierter Name ist, wird die Auswertung mit dem FormatFORWARDREFzulist[ForwardRef('undefined')]führen. Wenn das Argument formatSTRINGist, gibt die Methode__forward_arg__zurück.Das Argument owner bietet den bevorzugten Mechanismus zur Übergabe von Bereichsinformationen an diese Methode. Der Besitzer einer
ForwardRefist das Objekt, das die Anmerkung enthält, von der dieForwardRefabgeleitet ist, z. B. ein Modulobjekt, ein Typobjekt oder ein Funktions-Objekt.Die Argumente globals, locals und type_params bieten einen präziseren Mechanismus zur Steuerung der Namen, die verfügbar sind, wenn die
ForwardRefausgewertet wird. globals und locals werden aneval()übergeben und stellen die globalen und lokalen Namensräume dar, in denen der Name ausgewertet wird. Das Argument type_params ist relevant für Objekte, die mit der nativen Syntax für generische Klassen und Funktionen erstellt wurden. Es ist ein Tupel von Typ-Parametern, die während der Auswertung der Vorwärtsreferenz im Geltungsbereich liegen. Wenn beispielsweise eineForwardRefausgewertet wird, die aus einer Annotation im Klassen-Namensraum einer generischen KlasseCstammt, sollte type_params aufC.__type_params__gesetzt werden.ForwardRef-Instanzen, die vonget_annotations()zurückgegeben werden, behalten Verweise auf Informationen über den Bereich, aus dem sie stammen, bei. Das Aufrufen dieser Methode ohne weitere Argumente kann daher ausreichen, um solche Objekte auszuwerten.ForwardRef-Instanzen, die auf andere Weise erstellt wurden, haben möglicherweise keine Informationen über ihren Bereich, sodass die Übergabe von Argumenten an diese Methode erforderlich sein kann, um sie erfolgreich auszuwerten.Wenn keine owner, globals, locals oder type_params angegeben sind und die
ForwardRefkeine Informationen über ihren Ursprung enthält, werden leere Globals- und Locals-Dictionaries verwendet.
Hinzugefügt in Version 3.14.
Funktionen¶
- annotationlib.annotations_to_string(annotations)¶
Konvertiert ein Annotations-Dictionary, das Laufzeitwerte enthält, in ein Dictionary, das nur Strings enthält. Wenn die Werte keine Strings sind, werden sie mithilfe von
type_repr()konvertiert. Dies dient als Hilfsmittel für benutzerdefinierte Annotate-Funktionen, die das FormatSTRINGunterstützen, aber keinen Zugriff auf den Code haben, der die Annotationen erstellt.Dies wird beispielsweise zur Implementierung von
STRINGfürtyping.TypedDict-Klassen verwendet, die über die funktionale Syntax erstellt wurden>>> from typing import TypedDict >>> Movie = TypedDict("movie", {"name": str, "year": int}) >>> get_annotations(Movie, format=Format.STRING) {'name': 'str', 'year': 'int'}
Hinzugefügt in Version 3.14.
- annotationlib.call_annotate_function(annotate, format, *, owner=None)¶
Ruft die annotate function annotate mit dem angegebenen format, einem Mitglied der
Format-Enum, auf und gibt das von der Funktion erzeugte Annotations-Dictionary zurück.Diese Hilfsfunktion ist erforderlich, da von den Compilern für Funktionen, Klassen und Module generierte Annotate-Funktionen beim direkten Aufruf nur das Format
VALUEunterstützen. Um andere Formate zu unterstützen, ruft diese Funktion die Annotate-Funktion in einer speziellen Umgebung auf, die es ihr ermöglicht, Annotationen in anderen Formaten zu erzeugen. Dies ist ein nützlicher Baustein bei der Implementierung von Funktionalität, die Annotationen teilweise auswerten muss, während eine Klasse konstruiert wird.owner ist das Objekt, dem die Annotate-Funktion gehört, normalerweise eine Funktion, Klasse oder ein Modul. Wenn angegeben, wird es im Format
FORWARDREFverwendet, um einForwardRef-Objekt zu erzeugen, das mehr Informationen trägt.Siehe auch
PEP 649 enthält eine Erklärung der von dieser Funktion verwendeten Implementierungstechnik.
Hinzugefügt in Version 3.14.
- annotationlib.call_evaluate_function(evaluate, format, *, owner=None)¶
Ruft die evaluate function evaluate mit dem angegebenen format, einem Mitglied der
Format-Enum, auf und gibt den von der Funktion erzeugten Wert zurück. Dies ähneltcall_annotate_function(), aber letztere gibt immer ein Dictionary zurück, das Strings auf Annotationen abbildet, während diese Funktion einen einzelnen Wert zurückgibt.Dies ist für die Verwendung mit den von den Compilern generierten Auswertungsfunktionen für verzögert ausgewertete Elemente im Zusammenhang mit Typ-Aliassen und Typ-Parametern vorgesehen.
typing.TypeAliasType.evaluate_value(), der Wert von Typ-Aliassentyping.TypeVar.evaluate_bound(), die Bindung von Tyvariablentyping.TypeVar.evaluate_constraints(), die Einschränkungen von Tyvariablentyping.TypeVar.evaluate_default(), der Standardwert von Tyvariablentyping.ParamSpec.evaluate_default(), der Standardwert von Parameter-Spezifikationentyping.TypeVarTuple.evaluate_default(), der Standardwert von Typ-Variablen-Tupeln
owner ist das Objekt, dem die Auswertungsfunktion gehört, wie z. B. das Typ-Alias- oder Tyvariablen-Objekt.
format kann verwendet werden, um das Format zu steuern, in dem der Wert zurückgegeben wird
>>> type Alias = undefined >>> call_evaluate_function(Alias.evaluate_value, Format.VALUE) Traceback (most recent call last): ... NameError: name 'undefined' is not defined >>> call_evaluate_function(Alias.evaluate_value, Format.FORWARDREF) ForwardRef('undefined') >>> call_evaluate_function(Alias.evaluate_value, Format.STRING) 'undefined'
Hinzugefügt in Version 3.14.
- annotationlib.get_annotate_from_class_namespace(namespace)¶
Ruft die annotate function aus einem Klassen-Namensraum-Dictionary namespace ab. Gibt
Nonezurück, wenn der Namensraum keine Annotate-Funktion enthält. Dies ist hauptsächlich vor der vollständigen Erstellung der Klasse (z. B. in einer Metaklasse) nützlich; nachdem die Klasse existiert, kann die Annotate-Funktion mitcls.__annotate__abgerufen werden. Siehe unten für ein Beispiel, das diese Funktion in einer Metaklasse verwendet.Hinzugefügt in Version 3.14.
- annotationlib.get_annotations(obj, *, globals=None, locals=None, eval_str=False, format=Format.VALUE)¶
Berechnet das Annotations-Dictionary für ein Objekt.
obj kann ein aufrufbares Objekt, eine Klasse, ein Modul oder ein anderes Objekt mit den Attributen
__annotate__oder__annotations__sein. Das Übergeben eines anderen Objekts löst einenTypeErroraus.Der Parameter format steuert das Format, in dem Annotationen zurückgegeben werden, und muss ein Mitglied der
Format-Enum oder dessen ganzzahlige Entsprechung sein. Die verschiedenen Formate funktionieren wie folgt:VALUE: Zuerst wird versucht,
object.__annotations__abzurufen; wenn dies nicht existiert, wird die Funktionobject.__annotate__aufgerufen, falls sie existiert.FORWARDREF: Wenn
object.__annotations__existiert und erfolgreich ausgewertet werden kann, wird es verwendet; andernfalls wird die Funktionobject.__annotate__aufgerufen. Wenn auch diese nicht existiert, wird erneut versucht,object.__annotations__abzurufen, und jeder Fehler beim Zugriff darauf wird erneut ausgelöst.STRING: Wenn
object.__annotate__existiert, wird es zuerst aufgerufen; andernfalls wirdobject.__annotations__verwendet und mitannotations_to_string()in eine Zeichenkette umgewandelt.
Gibt ein Wörterbuch zurück.
get_annotations()gibt bei jedem Aufruf ein neues Wörterbuch zurück; ein zweifacher Aufruf auf dasselbe Objekt liefert zwei verschiedene, aber äquivalente Wörterbücher.Diese Funktion kümmert sich um mehrere Details für Sie:
Wenn eval_str wahr ist, werden Werte vom Typ
strmiteval()aus ihrer Zeichenkettenform gelöst. Dies ist für die Verwendung mit Zeichenkettenannotationen gedacht (from __future__ import annotations). Es ist ein Fehler, eval_str auf wahr zu setzen, wenn andere Formate alsFormat.VALUEverwendet werden.Wenn obj keinen Annotations-Wörterbuch hat, wird ein leeres Wörterbuch zurückgegeben. (Funktionen und Methoden haben immer ein Annotations-Wörterbuch; Klassen, Module und andere Arten von aufrufbaren Objekten möglicherweise nicht.)
Ignoriert geerbte Annotationen von Klassen sowie Annotationen von Metaklassen. Wenn eine Klasse kein eigenes Annotations-Wörterbuch hat, wird ein leeres Wörterbuch zurückgegeben.
Alle Zugriffe auf Objektmember und Wörterbuchwerte erfolgen zur Sicherheit über
getattr()unddict.get().
eval_str steuert, ob Werte vom Typ
strdurch das Ergebnis des Aufrufs voneval()auf diese Werte ersetzt werden oder nicht.Wenn eval_str wahr ist, wird
eval()auf Werte vom Typstraufgerufen. (Beachten Sie, dassget_annotations()keine Ausnahmen abfängt; wenneval()eine Ausnahme auslöst, wird der Stack-Aufruf über denget_annotations()-Aufruf hinaus zurückgesetzt.)Wenn eval_str falsch (der Standardwert) ist, bleiben Werte vom Typ
strunverändert.
globals und locals werden an
eval()übergeben; siehe die Dokumentation füreval()für weitere Informationen. Wenn globals oder localsNoneist, kann diese Funktion diesen Wert durch einen kontextspezifischen Standardwert ersetzen, abhängig vontype(obj).Wenn obj ein Modul ist, ist globals standardmäßig
obj.__dict__.Wenn obj eine Klasse ist, ist globals standardmäßig
sys.modules[obj.__module__].__dict__und locals ist standardmäßig der Namensraum der Klasse obj.Wenn obj ein aufrufbares Objekt ist, ist globals standardmäßig
obj.__globals__. Wenn obj jedoch eine dekorierte Funktion (unter Verwendung vonfunctools.update_wrapper()) oder einfunctools.partial-Objekt ist, wird es entpackt, bis eine nicht dekorierte Funktion gefunden wird.
Der Aufruf von
get_annotations()ist die empfohlene Methode zum Zugriff auf das Annotations-Wörterbuch eines beliebigen Objekts. Weitere Informationen zu Best Practices für Annotationen finden Sie unter Best Practices für Annotationen.>>> def f(a: int, b: str) -> float: ... pass >>> get_annotations(f) {'a': <class 'int'>, 'b': <class 'str'>, 'return': <class 'float'>}
Hinzugefügt in Version 3.14.
- annotationlib.type_repr(value)¶
Konvertiert einen beliebigen Python-Wert in ein Format, das für die Verwendung durch das
STRING-Format geeignet ist. Dies ruftrepr()für die meisten Objekte auf, hat aber spezielle Handhabung für einige Objekte, wie z. B. Typobjekte.Dies ist als Hilfsmittel für benutzerdefinierte Annotationsfunktionen gedacht, die das
STRING-Format unterstützen, aber keinen Zugriff auf den Code haben, der die Annotationen erstellt. Es kann auch verwendet werden, um eine benutzerfreundliche Zeichenkettendarstellung für andere Objekte bereitzustellen, die Werte enthalten, die häufig in Annotationen vorkommen.Hinzugefügt in Version 3.14.
Rezepte¶
Verwendung von Annotationen in einer Metaklasse¶
Eine Metaklasse möchte möglicherweise die Annotationen im Klassenrumpf während der Klassenerstellung untersuchen oder sogar modifizieren. Dies erfordert den Abruf von Annotationen aus dem Wörterbuch des Klassen-Namensraums. Für Klassen, die mit from __future__ import annotations erstellt wurden, befinden sich die Annotationen unter dem Schlüssel __annotations__ des Wörterbuchs. Für andere Klassen mit Annotationen kann get_annotate_from_class_namespace() verwendet werden, um die Annotationsfunktion zu erhalten, und call_annotate_function() kann verwendet werden, um sie aufzurufen und die Annotationen abzurufen. Die Verwendung des FORWARDREF-Formats ist normalerweise am besten, da dies ermöglicht, dass sich Annotationen auf Namen beziehen, die zum Zeitpunkt der Klassenerstellung noch nicht aufgelöst werden können.
Um die Annotationen zu ändern, ist es am besten, eine Wrapper-Annotationsfunktion zu erstellen, die die ursprüngliche Annotationsfunktion aufruft, notwendige Anpassungen vornimmt und das Ergebnis zurückgibt.
Unten finden Sie ein Beispiel für eine Metaklasse, die alle typing.ClassVar-Annotationen aus der Klasse herausfiltert und sie in einem separaten Attribut speichert.
import annotationlib
import typing
class ClassVarSeparator(type):
def __new__(mcls, name, bases, ns):
if "__annotations__" in ns: # from __future__ import annotations
annotations = ns["__annotations__"]
classvar_keys = {
key for key, value in annotations.items()
# Use string comparison for simplicity; a more robust solution
# could use annotationlib.ForwardRef.evaluate
if value.startswith("ClassVar")
}
classvars = {key: annotations[key] for key in classvar_keys}
ns["__annotations__"] = {
key: value for key, value in annotations.items()
if key not in classvar_keys
}
wrapped_annotate = None
elif annotate := annotationlib.get_annotate_from_class_namespace(ns):
annotations = annotationlib.call_annotate_function(
annotate, format=annotationlib.Format.FORWARDREF
)
classvar_keys = {
key for key, value in annotations.items()
if typing.get_origin(value) is typing.ClassVar
}
classvars = {key: annotations[key] for key in classvar_keys}
def wrapped_annotate(format):
annos = annotationlib.call_annotate_function(annotate, format, owner=typ)
return {key: value for key, value in annos.items() if key not in classvar_keys}
else: # no annotations
classvars = {}
wrapped_annotate = None
typ = super().__new__(mcls, name, bases, ns)
if wrapped_annotate is not None:
# Wrap the original __annotate__ with a wrapper that removes ClassVars
typ.__annotate__ = wrapped_annotate
typ.classvars = classvars # Store the ClassVars in a separate attribute
return typ
Einschränkungen des STRING-Formats¶
Das STRING-Format zielt darauf ab, den Quellcode der Annotation zu approximieren, aber die verwendete Implementierungsstrategie bedeutet, dass es nicht immer möglich ist, den exakten Quellcode wiederherzustellen.
Erstens kann der Stringifier natürlich keine Informationen wiederherstellen, die nicht im kompilierten Code vorhanden sind, einschließlich Kommentare, Leerzeichen, Klammerung und Operationen, die vom Compiler vereinfacht werden.
Zweitens kann der Stringifier fast alle Operationen abfangen, die sich auf Namen beziehen, die in einem bestimmten Geltungsbereich nachgeschlagen werden, aber er kann keine Operationen abfangen, die vollständig auf Konstanten operieren. Daraus folgt, dass es auch nicht sicher ist, das STRING-Format für nicht vertrauenswürdigen Code anzufordern: Python ist mächtig genug, um willkürliche Codeausführung zu erreichen, selbst ohne Zugriff auf Globals oder Builtins. Zum Beispiel:
>>> def f(x: (1).__class__.__base__.__subclasses__()[-1].__init__.__builtins__["print"]("Hello world")): pass
...
>>> annotationlib.get_annotations(f, format=annotationlib.Format.STRING)
Hello world
{'x': 'None'}
Hinweis
Dieses spezielle Beispiel funktioniert zum Zeitpunkt des Schreibens, aber es basiert auf Implementierungsdetails und funktioniert möglicherweise in Zukunft nicht mehr.
Von den verschiedenen Arten von Ausdrücken in Python, wie sie vom ast-Modul dargestellt werden, werden einige Ausdrücke unterstützt, was bedeutet, dass das STRING-Format im Allgemeinen den ursprünglichen Quellcode wiederherstellen kann; andere werden nicht unterstützt, was bedeutet, dass sie zu falschen Ausgaben oder Fehlern führen können.
Die folgenden werden unterstützt (manchmal mit Einschränkungen):
-
ast.Invert(~),ast.UAdd(+) undast.USub(-) werden unterstützt.ast.Not(not) wird nicht unterstützt.
ast.Dict(außer bei Verwendung von**-Entpackung)ast.Call(außer bei Verwendung von**-Entpackung)ast.Constant(jedoch nicht die exakte Darstellung der Konstante; z.B. gehen Escape-Sequenzen in Zeichenketten verloren; hexadezimale Zahlen werden in Dezimalzahlen umgewandelt)ast.Attribute(vorausgesetzt, der Wert ist keine Konstante)ast.Subscript(vorausgesetzt, der Wert ist keine Konstante)ast.Starred(*-Entpackung)
Die folgenden werden nicht unterstützt, lösen aber beim Auftreten durch den Stringifier eine informative Fehlermeldung aus:
ast.FormattedValue(f-strings; Fehler wird nicht erkannt, wenn Konvertierungsspezifizierer wie!rverwendet werden)ast.JoinedStr(f-strings)
Die folgenden werden nicht unterstützt und führen zu fehlerhaften Ausgaben:
Die folgenden sind in Annotations-Geltungsbereichen nicht zulässig und daher nicht relevant:
Einschränkungen des FORWARDREF-Formats¶
Das FORWARDREF-Format zielt darauf ab, so weit wie möglich echte Werte zu erzeugen, wobei alles, was nicht aufgelöst werden kann, durch ForwardRef-Objekte ersetzt wird. Es unterliegt im Wesentlichen denselben Einschränkungen wie das STRING-Format: Annotationen, die Operationen auf Literalen durchführen oder nicht unterstützte Ausdruckstypen verwenden, können Ausnahmen auslösen, wenn sie mit dem FORWARDREF-Format ausgewertet werden.
Unten sind einige Beispiele für das Verhalten mit nicht unterstützten Ausdrücken aufgeführt.
>>> from annotationlib import get_annotations, Format
>>> def zerodiv(x: 1 / 0): ...
>>> get_annotations(zerodiv, format=Format.STRING)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
>>> get_annotations(zerodiv, format=Format.FORWARDREF)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
>>> def ifexp(x: 1 if y else 0): ...
>>> get_annotations(ifexp, format=Format.STRING)
{'x': '1'}
Sicherheitsimplikationen der Introspektion von Annotationen¶
Ein Großteil der Funktionalität in diesem Modul beinhaltet die Ausführung von Code im Zusammenhang mit Annotationen, der dann beliebige Dinge tun kann. Zum Beispiel kann get_annotations() eine beliebige Annotationsfunktion aufrufen, und ForwardRef.evaluate() kann eval() auf einer beliebigen Zeichenkette aufrufen. Code, der in einer Annotation enthalten ist, kann willkürliche Systemaufrufe tätigen, eine Endlosschleife betreten oder eine beliebige andere Operation durchführen. Dies gilt auch für jeden Zugriff auf das Attribut __annotations__ und für verschiedene Funktionen im typing-Modul, die mit Annotationen arbeiten, wie z. B. typing.get_type_hints().
Jedes Sicherheitsproblem, das sich daraus ergibt, gilt auch unmittelbar nach dem Importieren von Code, der nicht vertrauenswürdige Annotationen enthalten kann: Das Importieren von Code kann immer beliebige Operationen auslösen. Es ist jedoch unsicher, Zeichenketten oder andere Eingaben von einer nicht vertrauenswürdigen Quelle zu akzeptieren und sie an eine der APIs zur Introspektion von Annotationen zu übergeben, z. B. durch Bearbeiten eines __annotations__-Wörterbuchs oder durch direktes Erstellen eines ForwardRef-Objekts.