python - tipos - ¿Por qué el nombre de la clase contenedora no se reconoce como una anotación de función de valor de retorno?
tipos de funciones en python (2)
Esta pregunta ya tiene una respuesta aquí:
Iba a utilizar las anotaciones de la función Python para especificar el tipo del valor de retorno de un método de fábrica estático. Entiendo que este es uno de los casos de uso deseados para las anotaciones.
class Trie:
@staticmethod
def from_mapping(mapping) -> Trie:
# docstrings and initialization ommitted
trie = Trie()
return trie
PEP 3107 establece que:
Las anotaciones de funciones no son más que una forma de asociar expresiones de Python arbitrarias con varias partes de una función en tiempo de compilación.
Trie
es una expresión válida en Python, ¿no es así? Python no está de acuerdo o, mejor dicho, no puede encontrar el nombre:
def from_mapping(mapping) -> Trie:
NameError: name ''Trie'' is not defined
Vale la pena señalar que este error no ocurre si se especifica un tipo fundamental (como object
o int
) o un tipo de biblioteca estándar (como collections.deque
).
¿Qué está causando este error y cómo puedo solucionarlo?
PEP 484 proporciona una solución oficial a esto en forma de referencias futuras .
Cuando una sugerencia de tipo contiene nombres que aún no se han definido, esa definición se puede expresar como una cadena literal, que se resolverá más adelante.
En el caso del código de la pregunta:
class Trie:
@staticmethod
def from_mapping(mapping) -> Trie:
# docstrings and initialization ommitted
trie = Trie()
return trie
Se convierte en
class Trie:
@staticmethod
def from_mapping(mapping) -> ''Trie'':
# docstrings and initialization ommitted
trie = Trie()
return trie
Trie
es una expresión válida y se evalúa al valor actual asociado con el nombre de Trie
. Pero ese nombre aún no está definido: un objeto de clase solo está vinculado a su nombre después de que el cuerpo de la clase se haya ejecutado hasta su finalización. Notarás el mismo comportamiento en este ejemplo mucho más simple:
class C:
myself = C
# or even just
C
Normalmente, la solución alternativa sería establecer el atributo de clase después de que se haya definido la clase, fuera del cuerpo de la clase. Esta no es una opción realmente buena aquí, aunque funciona. Alternativamente, puede usar cualquier valor de marcador de posición en la definición inicial, luego reemplazarlo en las __annotations__
(lo cual es legal porque es un diccionario regular):
class C:
def f() -> ...: pass
print(C.f.__annotations__)
C.f.__annotations__[''return''] = C
print(C.f.__annotations__)
Sin embargo, se siente bastante hacky. Dependiendo de su caso de uso, podría ser posible utilizar un objeto centinela (por ejemplo, CONTAINING_CLASS = object()
) y dejar que se interprete lo que realmente procesa las anotaciones.