TL; DR : se você estiver usando o Python 4.0, ele simplesmente funciona. A partir de hoje (2019) em 3.7+, você deve ativar esse recurso usando uma instrução futura ( from __future__ import annotations
) - para Python 3.6 ou abaixo, use uma string.
Eu acho que você recebeu essa exceção:
NameError: name 'Position' is not defined
Isso ocorre porque Position
deve ser definido antes que você possa usá-lo em uma anotação, a menos que esteja usando o Python 4.
Python 3.7+: from __future__ import annotations
O Python 3.7 introduz o PEP 563: avaliação adiada de anotações . Um módulo que usa a instrução futura from __future__ import annotations
armazenará anotações como seqüências de caracteres automaticamente:
from __future__ import annotations
class Position:
def __add__(self, other: Position) -> Position:
...
Isso está programado para se tornar o padrão no Python 4.0. Como o Python ainda é uma linguagem de tipo dinâmico, portanto, nenhuma verificação de tipo é feita em tempo de execução, as anotações de digitação não devem ter impacto no desempenho, certo? Errado! Antes de python 3.7 do módulo de digitação costumava ser um dos mais lentos módulos python no núcleo assim se você import typing
você vai ver até 7 vezes aumento no desempenho quando você atualizar para 3.7.
Python <3.7: use uma string
De acordo com o PEP 484 , você deve usar uma string em vez da própria classe:
class Position:
...
def __add__(self, other: 'Position') -> 'Position':
...
Se você usa a estrutura do Django, isso pode ser familiar, pois os modelos do Django também usam strings para referências futuras (definições de chave estrangeira em que o modelo estrangeiro é self
ou ainda não foi declarado). Isso deve funcionar com Pycharm e outras ferramentas.
Fontes
As partes relevantes do PEP 484 e PEP 563 , para poupar a viagem:
Encaminhar referências
Quando uma dica de tipo contém nomes que ainda não foram definidos, essa definição pode ser expressa como uma string literal, a ser resolvida posteriormente.
Uma situação em que isso ocorre geralmente é a definição de uma classe de contêiner, em que a classe que está sendo definida ocorre na assinatura de alguns dos métodos. Por exemplo, o código a seguir (o início de uma implementação simples de árvore binária) não funciona:
class Tree:
def __init__(self, left: Tree, right: Tree):
self.left = left
self.right = right
Para resolver isso, escrevemos:
class Tree:
def __init__(self, left: 'Tree', right: 'Tree'):
self.left = left
self.right = right
O literal da string deve conter uma expressão Python válida (ou seja, compile (lit, '', 'eval') deve ser um objeto de código válido) e deve ser avaliada sem erros quando o módulo estiver totalmente carregado. O espaço para nome local e global em que é avaliado deve ser o mesmo espaço para nome no qual os argumentos padrão para a mesma função seriam avaliados.
e PEP 563:
No Python 4.0, as anotações de função e variável não serão mais avaliadas no momento da definição. Em vez disso, um formulário de string será preservado no respectivo __annotations__
dicionário. Os verificadores de tipo estático não verão diferença no comportamento, enquanto as ferramentas que usam anotações em tempo de execução terão que realizar uma avaliação adiada.
...
A funcionalidade descrita acima pode ser ativada a partir do Python 3.7 usando a seguinte importação especial:
from __future__ import annotations
Coisas que você pode se sentir tentado a fazer
A. Definir um manequim Position
Antes da definição da classe, coloque uma definição fictícia:
class Position(object):
pass
class Position(object):
...
Isso vai se livrar do NameError
e pode até parecer OK:
>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}
Mas é isso?
>>> for k, v in Position.__add__.__annotations__.items():
... print(k, 'is Position:', v is Position)
return is Position: False
other is Position: False
B. Patch de macaco para adicionar as anotações:
Você pode tentar um pouco da mágica de programação de Python e escrever um decorador para corrigir a definição de classe com o objetivo de adicionar anotações:
class Position:
...
def __add__(self, other):
return self.__class__(self.x + other.x, self.y + other.y)
O decorador deve ser responsável pelo equivalente a isso:
Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position
Pelo menos parece certo:
>>> for k, v in Position.__add__.__annotations__.items():
... print(k, 'is Position:', v is Position)
return is Position: True
other is Position: True
Provavelmente demais.
Conclusão
Se você estiver usando o 3.6 ou abaixo, use uma string literal contendo o nome da classe, no 3.7 use from __future__ import annotations
e ele simplesmente funcionará.
typing
pois qualquer tipo que você usar deve estar no escopo quando a string for avaliada.''
em volta da classe, e não os parâmetros de tipofrom __future__ import annotations
estiver usando - isso deve ser importado antes de todas as outras importações.A especificação do tipo como string é boa, mas sempre me irrita um pouco que basicamente estamos contornando o analisador. Portanto, é melhor você não digitar incorretamente nenhuma dessas strings literais:
Uma pequena variação é usar uma typevar vinculada; pelo menos, você deve escrever a string apenas uma vez ao declarar a typevar:
fonte
typing.Self
que especificar isso explicitamente.typing.Self
existia. O retorno de uma sequência codificada não falha ao retornar o tipo correto ao alavancar o polimorfismo. No meu caso, eu queria implementar um método de classe desserializado . Decidi devolver um ditado (kwargs) e ligarsome_class(**some_class.deserialize(raw_data))
.Position
, e não a classe, portanto o exemplo acima é tecnicamente incorreto. A implementação deve substituirPosition(
por algo comoself.__class__(
.other
, mas provavelmente dependeself
. Portanto, você precisará colocar a anotaçãoself
para descrever o comportamento correto (e talvezother
deva apenasPosition
mostrar que ele não está vinculado ao tipo de retorno). Isso também pode ser usado para casos em que você está trabalhando apenasself
. por exemplodef __aenter__(self: T) -> T:
O nome 'Posição' não está disponível no momento em que o corpo da classe é analisado. Não sei como você está usando as declarações de tipo, mas o PEP 484 do Python - que é o que a maioria dos modos deve usar se usar essas dicas de digitação diz que você pode simplesmente colocar o nome como uma string neste momento:
Verifique https://www.python.org/dev/peps/pep-0484/#forward-references - ferramentas em conformidade com as que saberão desembrulhar o nome da classe a partir daí e utilizá-lo (é sempre importante ter lembre-se de que a própria linguagem Python não faz nada dessas anotações - elas geralmente são destinadas à análise de código estático, ou é possível ter uma biblioteca / estrutura para verificação de tipo em tempo de execução - mas você deve definir isso explicitamente).
update Além disso, no Python 3.8, marque pep-563 - no Python 3.8 é possível escrever
from __future__ import annotations
para adiar a avaliação das anotações - as classes de referência direta devem funcionar de maneira direta.fonte
Quando uma dica de tipo baseada em string é aceitável, o
__qualname__
item também pode ser usado. Ele contém o nome da classe e está disponível no corpo da definição da classe.Ao fazer isso, renomear a classe não implica modificar as dicas de tipo. Mas, pessoalmente, eu não esperaria que editores de código inteligentes lidassem bem com esse formulário.
fonte