跳转至

元类

元类 metaclasses 是一个描述其他类的构造和行为的类,类似于类描述对象的构造和行为。 默认元类是 type,但可以使用其他元类。 元类允许创建 “一种不同类型的类”,例如 enum.Enumtyping.NamedTuple 和单例。

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

Metaclasses

A metaclasses is a class that describes the construction and behavior of other classes, similarly to how classes describe the construction and behavior of objects. The default metaclass is type, but it's possible to use other metaclasses. Metaclasses allows one to create "a different kind of class", such as enum.Enums、typing.NamedTuples and singletons.

Mypy has some special understanding of {py:class}~abc.ABCMeta and EnumMeta.

定义元类

class M(type):
    pass

class A(metaclass=M):
    pass

Defining a metaclass

class M(type):
    pass

class A(metaclass=M):
    pass

元类使用示例

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

from typing import Type, TypeVar, ClassVar
T = TypeVar('T')

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

    def make(cls: Type[T]) -> T:
        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: Unsupported operand types for + ("int" and "str")

Metaclass usage example

Mypy supports the lookup of attributes in the metaclass:

from typing import Type, TypeVar, ClassVar
T = TypeVar('T')

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

    def make(cls: Type[T]) -> T:
        M.count += 1
        return cls()

class A(metaclass=M):
    pass

a: A = A.make()  # make() is looked up at M; the result is an object of type A
print(A.count)

class B(A):
    pass

b: B = B.make()  # metaclasses are inherited
print(B.count + " objects were created")  # Error: Unsupported operand types for + ("int" and "str")

元类支持的陷阱和限制

请注意,元类对继承结构提出了一些要求,因此最好不要将元类和类层次结构结合起来:

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 Error: metaclass conflict
# 在运行时,上面的定义会引发异常
# TypeError: metaclass conflict: 派生类的元类必须是其所有基类的元类的(非严格)子类

class B12(A1, A2): pass  # Mypy Error: metaclass conflict

# 这可以通过通用元类子类型来解决:
class CorrectMeta(M1, M2): pass
class B2(A1, A2, metaclass=CorrectMeta): pass  # OK, 运行时也没问题
  • Mypy 不理解动态计算的元类,例如 class A(metaclass=f()): ...
  • Mypy 不会也不可能理解任意元类代码。
  • Mypy 仅将 type 的子类识别为潜在的元类。

Gotchas and limitations of metaclass support

Note that metaclasses pose some requirements on the inheritance structure, so it's better not to combine metaclasses and class hierarchies:

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 Error: metaclass conflict
# At runtime the above definition raises an exception
# 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 Error: metaclass conflict

# This can be solved via a common metaclass subtype:
class CorrectMeta(M1, M2): pass
class B2(A1, A2, metaclass=CorrectMeta): pass  # OK, runtime is also OK
  • Mypy does not understand dynamically-computed metaclasses, such as class A(metaclass=f()): ...
  • Mypy does not and cannot understand arbitrary metaclass code.
  • Mypy only recognizes subclasses of type as potential metaclasses.

最后更新: 2023年7月29日
创建日期: 2023年7月6日