在元类中使用子类变量的 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 在这种情况下似乎确实是正确的做法。