По некоторым внешним причинам я динамически генерирую набор классов данных с помощью make_dataclass
. В других частях моей кодовой базы я хочу использовать эти типы в подсказках типов. Но и mypy
, и pyright
жалуются.
$ pyright dynamic_types.py
/home/user/testing/dynamic_types.py
/home/user/testing/dynamic_types.py:23:18 - error: Variable not allowed in type expression (reportInvalidTypeForm)
1 error, 0 warnings, 0 informations
$ mypy dynamic_types.py
dynamic_types.py:23: error: Variable "dynamic_types.mydc" is not valid as a type [valid-type]
dynamic_types.py:23: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
Found 1 error in 1 file (checked 1 source file)
Я понимаю аргумент, но в моем случае «динамическая» часть — это словарь внутри того же модуля. Есть ли способ заставить это работать?
МВЕ:
from dataclasses import asdict, field, make_dataclass
class Base:
def my_method(self):
pass
spec = {
"kind_a": [("foo", int, field(default=None)), ("bar", str, field(default=None))]
}
def make_mydc(kind: str) -> type:
"""Create dataclass from list of fields and types."""
fields = spec[kind]
return make_dataclass("mydc_t", fields, bases=(Base,))
mydc = make_mydc("kind_a")
def myfunc(data: mydc):
print(asdict(data))
data = mydc(foo=42, bar = "test")
myfunc(data)
ПРИМЕЧАНИЕ. Я не могу использовать Base
для ввода подсказки, поскольку у нее нет всех атрибутов.
@InSync спасибо за эту альтернативу, я ее рассмотрю. Возможно, это приемлемый вариант, поскольку в CI мы используем только mypy
.
Большинство средств проверки статических типов работают на уровне AST: они преобразуют текстовое представление программы в AST и выводят типы оттуда. Это работает, когда типы известны заранее, например. г.:
from typing import Union
a: int = 7
b: Union[int, str] = 3 if a < 5 else 'some string'
Правильность этих аннотаций типов можно проверить, только взглянув на AST. Однако str
также будет допустимой аннотацией типа для b
, потому что эта строка всегда будет иметь значение str
. Но для того, чтобы прийти к такому выводу, проверяющему типы необходимо будет проанализировать, что на самом деле происходит, одного взгляда на AST недостаточно. Вам нужна какая-то интерпретация.
Интерпретатор Python, конечно, интерпретирует код, запуская его. Но вы также можете сделать это статически, т.е. е. анализируя код без его запуска.
Для этого вам придется пойти на один уровень глубже и принять во внимание граф потока управления (и не только AST). Чтобы получить полный граф потока управления, вам необходимо абстрактно интерпретировать код Python.
(Абстрактная интерпретация выполняется довольно медленно (слишком много возможных путей программы), тогда как средства проверки типов, которые смотрят только на AST, работают быстро.)
Ваш код не создает новый тип класса данных с помощью методов AST (нет ClassDef или аналогичного узла AST), а вместо этого вызывает функцию Python, которая использует больше кода Python, который в конечном итоге вызывает __prepare__
и т. д. Чтобы определить, что make_mydc
возвращает весь этот код, нужен интерпретировать абстрактно.
Ваш пример кода кажется мне выполнимым (т. е. может быть проверен тип в разумные сроки). Но он замедлится, если вы вызовете myfunc
из более сложного кода.
Насколько мне известно, для Python не существует абстрактных интерпретаторов, выходящих за рамки проверки концепции. (Скажите, пожалуйста, есть ли они.)
Спасибо за очень четкий ответ, я боялся, что это так :(. Я делал это динамически, чтобы избежать дублирования, потому что мне приходилось поддерживать сопоставление с чем-то внешним, что на самом деле не под моим контролем. Но, возможно, это неизбежно .
Вы можете написать плагин Mypy, но он не будет работать для других средств проверки типов. Статическая типизация должна быть статичной.