Почему имя содержащего класса не распознается как аннотация функции возвращаемого значения?
Я собирался использовать аннотации функций Python, чтобы указать тип возвращаемого значения статического метода factory. Я понимаю, что это один из желаемых вариантов использования для аннотаций.
class Trie:
@staticmethod
def from_mapping(mapping) -> Trie:
# docstrings and initialization ommitted
trie = Trie()
return trie
PEP 3107 утверждает, что:
Аннотации функций - это не что иное, как способ сопоставления произвольных выражений Python с различными частями функции во время компиляции.
Trie
является допустимым выражением в Python, не так ли? Python не согласен или, скорее, не может найти имя:
def from_mapping(mapping) -> Trie:
NameError: name 'Trie' is not defined
Стоит отметить, что эта ошибка не возникает, если указан базовый тип (например, object
или int
) или стандартный тип библиотеки (например, collections.deque
).
Что вызывает эту ошибку и как ее исправить?
Ответы
Ответ 1
PEP 484 предоставляет официальное решение для этого в виде пересылаемых ссылок.
Если подсказка типа содержит имена, которые еще не определены, это определение может быть выражено как строковый литерал, который будет разрешен позже.
В случае кода вопроса:
class Trie:
@staticmethod
def from_mapping(mapping) -> Trie:
# docstrings and initialization ommitted
trie = Trie()
return trie
становится:
class Trie:
@staticmethod
def from_mapping(mapping) -> 'Trie':
# docstrings and initialization ommitted
trie = Trie()
return trie
Ответ 2
Trie
является допустимым выражением и вычисляет текущее значение, связанное с именем name Trie
. Но это имя еще не определено - объект класса привязан только к его имени после того, как тело класса запустилось до завершения. Вы заметите то же поведение в этом гораздо более простом примере:
class C:
myself = C
# or even just
C
Обычно обходной путь будет определять атрибут класса после того, как класс был определен, вне тела класса. Это не очень хороший вариант, хотя он работает. В качестве альтернативы вы можете использовать любое значение placeholder в первоначальном определении, а затем заменить его в __annotations__
(что является законным, потому что это обычный словарь):
class C:
def f() -> ...: pass
print(C.f.__annotations__)
C.f.__annotations__['return'] = C
print(C.f.__annotations__)
Однако он чувствует себя довольно взломанным. В зависимости от вашего варианта использования может быть возможно использовать объект-дозор (например, CONTAINING_CLASS = object()
) и оставить интерпретацию того, что на самом деле обрабатывает аннотации.