Подтвердить что ты не робот

Почему имя содержащего класса не распознается как аннотация функции возвращаемого значения?

Я собирался использовать аннотации функций 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).

Что вызывает эту ошибку и как ее исправить?

4b9b3361

Ответ 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()) и оставить интерпретацию того, что на самом деле обрабатывает аннотации.