annotationlib
— Funcionalidade para introspecção de anotações¶
Código-fonte: Lib/annotationlib.py
O módulo annotationlib
fornece ferramentas para introspecção de anotações em módulos, classes e funções.
As anotações são avaliadas preguiçosamente e frequentemente contêm referências futuras a objetos que ainda não foram definidos quando a anotação é criada. Este módulo fornece um conjunto de ferramentas de baixo nível que podem ser usadas para recuperar anotações de forma confiável, mesmo na presença de referências futuras e outros casos extremos.
Este módulo oferece suporte a recuperação de anotações em três formatos principais (veja Format
), cada um dos quais funciona melhor para diferentes casos de uso:
VALUE
avalia as anotações e retorna seus valores. Isso é mais simples de usar, mas pode levantar erros, por exemplo, se as anotações contiverem referências a nomes indefinidos.FORWARDREF
retorna objetosForwardRef
para anotações que não podem ser resolvidas, permitindo que você inspecione as anotações sem avaliá-las. Isso é útil quando você precisa trabalhar com anotações que podem conter referências de encaminhamento não resolvidas.STRING
retorna as anotações como uma string, semelhante a como apareceria no arquivo fonte. Isso é útil para geradores de documentação que desejam exibir anotações de forma legível.
A função get_annotations()
é o principal ponto de entrada para recuperar anotações. Dada uma função, classe ou módulo, ela retorna um dicionário de anotações no formato solicitado. Este módulo também fornece funcionalidade para trabalhar diretamente com a função de anotação, usada para avaliar anotações, como get_annotate_from_class_namespace()
e call_annotate_function()
, bem como a função call_evaluate_function()
para trabalhar com funções de avaliação.
Ver também
PEP 649 propôs o modelo atual de como as anotações funcionam em Python.
PEP 749 expandiu vários aspectos de PEP 649 e introduziu o módulo annotationlib
.
Boas práticas para anotações fornece práticas recomendadas para trabalhar com anotações.
typing-extensions fornece um backport de get_annotations()
que funciona em versões anteriores do Python.
Semânticas de anotação¶
A forma como as anotações são avaliadas mudou ao longo da história do Python 3 e atualmente ainda depende de uma importação futura. Houve modelos de execução para anotações:
Semântica de estoque (padrão no Python 3.0 a 3.13; veja PEP 3107 e PEP 526): As anotações são avaliadas avidamente, à medida que são encontradas no código-fonte.
Anotações em string (usadas com
from __future__ import annotations
no Python 3.7 e versões mais recentes; veja PEP 563): As anotações são armazenadas apenas como strings.Avaliação adiada (padrão no Python 3.14 e versões mais recentes; veja PEP 649 e PEP 749): As anotações são avaliadas preguiçosamente, somente quando são acessadas.
Como exemplo, considere o seguinte programa:
def func(a: Cls) -> None:
print(a)
class Cls: pass
print(func.__annotations__)
Isso se comportará da seguinte maneira:
De acordo com a semântica de estoque (Python 3.13 e versões anteriores), ele levantará uma
NameError
na linha ondefunc
está definido, porqueCls
é um nome indefinido naquele ponto.Em anotações stringificadas (se
from __future__ import annotations
for usado), exibirá{'a': 'Cls', 'return': 'None'}
.Em avaliação adiada (Python 3.14 e posterior), exibiirá
{'a': <class 'Cls'>, 'return': None}
.
A semântica de estoque foi usada quando as anotações de função foram introduzidas pela primeira vez no Python 3.0 (por PEP 3107) porque esta era a maneira mais simples e óbvia de implementar anotações. O mesmo modelo de execução foi usado quando as anotações de variáveis foram introduzidas no Python 3.6 (por PEP 526). No entanto, a semântica de estoque causou problemas ao usar anotações como dicas de tipo, como a necessidade de se referir a nomes que ainda não estavam definidos quando a anotação era encontrada. Além disso, havia problemas de desempenho com a execução de anotações no momento da importação do módulo. Portanto, no Python 3.7, PEP 563 introduziu a capacidade de armazenar anotações como strings usando a sintaxe from __future__ import annotations
. O plano na época era eventualmente tornar esse comportamento o padrão, mas um problema surgiu: anotações em string são mais difíceis de processar para aqueles que inspecionam anotações em tempo de execução. Uma proposta alternativa, PEP 649, introduziu o terceiro modelo de execução, avaliação adiada, e foi implementada no Python 3.14. Anotações em string ainda são usadas se from __future__ import annotations
estiver presente, mas esse comportamento será eventualmente removido.
Classes¶
- class annotationlib.Format¶
Uma
IntEnum
que descreve os formatos nos quais as anotações podem ser retornadas. Membros da enumeração, ou seus valores inteiros equivalentes, podem ser passados paraget_annotations()
e outras funções neste módulo, bem como para funções__annotate__
.- VALUE = 1¶
Os valores são o resultado da avaliação das expressões de anotação.
- VALUE_WITH_FAKE_GLOBALS = 2¶
Valor especial usado para sinalizar que uma função de anotação está sendo avaliada em um ambiente especial com variáveis globais falsas. Ao receber este valor, as funções de anotação devem retornar o mesmo valor do formato
Format.VALUE
ou levantarNotImplementedError
para sinalizar que não suportam execução neste ambiente. Este formato é usado apenas internamente e não deve ser passado para as funções deste módulo.
- FORWARDREF = 3¶
Valores são valores de anotação reais (conforme o formato
Format.VALUE
) para valores definidos e proxiesForwardRef
para valores indefinidos. Objetos reais podem conter referências a objetos proxyForwardRef
.
- STRING = 4¶
Valores são a string de texto da anotação como ela aparece no código-fonte, até modificações, incluindo, mas não se limitando a, normalizações de espaços em branco e otimizações de valores constantes.
Os valores exatos dessas strings podem mudar em versões futuras do Python.
Adicionado na versão 3.14.
- class annotationlib.ForwardRef¶
Um objeto proxy para referências futuras em anotações.
Instâncias desta classe são retornadas quando o formato
FORWARDREF
é usado e as anotações contêm um nome que não pode ser resolvido. Isso pode acontecer quando uma referência de avanço é usada em uma anotação, como quando uma classe é referenciada antes de ser definida.- __forward_arg__¶
Uma string contendo o código que foi avaliado para produzir
ForwardRef
. A string pode não ser exatamente equivalente à fonte original.
- evaluate(*, owner=None, globals=None, locals=None, type_params=None, format=Format.VALUE)¶
Avalia a referência futura, retornando seu valor.
Se o argumento format for
VALUE
(o padrão), este método poderá levantar uma exceção, comoNameError
, caso a referência futura se refira a um nome que não pode ser resolvido. Os argumentos deste método podem ser usados para fornecer ligações para nomes que, de outra forma, seriam indefinidos. Se o argumento format forFORWARDREF
, o método nunca levantará uma exceção, mas poderá retornar uma instância deForwardRef
. Por exemplo, se o objeto de referência futura contiver o códigolist[undefined]
, ondeundefined
é um nome que não está definido, avaliá-lo com o formatoFORWARDREF
retornarálist[ForwardRef('undefined')]
. Se o argumento format forSTRING
, o método retornará__forward_arg__
.O parâmetro owner fornece o mecanismo preferencial para passar informações de escopo para este método. O proprietário de uma
ForwardRef
é o objeto que contém a anotação da qual aForwardRef
deriva, como um objeto módulo, objeto tipo ou objeto função.Os parâmetros globals, locals e type_params fornecem um mecanismo mais preciso para influenciar os nomes disponíveis quando
ForwardRef
é avaliado. globals e locals são passados paraeval()
, representando os espaços de nomes global e local nos quais o nome é avaliado. O parâmetro type_params é relevante para objetos criados usando a sintaxe nativa para classes genéricas e funções. É uma tupla de parâmetros de tipo que estão no escopo enquanto a referência futura está sendo avaliada. Por exemplo, ao avaliar umForwardRef
recuperado de uma anotação encontrada no espaço de nomes de classe de uma classe genéricaC
, type_params deve ser definido comoC.__type_params__
.Instâncias de
ForwardRef
retornadas porget_annotations()
retêm referências a informações sobre o escopo de onde se originaram, portanto, chamar esse método sem argumentos adicionais pode ser suficiente para avaliar tais objetos. Instâncias deForwardRef
criadas por outros meios podem não ter nenhuma informação sobre seu escopo, portanto, passar argumentos para esse método pode ser necessário para avaliá-las com sucesso.Se nenhum entre owner, globals, locals ou type_params for fornecido e
ForwardRef
não contiver informações sobre sua origem, dicionários globals e locals vazios serão usados.
Adicionado na versão 3.14.
Funções¶
- annotationlib.annotations_to_string(annotations)¶
Converte um dicionário de anotações contendo valores de tempo de execução em um dicionário contendo apenas strings. Se os valores ainda não forem strings, eles serão convertidos usando
type_repr()
. Isso serve como um auxiliar para funções de anotação fornecidas pelo usuário que oferecem suporte ao formatoSTRING
, mas não têm acesso ao código que cria as anotações.Por exemplo, isso é usado para implementar o
STRING
para classestyping.TypedDict
criadas por meio da sintaxe funcional:>>> from typing import TypedDict >>> Movie = TypedDict("movie", {"name": str, "year": int}) >>> get_annotations(Movie, format=Format.STRING) {'name': 'str', 'year': 'int'}
Adicionado na versão 3.14.
- annotationlib.call_annotate_function(annotate, format, *, owner=None)¶
Chama a função de anotação annotate com o format fornecido, um membro da enumeração
Format
e retorne o dicionário de anotações produzido pela função.Esta função auxiliar é necessária porque as funções de anotação geradas pelo compilador para funções, classes e módulos oferecem suporte a apenas o formato
VALUE
quando chamadas diretamente. Para oferecer suporte a outros formatos, esta função chama a função de anotação em um ambiente especial que permite a produção de anotações nos outros formatos. Este é um bloco de construção útil ao implementar funcionalidades que precisam avaliar anotações parcialmente enquanto uma classe está sendo construída.owner é o objeto que possui a função de anotação, geralmente uma função, classe ou módulo. Se fornecido, é usado no formato
FORWARDREF
para produzir um objetoForwardRef
que contém mais informações.Ver também
PEP 649 contém uma explicação da técnica de implementação usada por esta função.
Adicionado na versão 3.14.
- annotationlib.call_evaluate_function(evaluate, format, *, owner=None)¶
Chama a função de avaliação evaluate com o format fornecido, um membro da enumeração
Format
e retorna o valor produzido pela função. Isso é semelhante acall_annotate_function()
, mas esta última sempre retorna um dicionário que mapeia strings para anotações, enquanto esta função retorna um único valor.Isso se destina ao uso com as funções de avaliação geradas para elementos avaliados preguiçosamente relacionados a apelidos de tipo e parâmetros de tipo:
typing.TypeAliasType.evaluate_value()
, o valor dos apelidos de tipotyping.TypeVar.evaluate_bound()
, a delimitação das variáveis de tipotyping.TypeVar.evaluate_constraints()
, as restrições de tipos variáveistyping.TypeVar.evaluate_default()
, o valor padrão de tipos variáveistyping.ParamSpec.evaluate_default()
, o valor padrão de especificações de parâmetrostyping.TypeVarTuple.evaluate_default()
, o valor padrão de tuplas de tipos variáveis
owner é o objeto que possui a função de avaliação, como o apelido de tipo ou o objeto de tipo variável.
format pode ser usado para controlar o formato no qual o valor é retornado:
>>> 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'
Adicionado na versão 3.14.
- annotationlib.get_annotate_from_class_namespace(namespace)¶
Recupera a função de anotação de um dicionário de espaço de nomes de classe namespace. Retorna
None
se o espaço de nomes não contiver uma função de anotação. Isso é útil principalmente antes da classe ser totalmente criada (por exemplo, em uma metaclasse); após a classe existir, a função de anotação pode ser recuperada comcls.__annotate__
. Veja abaixo para um exemplo usando esta função em uma metaclasse.Adicionado na versão 3.14.
- annotationlib.get_annotations(obj, *, globals=None, locals=None, eval_str=False, format=Format.VALUE)¶
Calcula o dicionário de anotações para um objeto.
obj pode ser um objeto chamável, classe, módulo ou outro objeto com os atributos
__annotate__
ou__annotations__
. Passar qualquer outro objeto levantaTypeError
.O parâmetro format controla o formato em que as anotações são retornadas e deve ser um membro da enumeração
Format
ou seu equivalente inteiro. Os diferentes formatos funcionam da seguinte forma:VALUE:
object.__annotations__
é tentado primeiro; se não existir, a funçãoobject.__annotate__
é chamada, se existir.FORWARDREF: Se
object.__annotations__
existir e puder ser avaliado com sucesso, ele será usado; caso contrário, a funçãoobject.__annotate__
será chamada. Se também não existir,object.__annotations__
será tentado novamente e qualquer erro ao acessá-lo será levantado novamente.STRING: Se
object.__annotate__
existir, ele será chamado primeiro; caso contrário,object.__annotations__
será usado e transformado em string usandoannotations_to_string()
.
Retorna um dict.
get_annotations()
retorna um novo dict toda vez que é chamado; chamá-lo duas vezes no mesmo objeto retornará dois dicts diferentes, mas equivalentes.Esta função cuida de vários detalhes para você:
Se eval_str for verdadeiro, valores do tipo
str
serão descodificados usandoeval()
. Isso se destina ao uso com anotações transformadas em string (from __future__ import annotations
). É um erro definir eval_str como true com formatos diferentes deFormat.VALUE
.Se obj não tiver um dicionário de anotações, retorna um dicionário vazio. (Funções e métodos sempre têm um dicionário de anotações; classes, módulos e outros tipos de chamáveis podem não ter.)
Ignora anotações herdadas em classes, bem como anotações em metaclasses. Se uma classe não tiver seu próprio dicionário de anotações, retorna um dicionário vazio.
Todos os acessos aos membros do objeto e valores do dicionário são feitos usando
getattr()
edict.get()
por segurança.
eval_str controla se valores do tipo
str
são substituídos ou não pelo resultado da chamada deeval()
nesses valores:Se eval_str for verdadeiro,
eval()
será chamado em valores do tipostr
. (Observe queget_annotations()
não captura exceções; seeval()
levanta uma exceção, ele desenrolará a pilha após a chamada deget_annotations()
.)Se eval_str for falso (o padrão), os valores do tipo
str
não serão alterados.
globals e locals são passados para
eval()
; consulte a documentação deeval()
para mais informações. Se globals ou locals forNone
, esta função pode substituir esse valor por um padrão específico do contexto, dependendo detype(obj)
:Se obj for um módulo, globals assume como padrão
obj.__dict__
.Se obj for uma classe, globals assume como padrão
sys.modules[obj.__module__].__dict__
e locals assume como padrão o espaço de nomes da classe obj.Se obj for um chamável, globals assume como padrão
obj.__globals__
, embora se obj for uma função encapsulada (usandofunctools.update_wrapper()
) ou um objetofunctools.partial
, ela será desencapsulada até que uma função não encapsulada seja encontrada.
Chamar
get_annotations()
é uma boa prática para acessar o dicionário de anotações de qualquer objeto. Consulte Boas práticas para anotações para obter mais informações sobre as práticas recomendadas para anotações.>>> def f(a: int, b: str) -> float: ... pass >>> get_annotations(f) {'a': <class 'int'>, 'b': <class 'str'>, 'return': <class 'float'>}
Adicionado na versão 3.14.
- annotationlib.type_repr(value)¶
Converte um valor Python arbitrário para um formato adequado para uso pelo formato
STRING
. Isso chamarepr()
para a maioria dos objetos, mas tem um tratamento especial para alguns objetos, como objetos tipo.Isto serve como um auxiliar para funções de anotação fornecidas pelo usuário que oferecem suporte ao formato
STRING
, mas não têm acesso ao código que cria as anotações. Também pode ser usado para fornecer uma representação de string amigável para outros objetos que contêm valores comumente encontrados em anotações.Adicionado na versão 3.14.
Receitas¶
Usando anotações em uma metaclasse¶
Uma metaclasse pode querer inspecionar ou até mesmo modificar as anotações no corpo de uma classe durante a criação da classe. Isso requer a recuperação das anotações do dicionário de espaços de nomes da classe. Para classes criadas com from __future__ import annotations
, as anotações estarão na chave __annotations__
do dicionário. Para outras classes com anotações, get_annotate_from_class_namespace()
pode ser usado para obter a função annotate, e call_annotate_function()
pode ser usado para chamá-la e recuperar as anotações. Usar o formato FORWARDREF
geralmente é a melhor opção, pois permite que as anotações se refiram a nomes que ainda não podem ser resolvidos quando a classe é criada.
Para modificar as anotações, é melhor criar uma função de anotação que chame a função de anotação original, faça os ajustes necessários e retorne o resultado.
Abaixo está um exemplo de uma metaclasse que filtra todas as anotações typing.ClassVar
da classe e as coloca em um atributo separado:
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 comparação de strings para simplificar; uma solução
# mais robusta poderia usar 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: # nenhuma anotação
classvars = {}
wrapped_annotate = None
typ = super().__new__(mcls, name, bases, ns)
if wrapped_annotate is not None:
# Encapsula o __annotate__ original com um invólucro que remove ClassVars
typ.__annotate__ = wrapped_annotate
typ.classvars = classvars # Armazena as ClassVars em um atributo separado
return typ
Limitações do formato STRING
¶
O formato STRING
tem como objetivo aproximar o código-fonte da anotação, mas a estratégia de implementação usada significa que nem sempre é possível recuperar o código-fonte exato.
Primeiro, a transformação em string, é claro, não pode recuperar nenhuma informação que não esteja presente no código compilado, incluindo comentários, espaços em branco, parênteses e operações que são simplificadas pelo compilador.
Em segundo lugar, a transformação em string pode interceptar quase todas as operações que envolvem nomes pesquisados em algum escopo, mas não pode interceptar operações que operem totalmente em constantes. Como corolário, isso também significa que não é seguro solicitar o formato STRING
em código não confiável: Python é poderoso o suficiente para permitir a execução arbitrária de código mesmo sem acesso a nenhuma variável global ou embutida. Por exemplo:
>>> def f(x: (1).__class__.__base__.__subclasses__()[-1].__init__.__builtins__["print"]("Hello world")): pass
...
>>> annotationlib.get_annotations(f, format=annotationlib.Format.SOURCE)
Hello world
{'x': 'None'}
Nota
Este exemplo específico funciona no momento em que este artigo foi escrito, mas depende de detalhes de implementação e não há garantia de que funcionará no futuro.
Entre os diferentes tipos de expressões que existem em Python, conforme representado pelo módulo ast
, algumas expressões são suportadas, o que significa que o formato STRING
geralmente pode recuperar o código-fonte original; outras não são suportadas, o que significa que podem resultar em saída incorreta ou erro.
Os seguintes são aceitos (às vezes com ressalvas):
-
ast.Invert
(~
),ast.UAdd
(+
) east.USub
(-
) são suportadasast.Not
(not
) não é suportada
ast.Dict
(exceto ao usar desempacotamento**
)ast.Call
(exceto ao usar desempacotamento**
)ast.Constant
(embora não seja a representação exata da constante; por exemplo, sequências de escape em strings são perdidas; números hexadecimais são convertidos em decimais)ast.Attribute
(presumindo que o valor não é uma constante)ast.Subscript
(presumindo que o valor não é uma constante)ast.Starred
(desempacotamento*
)
Os seguintes não são suportados, mas geram um erro informativo quando encontrados pela transformação em string:
ast.FormattedValue
(f-strings; o erro não é detectado se especificadores de conversões como!r
forem usados)ast.JoinedStr
(f-strings)
Os seguintes itens não são suportados e resultam em saída incorreta:
Os seguintes itens não são permitidos em escopos de anotação e, portanto, não são relevantes:
Limitações do formato FORWARDREF
¶
O formato FORWARDREF
visa produzir valores reais o máximo possível, com tudo o que não puder ser resolvido sendo substituído por objetos ForwardRef
. Ele é afetado, em linhas gerais, pelas mesmas limitações do formato STRING
: anotações que realizam operações em literais ou que usam tipos de expressão não suportados podem levantar exceções quando avaliadas usando o formato FORWARDREF
.
Abaixo estão alguns exemplos do comportamento com expressões não suportadas:
>>> 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'}