annotationlib
— Funktionalitet för introspektion av annoteringar¶
Källkod: Lib/annotationlib.py
Modulen annotationlib
tillhandahåller verktyg för introspektion av annotationer på moduler, klasser och funktioner.
Annotationer är långsamt utvärderade och innehåller ofta framåtriktade referenser till objekt som ännu inte är definierade när annotationen skapas. Den här modulen innehåller en uppsättning lågnivåverktyg som kan användas för att hämta annoteringar på ett tillförlitligt sätt, även om det finns framåtriktade referenser och andra gränsfall.
Den här modulen stöder hämtning av anteckningar i tre huvudformat (se Format
), som var och en fungerar bäst för olika användningsfall:
VALUE
utvärderar annoteringarna och returnerar deras värde. Detta är enklast att arbeta med, men det kan ge upphov till fel, t.ex. om annotationerna innehåller referenser till odefinierade namn.FORWARDREF
returnerarForwardRef
-objekt för annoteringar som inte kan lösas, vilket gör att du kan inspektera annoteringarna utan att utvärdera dem. Detta är användbart när du behöver arbeta med annoteringar som kan innehålla olösta framåtriktade referenser.STRING
returnerar anteckningarna som en sträng, liknande hur de skulle se ut i källfilen. Detta är användbart för dokumentationsgeneratorer som vill visa anteckningar på ett läsbart sätt.
Funktionen get_annotations()
är den huvudsakliga ingångspunkten för att hämta anteckningar. Om en funktion, klass eller modul anges returnerar den en annotationsordbok i det begärda formatet. Den här modulen innehåller också funktioner för att arbeta direkt med annotate function som används för att utvärdera anteckningar, till exempel get_annotate_from_class_namespace()
och call_annotate_function()
, samt funktionen call_evaluate_function()
för att arbeta med evaluate functions.
Se även
PEP 649 föreslog den nuvarande modellen för hur annoteringar fungerar i Python.
PEP 749 utvidgade olika aspekter av PEP 649 och introducerade modulen annotationlib
.
Bästa praxis för annoteringar ger bästa praxis för att arbeta med annoteringar.
typing-extensions tillhandahåller en backport av get_annotations()
som fungerar på tidigare versioner av Python.
Semantik för annoteringar¶
Hur annoteringar utvärderas har förändrats under Python 3:s historia, och beror för närvarande fortfarande på en future import. Det har funnits exekveringsmodeller för annotationer:
Stock semantics (standard i Python 3.0 till och med 3.13; se PEP 3107 och PEP 526): Annoteringar utvärderas ivrigt, när de påträffas i källkoden.
Stringifierade annoteringar (används med
from __future__ import annotations
i Python 3.7 och senare; se PEP 563): Annotationer lagras endast som strängar.Uppskjuten utvärdering (standard i Python 3.14 och nyare; se PEP 649 och PEP 749): Annoteringar utvärderas latent, endast när de används.
Som ett exempel kan man tänka sig följande program:
def func(a: Cls) -> Ingen:
print(a)
klass Cls: pass
print(func.__annotations__)
Detta kommer att bete sig på följande sätt:
Under standardsemantik (Python 3.13 och tidigare) kommer den att kasta ett
NameError
på raden därfunc
definieras, eftersomCls
är ett odefinierat namn vid den tidpunkten.Under strängifierade anteckningar (om
from __future__ import annotations
används) kommer den att skriva ut{'a': 'Cls', 'return': 'None'}
.Under uppskjuten utvärdering (Python 3.14 och senare) kommer den att skriva ut
{'a': <class 'Cls'>, 'return': None}
.
Stocksemantik användes när funktionsannoteringar först introducerades i Python 3.0 (av PEP 3107) eftersom detta var det enklaste och mest uppenbara sättet att implementera annoteringar. Samma exekveringsmodell användes när variabelannoteringar introducerades i Python 3.6 (av PEP 526). Stocksemantik orsakade dock problem när man använde annotationer som typtips, till exempel ett behov av att hänvisa till namn som ännu inte är definierade när annotationen påträffas. Dessutom fanns det prestandaproblem med att exekvera annotationer vid modulimport. Därför introducerade PEP 563 i Python 3.7 möjligheten att lagra annotationer som strängar med syntaxen from __future__ import annotations
. Planen vid den tidpunkten var att så småningom göra detta beteende till standard, men ett problem uppstod: strängifierade annoteringar är svårare att bearbeta för dem som introspekterar annoteringar vid körning. Ett alternativt förslag, PEP 649, introducerade den tredje exekveringsmodellen, uppskjuten utvärdering, och implementerades i Python 3.14. Stringifierade annoteringar används fortfarande om from __future__ import annotations
är närvarande, men detta beteende kommer så småningom att tas bort.
Klasser¶
- class annotationlib.Format¶
En
IntEnum
som beskriver de format i vilka anteckningar kan returneras. Medlemmar i enum, eller deras motsvarande heltalsvärden, kan skickas tillget_annotations()
och andra funktioner i denna modul, samt till__annotate__
funktioner.- VALUE = 1¶
Värdena är resultatet av utvärderingen av annoteringsuttrycken.
- VALUE_WITH_FAKE_GLOBALS = 2¶
Specialvärde som används för att signalera att en annotate-funktion utvärderas i en speciell miljö med falska globaler. När detta värde skickas till annotate-funktioner ska de antingen returnera samma värde som för formatet
Format.VALUE
, eller ge upphov tillNotImplementedError
för att signalera att de inte stöder exekvering i denna miljö. Detta format används endast internt och bör inte skickas till funktionerna i denna modul.
- FORWARDREF = 3¶
Värdena är verkliga annoteringsvärden (enligt formatet
Format.VALUE
) för definierade värden ochForwardRef
-proxyer för odefinierade värden. Verkliga objekt kan innehålla referenser tillForwardRef
proxyobjekt.
- STRING = 4¶
Värdena är textsträngen i annotationen som den visas i källkoden, upp till ändringar inklusive, men inte begränsat till, normaliseringar av blanksteg och optimeringar av konstantvärden.
De exakta värdena för dessa strängar kan komma att ändras i framtida versioner av Python.
Tillagd i version 3.14.
- class annotationlib.ForwardRef¶
Ett proxyobjekt för framåtriktade referenser i annotationer.
Instanser av denna klass returneras när formatet
FORWARDREF
används och annotationer innehåller ett namn som inte kan lösas. Detta kan inträffa när en framåtriktad referens används i en annotering, t.ex. när en klass refereras innan den har definierats.- __forward_arg__¶
En sträng som innehåller den kod som utvärderades för att producera
ForwardRef
. Strängen kanske inte är exakt likvärdig med den ursprungliga källan.
- evaluate(*, owner=None, globals=None, locals=None, type_params=None, format=Format.VALUE)¶
Utvärdera den framåtriktade referensen och återge dess värde.
Om argumentet format är
VALUE
(standard) kan den här metoden ge upphov till ett undantag, t.ex.NameError
, om den framåtriktade referensen hänvisar till ett namn som inte kan lösas. Argumenten till denna metod kan användas för att tillhandahålla bindningar för namn som annars skulle vara odefinierade. Om argumentet format ärFORWARDREF
, kommer metoden aldrig att ge upphov till ett undantag, men kan returnera enForwardRef
-instans. Om till exempel forward reference-objektet innehåller kodenlist[undefined]
, därundefined
är ett namn som inte är definierat, kommer utvärderingen medFORWARDREF
-formatet att returneralist[ForwardRef('undefined')]
. Om argumentet format ärSTRING
, kommer metoden att returnera__forward_arg__
.Parametern owner anger vilken mekanism som föredras för att skicka information om omfattning till denna metod. Ägaren till en
ForwardRef
är det objekt som innehåller den annotation somForwardRef
härrör från, t.ex. ett modulobjekt, typobjekt eller funktionsobjekt.Parametrarna globals, locals och type_params ger en mer exakt mekanism för att påverka de namn som är tillgängliga när
ForwardRef
utvärderas. globals och locals skickas tilleval()
och representerar de globala och lokala namnrymder i vilka namnet utvärderas. Parametern type_params är relevant för objekt som skapas med den ursprungliga syntaxen för generic classes och functions. Det är en tupel av typ-parametrar som är i omfattning medan den framåtriktade referensen utvärderas. Om man t.ex. utvärderar enForwardRef
som hämtats från en annotation som finns i klassnamnrymden för en generisk klassC
, bör type_params sättas tillC.__type_params__
.ForwardRef
-instanser som returneras avget_annotations()
behåller referenser till information om det scope de härstammar från, så det kan räcka med att anropa denna metod utan ytterligare argument för att utvärdera sådana objekt.ForwardRef
-instanser som skapas på annat sätt kanske inte har någon information om sitt scope, så det kan vara nödvändigt att skicka argument till denna metod för att utvärdera dem framgångsrikt.Om inga owner, globals, locals eller type_params anges och
ForwardRef
inte innehåller information om sitt ursprung, används tomma globals- och locals-ordlistor.
Tillagd i version 3.14.
Funktioner¶
- annotationlib.annotations_to_string(annotations)¶
Konverterar en annotationsdikt som innehåller runtime-värden till en dikt som bara innehåller strängar. Om värdena inte redan är strängar konverteras de med hjälp av
type_repr()
. Detta är tänkt som en hjälp för användartillhandahållna annotate-funktioner som stöder formatetSTRING
men som inte har tillgång till koden som skapar annotationerna.Detta används t.ex. för att implementera
STRING
förtyping.TypedDict
-klasser som skapats genom den funktionella syntaxen:>>> from typing import TypedDict >>> Movie = TypedDict("movie", {"name": str, "year": int}) >>> get_annotations(Movie, format=Format.STRING) {'name': 'str', 'year': 'int'}
Tillagd i version 3.14.
- annotationlib.call_annotate_function(annotate, format, *, owner=None)¶
Anropa annotate function annotate med det angivna formatet, en medlem av enum
Format
, och returnera den annoteringsordbok som funktionen har producerat.Denna hjälpfunktion behövs eftersom annoteringsfunktioner som genereras av kompilatorn för funktioner, klasser och moduler endast stöder formatet
VALUE
när de anropas direkt. För att stödja andra format anropar den här funktionen annotate-funktionen i en speciell miljö som gör det möjligt att producera annotationer i de andra formaten. Detta är en användbar byggsten när man implementerar funktionalitet som delvis behöver utvärdera annotationer medan en klass konstrueras.owner är det objekt som äger annoteringsfunktionen, vanligtvis en funktion, klass eller modul. Om det anges används det i formatet
FORWARDREF
för att producera ettForwardRef
-objekt som innehåller mer information.Se även
PEP 649 innehåller en förklaring av den implementeringsteknik som används av denna funktion.
Tillagd i version 3.14.
- annotationlib.call_evaluate_function(evaluate, format, *, owner=None)¶
Anropar evaluate function evaluate med det angivna formatet, en medlem av enum
Format
, och returnerar det värde som funktionen producerar. Detta liknarcall_annotate_function()
, men den senare returnerar alltid en ordbok som mappar strängar till annoteringar, medan denna funktion returnerar ett enda värde.Detta är avsett att användas med de utvärderingsfunktioner som genereras för latent utvärderade element relaterade till typaliaser och typparametrar:
typing.TypeAliasType.evaluate_value()
, värdet av typaliasertyping.TypeVar.evaluate_bound()
, bindning av typvariablertyping.TypeVar.evaluate_constraints()
, begränsningarna för typvariablertyping.TypeVar.evaluate_default()
, standardvärdet för typvariablertyping.ParamSpec.evaluate_default()
, standardvärdet för parameterspecifikationertyping.TypeVarTuple.evaluate_default()
, standardvärdet för typvariabeltuplar
owner är det objekt som äger funktionen evaluate, t.ex. typalias eller typvariabelobjekt.
format kan användas för att styra det format som värdet returneras i:
>>> 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'
Tillagd i version 3.14.
- annotationlib.get_annotate_from_class_namespace(namespace)¶
Hämtar annotate function från en klassnamnrymdsordbok namnrymd. Returnerar
None
om namnrymden inte innehåller någon annotate-funktion. Detta är främst användbart innan klassen har skapats helt (t.ex. i en metaklass); när klassen finns kan annotate-funktionen hämtas medcls.__annotate__
. Se below för ett exempel på användning av denna funktion i en metaklass.Tillagd i version 3.14.
- annotationlib.get_annotations(obj, *, globals=None, locals=None, eval_str=False, format=Format.VALUE)¶
Beräkna annoteringsdikten för ett objekt.
obj kan vara en anropsbar, klass, modul eller annat objekt med
__annotate__
eller__annotations__
attribut. Att skicka något annat objekt ger upphov tillTypeError
.Parametern format styr det format i vilket anteckningarna returneras och måste vara en medlem i enum
Format
eller dess heltalsekvivalent. De olika formaten fungerar på följande sätt:VÄRDE:
object.__annotations__
prövas först; om den inte finns anropas funktionenobject.__annotate__
om den finns.FORWARDREF: Om
object.__annotations__
finns och kan utvärderas framgångsrikt, används den; annars anropas funktionenobject.__annotate__
. Om den inte heller finns, försökerobject.__annotations__
igen och eventuella fel från åtkomsten till den tas upp igen.STRING: Om
object.__annotate__
finns, anropas den först; annars användsobject.__annotations__
och strängifieras medannotations_to_string()
.
Returnerar en dict.
get_annotations()
returnerar en ny dict varje gång den anropas; om den anropas två gånger på samma objekt returneras två olika men likvärdiga dicts.Denna funktion hanterar flera detaljer åt dig:
Om eval_str är true, kommer värden av typen
str
att avsträngas med hjälp aveval()
. Detta är avsett att användas med strängade annotationer (from __future__ import annotations
). Det är ett fel att sätta eval_str till true med andra format änFormat.VALUE
.Om obj inte har någon annotationsdikt returneras en tom dikt. (Funktioner och metoder har alltid en annotationsdikt; klasser, moduler och andra typer av anropsbara objekt kanske inte har det)
Ignorerar ärvda annotationer på klasser, liksom annotationer på metaklasser. Om en klass inte har någon egen annotationsdikt returneras en tom dikt.
Alla åtkomster till objektmedlemmar och dict-värden görs med hjälp av
getattr()
ochdict.get()
för säkerhets skull.
eval_str styr om värden av typen
str
ska ersättas med resultatet av anropet aveval()
på dessa värden:Om eval_str är true, anropas
eval()
på värden av typenstr
. (Observera attget_annotations()
inte fångar upp undantag; omeval()
ger upphov till ett undantag kommer den att spola tillbaka stacken förbi anropet avget_annotations()
)Om eval_str är false (standard) är värden av typen
str
oförändrade.
globals och locals skickas in till
eval()
; se dokumentationen föreval()
för mer information. Om globals eller locals ärNone
, kan denna funktion ersätta det värdet med en kontextspecifik standard, beroende påtype(obj)
:Om obj är en modul är globals standardvärdet för
obj.__dict__
.Om obj är en klass är globals standardvärdet
sys.modules[obj.__module__].__dict__
och locals standardvärdet klassnamnrymden för obj.Om obj är en callable är globals standardvärdet för
obj.__globals__
, men om obj är en omsluten funktion (medfunctools.update_wrapper()
) eller ettfunctools.partial
-objekt, är den omsluten tills en icke omsluten funktion hittas.
Att anropa
get_annotations()
är bästa praxis för att få tillgång till annotationsdikten för ett objekt. Se Bästa praxis för annoteringar för mer information om bästa praxis för annotationer.>>> def f(a: int, b: str) -> float: ... pass >>> get_annotations(f) {'a': <class 'int'>, 'b': <class 'str'>, 'return': <class 'float'>}
Tillagd i version 3.14.
- annotationlib.type_repr(value)¶
Konverterar ett godtyckligt Python-värde till ett format som kan användas av
STRING
-formatet. Detta anroparrepr()
för de flesta objekt, men har speciell hantering för vissa objekt, t.ex. typobjekt.Detta är tänkt som en hjälp för användartillhandahållna annotate-funktioner som stöder formatet
STRING
men som inte har tillgång till koden som skapar annotationerna. Det kan också användas för att tillhandahålla en användarvänlig strängrepresentation för andra objekt som innehåller värden som är vanliga i annoteringar.Tillagd i version 3.14.
Recept¶
Använda annoteringar i en metaklass¶
En metaklass kan vilja inspektera eller till och med ändra annoteringarna i en klass kropp under klassens skapande. För att göra detta krävs att anteckningar hämtas från klassens namnrymdsordbok. För klasser som skapats med from __future__ import annotations
kommer annotationerna att finnas i nyckeln __annotations__
i ordlistan. För andra klasser med anteckningar kan get_annotate_from_class_namespace()
användas för att hämta anteckningsfunktionen och call_annotate_function()
kan användas för att anropa den och hämta anteckningarna. Att använda formatet FORWARDREF
är oftast bäst, eftersom det gör det möjligt för annoteringarna att referera till namn som ännu inte kan lösas när klassen skapas.
För att modifiera annotationerna är det bäst att skapa en annotate-funktion som anropar den ursprungliga annotate-funktionen, gör eventuella nödvändiga justeringar och returnerar resultatet.
Nedan visas ett exempel på en metaklass som filtrerar bort alla typing.ClassVar
-annoteringar från klassen och placerar dem i ett separat attribut:
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
Begränsningar av formatet STRING
¶
Formatet STRING
är avsett att approximera källkoden för anteckningen, men den implementeringsstrategi som används innebär att det inte alltid är möjligt att återskapa den exakta källkoden.
För det första kan strängifieraren naturligtvis inte återskapa någon information som inte finns i den kompilerade koden, inklusive kommentarer, blanksteg, parenteser och operationer som förenklas av kompilatorn.
För det andra kan stringifieraren fånga upp nästan alla operationer som involverar namn som letas upp i något scope, men den kan inte fånga upp operationer som helt och hållet arbetar med konstanter. Som en följd av detta betyder det också att det inte är säkert att begära STRING
-formatet på opålitlig kod: Python är tillräckligt kraftfullt för att det är möjligt att utföra godtycklig kod även utan tillgång till några globaler eller inbyggda program. Till exempel:
>>> 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'}
Anteckning
Det här exemplet fungerar i skrivande stund, men det är beroende av implementeringsdetaljer och det finns ingen garanti för att det fungerar i framtiden.
Bland de olika typer av uttryck som finns i Python, som representeras av modulen ast
, stöds vissa uttryck, vilket innebär att STRING
-formatet i allmänhet kan återskapa den ursprungliga källkoden; andra stöds inte, vilket innebär att de kan resultera i felaktig utdata eller ett fel.
Följande stöds (ibland med förbehåll):
-
ast.Invert
(~
),ast.UAdd
(+
), ochast.USub
(-
) stödsast.Not
(not
) stöds inte
ast.Dict
(utom när du använder**
uppackning)ast.Call
(utom vid användning av**
uppackning)ast.Constant
(dock inte den exakta representationen av konstanten; t.ex. försvinner escape-sekvenser i strängar; hexadecimala tal konverteras till decimal)ast.Attribute
(förutsatt att värdet inte är en konstant)ast.Subscript
(förutsatt att värdet inte är en konstant)ast.Starred
(*
uppackning)ast.namn
Följande stöds inte, men ger ett informativt fel när strängifieraren stöter på dem:
ast.FormattedValue
(f-strängar; felet upptäcks inte om konverteringsspecifikatorer som!r
används)ast.JoinedStr
(f-strängar)
Följande stöds inte och resulterar i felaktiga utdata:
ast.BoolOp
(och
ocheller
)
Följande är inte tillåtna i annotationsomfång och är därför inte relevanta:
Begränsningar av formatet FORWARDREF
¶
Formatet FORWARDREF
syftar till att producera verkliga värden så mycket som möjligt, och allt som inte kan lösas upp ersätts med ForwardRef
-objekt. Det påverkas av i stort sett samma begränsningar som formatet STRING
: anteckningar som utför operationer på litteraler eller som använder uttryckstyper som inte stöds kan ge upphov till undantag när de utvärderas med formatet FORWARDREF
.
Nedan följer några exempel på beteendet med uttryck som inte stöds:
>>> 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'}