TL; DR : si está utilizando Python 4.0, simplemente funciona. A partir de hoy (2019) en 3.7+, debe activar esta función usando una declaración futura ( from __future__ import annotations
); para Python 3.6 o inferior, use una cadena.
Supongo que tienes esta excepción:
NameError: name 'Position' is not defined
Esto se Position
debe a que debe definirse antes de poder usarlo en una anotación a menos que esté usando Python 4.
Python 3.7+: from __future__ import annotations
Python 3.7 presenta PEP 563: evaluación pospuesta de anotaciones . Un módulo que usa la declaración futura from __future__ import annotations
almacenará anotaciones como cadenas automáticamente:
from __future__ import annotations
class Position:
def __add__(self, other: Position) -> Position:
...
Esto está programado para convertirse en el predeterminado en Python 4.0. Dado que Python todavía es un lenguaje de tipo dinámico, por lo que no se realiza ninguna verificación de tipo en tiempo de ejecución, las anotaciones de escritura no deberían tener un impacto en el rendimiento, ¿verdad? ¡Incorrecto! Antes de Python 3.7, el módulo de mecanografía solía ser uno de los módulos de Python más lentos en el núcleo, por lo import typing
que si verá un aumento de rendimiento de hasta 7 veces cuando actualice a 3.7.
Python <3.7: usa una cadena
De acuerdo con PEP 484 , debe usar una cadena en lugar de la clase en sí:
class Position:
...
def __add__(self, other: 'Position') -> 'Position':
...
Si usa el marco de Django, esto puede resultarle familiar, ya que los modelos de Django también usan cadenas para referencias directas (definiciones de clave externa donde el modelo externo está self
o no declarado aún). Esto debería funcionar con Pycharm y otras herramientas.
Fuentes
Las partes relevantes de PEP 484 y PEP 563 , para ahorrarle el viaje:
Reenviar referencias
Cuando una sugerencia de tipo contiene nombres que aún no se han definido, esa definición puede expresarse como un literal de cadena, que se resolverá más adelante.
Una situación en la que esto ocurre comúnmente es la definición de una clase contenedor, donde la clase que se define ocurre en la firma de algunos de los métodos. Por ejemplo, el siguiente código (el inicio de una implementación de árbol binario simple) no funciona:
class Tree:
def __init__(self, left: Tree, right: Tree):
self.left = left
self.right = right
Para abordar esto, escribimos:
class Tree:
def __init__(self, left: 'Tree', right: 'Tree'):
self.left = left
self.right = right
El literal de cadena debe contener una expresión Python válida (es decir, compilar (iluminado, '', 'evaluar') debe ser un objeto de código válido) y debe evaluar sin errores una vez que el módulo se haya cargado completamente. El espacio de nombres local y global en el que se evalúa debe ser el mismo espacio de nombres en el que se evaluarían los argumentos predeterminados para la misma función.
y PEP 563:
En Python 4.0, las anotaciones de funciones y variables ya no se evaluarán en el momento de la definición. En cambio, se conservará una forma de cadena en el __annotations__
diccionario respectivo . Los verificadores de tipo estático no verán diferencias en el comportamiento, mientras que las herramientas que usan anotaciones en tiempo de ejecución deberán realizar una evaluación pospuesta.
...
La funcionalidad descrita anteriormente se puede habilitar a partir de Python 3.7 utilizando la siguiente importación especial:
from __future__ import annotations
Cosas que quizás tengas la tentación de hacer
A. Define un muñeco Position
Antes de la definición de clase, coloque una definición ficticia:
class Position(object):
pass
class Position(object):
...
Esto eliminará NameError
e incluso puede verse bien:
>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}
¿Pero es?
>>> for k, v in Position.__add__.__annotations__.items():
... print(k, 'is Position:', v is Position)
return is Position: False
other is Position: False
B. Parche de mono para agregar las anotaciones:
Es posible que desee probar un poco de magia de metaprogramación de Python y escribir un decorador para parchear la definición de clase para agregar anotaciones:
class Position:
...
def __add__(self, other):
return self.__class__(self.x + other.x, self.y + other.y)
El decorador debe ser responsable del equivalente de esto:
Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position
Al menos parece correcto:
>>> for k, v in Position.__add__.__annotations__.items():
... print(k, 'is Position:', v is Position)
return is Position: True
other is Position: True
Probablemente demasiados problemas.
Conclusión
Si está utilizando 3.6 o menos, use un literal de cadena que contenga el nombre de la clase, use 3.7 from __future__ import annotations
y simplemente funcionará.
typing
ya que cualquier tipo que use debe estar dentro del alcance cuando se evalúa la cadena.''
vuelta a la clase, no a los parámetros de tipofrom __future__ import annotations
: esto debe importarse antes que todas las demás importaciones.Especificar el tipo como cadena está bien, pero siempre me molesta un poco que básicamente eludamos el analizador. Por lo tanto, es mejor que no escriba mal ninguna de estas cadenas literales:
Una ligera variación es usar un typevar encuadernado, al menos entonces debe escribir la cadena solo una vez al declarar el typevar:
fuente
typing.Self
que especificar esto explícitamente.typing.Self
existía algo así . La devolución de una cadena codificada no devuelve el tipo correcto al aprovechar el polimorfismo. En mi caso, quería implementar un método de clase deserializado . Me decidí a devolver un dict (kwargs) y llamarsome_class(**some_class.deserialize(raw_data))
.Position
, y no la clase, por lo que el ejemplo anterior es técnicamente incorrecto. La implementación debería reemplazarsePosition(
por algo asíself.__class__(
.other
, pero lo más probable es que realmente dependa deself
. Por lo tanto, necesitaría poner la anotaciónself
para describir el comportamiento correcto (y tal vezother
debería ser soloPosition
para mostrar que no está vinculado al tipo de retorno). Esto también se puede usar para casos en los que solo está trabajandoself
. Por ejemplodef __aenter__(self: T) -> T:
El nombre 'Posición' no está disponible en el momento en que se analiza el cuerpo de la clase. No sé cómo está utilizando las declaraciones de tipo, pero el PEP 484 de Python, que es lo que la mayoría de los modos debería usar si usa estas sugerencias de escritura, dice que puede simplemente poner el nombre como una cadena en este punto:
Verifique https://www.python.org/dev/peps/pep-0484/#forward-references : las herramientas que se ajusten a eso sabrán desenvolver el nombre de la clase desde allí y usarlo. (Siempre es importante tener Tenga en cuenta que el lenguaje Python en sí mismo no hace nada de estas anotaciones, generalmente están destinadas al análisis de código estático, o uno podría tener una biblioteca / marco para la verificación de tipos en tiempo de ejecución, pero debe configurarlo explícitamente).
actualización Además, a partir de Python 3.8, marque pep-563 - a partir de Python 3.8 es posible escribir
from __future__ import annotations
para diferir la evaluación de las anotaciones - las clases de referencia directa deberían funcionar de manera directa.fuente
Cuando una sugerencia de tipo basada en cadenas es aceptable, el
__qualname__
elemento también se puede usar. Contiene el nombre de la clase, y está disponible en el cuerpo de la definición de clase.Al hacer esto, cambiar el nombre de la clase no implica modificar las sugerencias de tipo. Pero personalmente no esperaría que los editores de códigos inteligentes manejen bien este formulario.
fuente