Repository navigation
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
| @property | |
| def __class__(self) -> type[Self]: ... | |
| @__class__.setter | |
| def __class__(self, type: type[object], /) -> None: ... |
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: ignores 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:
Activity
Daverball commentedon Nov 14, 2024
The reason mypy reports an error, is because mypy doesn't support asymmetric properties, it will only use the annotation from the getter, so mypy already works the way you want it to by accident.
I agree however that it seems better to only allow
type[Self]for the setter, even if that doesn't reflect what's legal at runtime, since the only runtime requirement is a compatible object layout, it at least means as long as a subclass doesn't violate LSP it should be type safe to assign a subclass.If you want to do an unsafe assignment you can still do it, you'll just have to ignore the type error.
jorenham commentedon Nov 14, 2024
Perhaps this could also explain why this hasn't been brought up before (as far as I could tell) 🤔
srittau commentedon Nov 16, 2024
An exploratory PR couldn't hurt. To me, changing the setter types seems fine.
jorenham commentedon Nov 16, 2024
the mypy primer seems to like it
hauntsaninja commentedon Nov 16, 2024
As Daverball mentions, mypy doesn't model setters of properties correctly, so it already works the way you want it to so primer is a no-op
jorenham commentedon Nov 18, 2024
Does that mypy bug also apply to inheritance,
Protocolmatching, and other LSP things?Daverball commentedon Nov 18, 2024
Yes, also I wouldn't call it a bug per se, it's a difference between mypy's type model and python's object model of properties. I assume this was originally an intentional design choice, so mypyc can always optimize properties a certain way.