在元类中使用子类变量的 MyPy 错误
MyPy error from using subclass variables in metaclass
我必须使用 metaclass 并且需要在新函数中访问 subclass 变量(因此它本质上是一个抽象 属性)。这当然会导致 MyPy 错误,因为 'type' 没有名为 FILE_PATH 的属性,也没有 ADDRESS。这里有什么解决方法吗?
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']
访问子 class 中设置的变量解决。
检查属性是否在创建的 class dct
中,如您发现的变通方法有一个很大的缺点 - 这可能适用于也可能不适用于您的项目:
如果属性是在您正在创建的 class 的超 class 中定义的,则在调用 type.__new___
之前它们不会显示在 class 命名空间中。
换句话说:
class Base(metaclass=metaClass):
FILE_PATH = "file.type"
ADDRESS = "sadfsadfa"
class Sub(Base):
pass
此代码将无法通过您的检查 - 但会通过 hasattr
的检查,因为它在查找属性时做了正确的事情。
我认为这可能是少数几个使用 typing.cast
的地方之一,它不仅是愚弄静态检查器的一种快速而肮脏的方法,而且是正确的做法: 由于静态检查器无法知道调用 type.__new__
返回的类型是什么,我们使用 typing.cast
使其明确。
作为使其工作的额外内容,MyPy 必须知道 metaClass
的实例应该具有这些属性。我发现这样做的方式
是将想要的属性注释到 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"
(我做了一些重命名以避免混淆 - 比如 subClass-> Base,因为“subClass”不是 metaclass 的子class,第一个参数是 metaclass.__new__
到 mcls
:因为它是元 class,如果我们保留 cls
作为要创建的 class 的名称,它更容易阅读)
另一个选择,因为 MyPY 显然无法弄清楚 metaclass 机制,是用 # type: ignore
评论。然而,cast
在这种情况下似乎确实是正确的做法。
我必须使用 metaclass 并且需要在新函数中访问 subclass 变量(因此它本质上是一个抽象 属性)。这当然会导致 MyPy 错误,因为 'type' 没有名为 FILE_PATH 的属性,也没有 ADDRESS。这里有什么解决方法吗?
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']
访问子 class 中设置的变量解决。
检查属性是否在创建的 class dct
中,如您发现的变通方法有一个很大的缺点 - 这可能适用于也可能不适用于您的项目:
如果属性是在您正在创建的 class 的超 class 中定义的,则在调用 type.__new___
之前它们不会显示在 class 命名空间中。
换句话说:
class Base(metaclass=metaClass):
FILE_PATH = "file.type"
ADDRESS = "sadfsadfa"
class Sub(Base):
pass
此代码将无法通过您的检查 - 但会通过 hasattr
的检查,因为它在查找属性时做了正确的事情。
我认为这可能是少数几个使用 typing.cast
的地方之一,它不仅是愚弄静态检查器的一种快速而肮脏的方法,而且是正确的做法: 由于静态检查器无法知道调用 type.__new__
返回的类型是什么,我们使用 typing.cast
使其明确。
作为使其工作的额外内容,MyPy 必须知道 metaClass
的实例应该具有这些属性。我发现这样做的方式
是将想要的属性注释到 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"
(我做了一些重命名以避免混淆 - 比如 subClass-> Base,因为“subClass”不是 metaclass 的子class,第一个参数是 metaclass.__new__
到 mcls
:因为它是元 class,如果我们保留 cls
作为要创建的 class 的名称,它更容易阅读)
另一个选择,因为 MyPY 显然无法弄清楚 metaclass 机制,是用 # type: ignore
评论。然而,cast
在这种情况下似乎确实是正确的做法。