Description
The object.__class__
getter is annotated to return type[Self]
, while the setter accepts any argument that's assignable object
.
Lines 107 to 110 in 300204c
As I also mentioned in microsoft/pyright#9410 (comment), this could lead to type-unsafe situation, e.g.
class A: ...
class B: ...
a = A()
a.__class__ = B
reveal_type(a.__class__) # Type of "a.__class__" is "type[A]"
Pyright accepts this, as you'd expect by looking at the stubs (playground link).
However, mypy reports an error reports an error at a.__class__ = B
(playground link), so I'm guessing it special-cased it.
At runtime the __class__
behaves just like any other attribute, i.e. invariantly, when getting/setting.
So that's why I think that object.__class__
should be more "self-centered", and only accept type[Self]
in its setter.
As for a practical use-case; I recently came up with the following trick. But at the moment, this unexpectedly requires adding # type: ignore
s with both pyright and mypy:
from typing import Protocol, override
class Just[T](Protocol): # ~T@Just is invariant
@property # type: ignore[override]
@override
def __class__(self, /) -> type[T]: ... # pyright: ignore[reportIncompatibleMethodOverride]
@__class__.setter
def __class__(self, t: type[T], /) -> None: ...
def only_ints_pls(x: Just[int], /):
assert type(x) is int
only_ints_pls(42) # true negative
only_ints_pls("42") # true positive (sanity check)
only_ints_pls(False) # true positive (only in mypy at the moment)
playgrounds: