6.5 KiB
Default
class M(type): ...
reveal_type(M.__class__) # revealed: <class 'type'>
object
reveal_type(object.__class__) # revealed: <class 'type'>
type
reveal_type(type.__class__) # revealed: <class 'type'>
Basic
class M(type): ...
class B(metaclass=M): ...
reveal_type(B.__class__) # revealed: <class 'M'>
Invalid metaclass
A class which doesn't inherit type
(and/or doesn't implement a custom __new__
accepting the same
arguments as type.__new__
) isn't a valid metaclass.
class M: ...
class A(metaclass=M): ...
# TODO: emit a diagnostic for the invalid metaclass
reveal_type(A.__class__) # revealed: <class 'M'>
Linear inheritance
If a class is a subclass of a class with a custom metaclass, then the subclass will also have that metaclass.
class M(type): ...
class A(metaclass=M): ...
class B(A): ...
reveal_type(B.__class__) # revealed: <class 'M'>
Linear inheritance with PEP 695 generic class
The same is true if the base with the metaclass is a generic class.
[environment]
python-version = "3.13"
class M(type): ...
class A[T](metaclass=M): ...
class B(A): ...
class C(A[int]): ...
reveal_type(B.__class__) # revealed: <class 'M'>
reveal_type(C.__class__) # revealed: <class 'M'>
Conflict (1)
The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases. ("Strict subclass" is a synonym for "proper subclass"; a non-strict subclass can be a subclass or the class itself.)
class M1(type): ...
class M2(type): ...
class A(metaclass=M1): ...
class B(metaclass=M2): ...
# error: [conflicting-metaclass] "The metaclass of a derived class (`C`) must be a subclass of the metaclasses of all its bases, but `M1` (metaclass of base class `A`) and `M2` (metaclass of base class `B`) have no subclass relationship"
class C(A, B): ...
reveal_type(C.__class__) # revealed: type[Unknown]
Conflict (2)
The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases. ("Strict subclass" is a synonym for "proper subclass"; a non-strict subclass can be a subclass or the class itself.)
class M1(type): ...
class M2(type): ...
class A(metaclass=M1): ...
# error: [conflicting-metaclass] "The metaclass of a derived class (`B`) must be a subclass of the metaclasses of all its bases, but `M2` (metaclass of `B`) and `M1` (metaclass of base class `A`) have no subclass relationship"
class B(A, metaclass=M2): ...
reveal_type(B.__class__) # revealed: type[Unknown]
Common metaclass
A class has two explicit bases, both of which have the same metaclass.
class M(type): ...
class A(metaclass=M): ...
class B(metaclass=M): ...
class C(A, B): ...
reveal_type(C.__class__) # revealed: <class 'M'>
Metaclass metaclass
A class has an explicit base with a custom metaclass. That metaclass itself has a custom metaclass.
class M1(type): ...
class M2(type, metaclass=M1): ...
class M3(M2): ...
class A(metaclass=M3): ...
class B(A): ...
reveal_type(A.__class__) # revealed: <class 'M3'>
Diamond inheritance
class M(type): ...
class M1(M): ...
class M2(M): ...
class M12(M1, M2): ...
class A(metaclass=M1): ...
class B(metaclass=M2): ...
class C(metaclass=M12): ...
# error: [conflicting-metaclass] "The metaclass of a derived class (`D`) must be a subclass of the metaclasses of all its bases, but `M1` (metaclass of base class `A`) and `M2` (metaclass of base class `B`) have no subclass relationship"
class D(A, B, C): ...
reveal_type(D.__class__) # revealed: type[Unknown]
Unknown
from nonexistent_module import UnknownClass # error: [unresolved-import]
class C(UnknownClass): ...
reveal_type(C.__class__) # revealed: type[type] & Unknown
class M(type): ...
class A(metaclass=M): ...
class B(A, UnknownClass): ...
reveal_type(B.__class__) # revealed: type[M] & Unknown
Duplicate
class M(type): ...
class A(metaclass=M): ...
class B(A, A): ... # error: [duplicate-base] "Duplicate base class `A`"
reveal_type(B.__class__) # revealed: type[M] & Unknown
Non-class
When a class has an explicit metaclass
that is not a class, but is a callable that accepts
type.__new__
arguments, we should return the meta-type of its return type.
def f(*args, **kwargs) -> int:
return 1
class A(metaclass=f): ...
# TODO: Should be `int`
reveal_type(A) # revealed: <class 'A'>
reveal_type(A.__class__) # revealed: type[int]
def _(n: int):
# error: [invalid-metaclass]
class B(metaclass=n): ...
# TODO: Should be `Unknown`
reveal_type(B) # revealed: <class 'B'>
reveal_type(B.__class__) # revealed: type[Unknown]
def _(flag: bool):
m = f if flag else 42
# error: [invalid-metaclass]
class C(metaclass=m): ...
# TODO: Should be `int | Unknown`
reveal_type(C) # revealed: <class 'C'>
reveal_type(C.__class__) # revealed: type[Unknown]
class SignatureMismatch: ...
# TODO: Emit a diagnostic
class D(metaclass=SignatureMismatch): ...
# TODO: Should be `Unknown`
reveal_type(D) # revealed: <class 'D'>
# TODO: Should be `type[Unknown]`
reveal_type(D.__class__) # revealed: <class 'SignatureMismatch'>
Cyclic
Retrieving the metaclass of a cyclically defined class should not cause an infinite loop.
class A(B): ... # error: [cyclic-class-definition]
class B(C): ... # error: [cyclic-class-definition]
class C(A): ... # error: [cyclic-class-definition]
reveal_type(A.__class__) # revealed: type[Unknown]
PEP 695 generic
[environment]
python-version = "3.12"
class M(type): ...
class A[T: str](metaclass=M): ...
reveal_type(A.__class__) # revealed: <class 'M'>
Metaclasses of metaclasses
class Foo(type): ...
class Bar(type, metaclass=Foo): ...
class Baz(type, metaclass=Bar): ...
class Spam(metaclass=Baz): ...
reveal_type(Spam.__class__) # revealed: <class 'Baz'>
reveal_type(Spam.__class__.__class__) # revealed: <class 'Bar'>
reveal_type(Spam.__class__.__class__.__class__) # revealed: <class 'Foo'>
def test(x: Spam):
reveal_type(x.__class__) # revealed: type[Spam]
reveal_type(x.__class__.__class__) # revealed: type[Baz]
reveal_type(x.__class__.__class__.__class__) # revealed: type[Bar]
reveal_type(x.__class__.__class__.__class__.__class__) # revealed: type[Foo]
reveal_type(x.__class__.__class__.__class__.__class__.__class__) # revealed: type[type]
# revealed: type[type]
reveal_type(x.__class__.__class__.__class__.__class__.__class__.__class__.__class__.__class__)