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 returnerar ForwardRef-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är func definieras, eftersom Cls ä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 till get_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 till NotImplementedError 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 och ForwardRef-proxyer för odefinierade värden. Verkliga objekt kan innehålla referenser till ForwardRef 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 är FORWARDREF, kommer metoden aldrig att ge upphov till ett undantag, men kan returnera en ForwardRef-instans. Om till exempel forward reference-objektet innehåller koden list[undefined], där undefined är ett namn som inte är definierat, kommer utvärderingen med FORWARDREF-formatet att returnera list[ForwardRef('undefined')]. Om argumentet format är STRING, 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 som ForwardRef 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 till eval() 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 en ForwardRef som hämtats från en annotation som finns i klassnamnrymden för en generisk klass C, bör type_params sättas till C.__type_params__.

ForwardRef-instanser som returneras av get_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 formatet STRING men som inte har tillgång till koden som skapar annotationerna.

Detta används t.ex. för att implementera STRING för typing.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 ett ForwardRef-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 liknar call_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:

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 med cls.__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 till TypeError.

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 funktionen object.__annotate__ om den finns.

  • FORWARDREF: Om object.__annotations__ finns och kan utvärderas framgångsrikt, används den; annars anropas funktionen object.__annotate__. Om den inte heller finns, försöker object.__annotations__ igen och eventuella fel från åtkomsten till den tas upp igen.

  • STRING: Om object.__annotate__ finns, anropas den först; annars används object.__annotations__ och strängifieras med annotations_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 av eval(). 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 än Format.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() och dict.get() för säkerhets skull.

eval_str styr om värden av typen str ska ersättas med resultatet av anropet av eval() på dessa värden:

  • Om eval_str är true, anropas eval() på värden av typen str. (Observera att get_annotations() inte fångar upp undantag; om eval() ger upphov till ett undantag kommer den att spola tillbaka stacken förbi anropet av get_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ör eval() för mer information. Om globals eller locals är None, 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 (med functools.update_wrapper()) eller ett functools.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 anropar repr() 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):

Följande stöds inte, men ger ett informativt fel när strängifieraren stöter på dem:

Följande stöds inte och resulterar i felaktiga utdata:

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'}