元类(Metaclasses)

元类是描述其他类的构造和行为的类,类似于类描述对象的构造和行为。默认的元类是 type,但也可以使用其他元类。元类允许创建 “不同种类的类(a different kind of class)” ,例如 ~enum.Enum~typing.NamedTuple 和单例(singletons)。

Mypy 对 ~abc.ABCMetaEnumMeta 有一些特殊的理解。

定义元类(Defining)

class M(type):
    pass

class A(metaclass=M):
    pass

元类使用示例(example)

Mypy 支持在元类中查找属性:

from typing import ClassVar, Self

class M(type):
    count: ClassVar[int] = 0

    def make(cls) -> Self:
        M.count += 1
        return cls()

class A(metaclass=M):
    pass

a: A = A.make()  # make() 在 M 中查找;结果是类型 A 的对象
print(A.count)

class B(A):
    pass

b: B = B.make()  # 元类是继承的
print(B.count + " objects were created")  # Error: 不支持的操作数类型用于 + ("int" 和 "str")

备注

在 Python 3.10 及更早版本中,Self 可以在 typing_extensions 中找到。

元类支持的陷阱和限制(Gotchas and limitations)

请注意,元类对继承结构有一些要求,因此最好不要将元类与类层次结构结合:

class M1(type): pass
class M2(type): pass

class A1(metaclass=M1): pass
class A2(metaclass=M2): pass

class B1(A1, metaclass=M2): pass  # Mypy 错误:元类冲突
# 在运行时,上述定义引发异常
# TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

class B12(A1, A2): pass  # Mypy 错误:元类冲突

# 这可以通过一个公共元类子类型来解决:
class CorrectMeta(M1, M2): pass
class B2(A1, A2, metaclass=CorrectMeta): pass  # OK,运行时也可以正常工作
  • Mypy 不理解动态计算的元类,例如 class A(metaclass=f()): ...

  • Mypy 不理解也不能理解任意元类代码。

  • Mypy 只识别 type 的子类作为潜在的元类。