#python #python-typing #python-dataclasses
Вопрос:
Вот простой пример, в котором я пытаюсь создать рекурсивное определение узла, содержащее необязательный дочерний элемент, который также является узлом. Код компилируется, но когда я пытаюсь получить доступ к определениям типов, которые я получаю node
, они не определены. Можно ли обойти эту ошибку?
import dataclasses
import typing as t
node_type = dataclasses.make_dataclass(
"node", [("child", t.Optional["node"], dataclasses.field(default=None))]
)
print(t.get_type_hints(node_type))
Выходы
NameError: name 'node' is not defined
Я использую python 3.9.2.
Ответ №1:
Здесь есть три проблемы. Они разрешимы, но они могут быть не совсем разрешимы в тех ситуациях, когда вы бы их действительно использовали dataclasses.make_dataclass
.
Первая проблема заключается в том , что typing.get_type_hints
ищется класс с именем 'node'
, но вы назвали глобальную переменную node_type
. Имя , которое вы передаете make_dataclass
, имя, которое вы используете в аннотациях, и имя, которому вы назначаете класс данных, должны быть одинаковыми:
Node = dataclasses.make_dataclass(
"Node", [("child", t.Optional["Node"], dataclasses.field(default=None))]
)
Но этого все равно будет недостаточно, потому typing.get_type_hints
что это не поиск в правильном пространстве имен. Это вторая проблема.
Когда вы вызываете typing.get_type_hints
класс, typing.get_type_hints
попытаетесь разрешить строковые аннотации, заглянув в модуль, в котором был определен класс. Он определяет этот модуль, просматривая __module__
запись в классе __dict__
. Поскольку вы создали свой класс узлов странным способом, который не проходит через обычный class
оператор, класс __module__
не настроен для ссылки на нужный модуль. Вместо этого он настроен на 'types'
.
Вы можете исправить это, вручную предварительно настроив __module__
__name__
текущий модуль:
Node = dataclasses.make_dataclass(
"Node",
[("child", t.Optional["Node"], dataclasses.field(default=None))],
namespace={'__module__': __name__}
)
Затем typing.get_type_hints
вы сможете разрешить аннотации строк.
Мета-проблема в том, что если вы используете dataclasses.make_dataclass
на практике, вы, вероятно, не знаете имени класса. Вероятно, вы используете его в функции и/или внутри цикла. typing.get_type_hints
должен быть в состоянии найти класс с помощью глобальной переменной, соответствующей имени класса, но имена динамических переменных запутаны.
Вы можете воспользоваться простым подходом, просто установив глобальную globals()
:
globals()[your_dataclass.__name__] = your_dataclass
но это опасно. Если два сгенерированных класса имеют одно и то же имя, второй заменит первый. Если созданный класс имеет то же имя, что и что-то другое в глобальном пространстве имен , например, если вы сделали from some_dependency import Thing
, а затем создали класс с именем Thing
, созданный класс уничтожит существующее глобальное значение.
Если вы можете гарантировать, что этого не произойдет, globals()
может быть, все в порядке. Если вы не можете предоставить такие гарантии, вам, возможно, потребуется сделать что-то вроде создания нового модуля для каждого сгенерированного класса, чтобы каждый из них получил свое собственное независимое глобальное пространство имен, или вы можете просто принять и задокументировать тот факт, что get_type_hints
это не будет работать для ваших сгенерированных классов.
Комментарии:
1. хорошо, это очень всеобъемлющий и, безусловно, правильный ответ. Я использую этот синтаксис для динамического создания классов во время выполнения, которые имеют правильные аннотации типов, а имя класса во время выполнения неизвестно. Можно ли динамически задать имя, которому я назначаю класс данных? Например, я заранее не знаю, что переменная будет называться узлом.
2. Не обращай внимания на мой комментарий. Я могу просто использовать
globals()
.3. @LukeMurray: Обратите внимание, что это зависит от того, что никакие два класса, созданные таким образом, не имеют одного и того же имени, и никакие имена классов никогда не сталкиваются с именем, которое вы уже используете в этой глобальной области.