Repository navigation
Self type with multiple inheritance #18458
Description
Not sure whether this is actually a bug or not, but I'm getting a base class incompatibility error when inheriting from two classes defining a method with the same signature containing the typing.Self type.
To Reproduce
from typing import Self
class A:
def a_method(self) -> None:
pass
def method(self: Self, other: Self) -> None:
self.a_method()
other.a_method()
class B:
def b_method(self) -> None:
pass
def method(self: Self, other: Self) -> None:
self.b_method()
other.b_method()
class C(A, B):
passRunning mypy gives this error:
test.py:17: error: Definition of "method" in base class "A" is incompatible with definition in base class "B" [misc]
I expected Self to mean "whatever this type happens to be" (base class or subclass) but the error would suggest that it's interpreting Self as the specific class containing that method definition.
Next I tried the following variation:
from typing import TypeVar
T = TypeVar('T')
class A:
def a_method(self) -> None:
pass
def method(self: T, other: T) -> None:
self.a_method()
class B:
def b_method(self) -> None:
pass
def method(self: T, other: T) -> None:
self.b_method()
class C(A, B):
passThis fixes the incompatibility error but results in new errors:
test.py:9: error: "T" has no attribute "a_method" [attr-defined]
test.py:15: error: "T" has no attribute "b_method" [attr-defined]
I also tried using two separate TypeVars, one with bound='A' and the other with bound='B', but this brought back the "incompatible definition" error from before.
Finally (after reverting the types back to Self), I updated C's definition to the following:
class C(A, B):
def method(self: Self, other: Self) -> None:
A.method(self, other)This ended up type-checking, but I'm wondering if mypy ought to be clever enough to figure it out without having to explicitly override method, or if there's some other alternative?
And if not, perhaps the error message about the incompatible definition could elaborate a bit more in the case where the type signatures "appear" to match due to the use of Self?
Environment
- Python 3.11.11
mypy1.14.1 (no flags or config file)
Activity
sterliakov commentedon Jan 14, 2025
This is trivial to fix (as far as I understand, that's just a bug, and
active_self_typeis None when checking base class compatibility - but it has nothing to do with scope,sub_infois already the correct typeinfo, and free vars aren't important here?)However,
Selfin argument position still bugs me. It's inherently unsafe and/or underspecified.To be clear, minimal unsafety illustration (crashes at runtime, accepted both by mypy and pyright):
We need a better approach to Self type in method arguments (actually IMO we need to prohibit it entirely, but I probably won't be heard).
ilevkivskyi commentedon Jan 14, 2025
From the very beginning we new that self types are inherently unsafe. It was kind of a conscious trade-off, as otherwise they will be a pain to use.
Bind self to the class being defined when checking multiple inheritan…
sterliakov commentedon Jan 14, 2025
This very problem is not about
Self, it can be reproduced with explicit typevars. This is obviously a usability+implementation complexity vs strictness tradeoff, and perhaps current approach is the most ergonomic one (though I'd prefer to reject Self and its typevar-based counterpart in any contravariant position in overrides, and so just stay away from Self in my own code entirely except for factory classmethods).ilevkivskyi commentedon Jan 15, 2025
When I say from the very beginning I mean from the very beginning. What you call "with explicit typevars" is also called self-types, since it works not just because this is how type variables work, there are specific code paths that were added to support this pattern, note the title of #2193 (and this is btw how
Selfis implemented internally, it is just transformed into a type variable).