#python #python-3.x #mypy #metaclass
Вопрос:
Я должен использовать метакласс и должен получить доступ к переменной подкласса внутри новой функции (так что это, по сути, абстрактное свойство). Это, конечно, приводит к ошибкам MyPy, потому что «тип» не имеет атрибутов с именем FILE_PATH или АДРЕСА. Есть ли здесь какой-нибудь обходной путь?
class metaClass(type):
def __new__(cls, name, bases, dct):
x = super().__new__(cls, name, bases, dct)
if not hasattr(x, "FILE_PATH") or not hasattr(x, "ADDRESS"):
raise NotImplementedError("FILE_PATH and ADDRESS not set")
#do stuff with x.FILE_PATH and x.ADDRESS
path = x.FILE_PATH # <- "mypy --strict" errors here
...
return x
class subClass(metaclass=metaClass):
FILE_PATH = "file.type"
ADDRESS = "sadfsadfa"
Обновить:
Решается с помощью dct['FILE_PATH']
и dct['ADDRESS']
для доступа к переменным, заданным в дочернем классе.
Комментарии:
1. Делает ли установка
x.FILE_PATH
иx.ADDRESS
фиктивные значения перед созданием исключенияmypy
счастливыми?2. (Однако я, похоже, не могу воспроизвести проблему.)
3. Кроме того,
subClass
на самом деле это не подкласс, или, скорее , это всего лишь подклассobject
, как и любой другой класс без явного родительского класса.4. Установка фиктивных значений подавляет предупреждения в PyCharm, но MyPy все еще недоволен этим.
5. Можете ли вы предоставить точный рецепт , который вызывает проблему (включая то, как вы вызываете
mypy
, с любыми необходимыми опциями)?
Ответ №1:
Проверка наличия атрибутов в созданном классе dct
, как в описанном обходном пути, имеет один большой недостаток, который может быть применим или не применим к вашему проекту:
если атрибуты определены в суперклассе создаваемого класса, они не будут отображаться в пространстве имен класса перед вызовом type.__new___
.
Другими словами:
class Base(metaclass=metaClass):
FILE_PATH = "file.type"
ADDRESS = "sadfsadfa"
class Sub(Base):
pass
Этот код не пройдет вашу проверку , но пройдет проверку hasattr
, так как он делает правильные вещи при поиске атрибутов.
Я думаю, что это может быть одним из немногих мест, где использование typing.cast
будет не просто быстрым и грязным способом обмануть статическую проверку , а, скорее, правильным решением: поскольку статическая проверка не может знать, какой тип возвращается вызовом type.__new__
, мы используем typing.cast
, чтобы сделать это явным.
В качестве дополнительной вещи, чтобы заставить его работать, MyPy должен знать, что экземпляры metaClass
должны иметь эти атрибуты. Я нашел способ сделать это, чтобы аннотировать нужные атрибуты в самом метаклассе.
В любом случае, это проходит mypy --strict
import typing as T
class metaClass(type):
FILE_PATH: str
ADDRESS: str
def __new__(mcls, name: str, bases: tuple[type, ...], dct: dict[str, T.Any]) -> "metaClass":
x = super().__new__(mcls, name, bases, dct)
if not hasattr(x, "FILE_PATH") or not hasattr(x, "ADDRESS"):
raise NotImplementedError("FILE_PATH and ADDRESS not set")
cls = T.cast(metaClass, x)
#do stuff with x.FILE_PATH and x.ADDRESS
a = cls.FILE_PATH
return cls
class Base(metaclass=metaClass):
FILE_PATH = "file.type"
ADDRESS = "sadfsadfa"
(Я сделал некоторое переименование, чтобы избежать путаницы — как подКласс-> База, так как «подКласс» не является подклассом метакласса, и первый параметр to > metaclass.__new__
to mcls
: так как это метакласс, и его легче читать, если мы зарезервируем cls
в качестве имени для создаваемого класса)
Еще один вариант, поскольку MyPY явно не может понять механизм метакласса, состоит в том, чтобы пометить вещи внутри метода метакласса, которые MyPy будет игнорировать с # type: ignore
помощью комментария. cast
Однако это, по-видимому, действительно правильно в данном случае.