When I try to overwrite a method in a subclass with different return and additional arguments, Pylance raises these two exceptions (Strict type checking):
Method "my_method" overrides class "foo" in an incompatible manner
Positional parameter count mismatch; base method has 3, but override has 4 Pylance (reportIncompatibleMethodOverride)
Return type mismatch: base method returns type "tuple[Any, Any]", override returns type "tuple[Any, Any, Any]"
"tuple[Any, Any, Any]" is not assignable to "tuple[Any, Any]"
Tuple size mismatch; expected 2 but received 3 Pylance (reportIncompatibleMethodOverride)
from typing import Any
class foo:
def my_method(self, arg1: Any, arg2: Any) -> tuple[Any, Any]:
return arg1, arg2
class bar(foo):
def my_method(self, arg1: Any, arg2: Any, arg3: Any) -> tuple[Any, Any, Any]: # Pylance: Method "my_method" overrides class "foo" in an incompatible manner. Positional parameter count mismatch; base method has 3, but override has 4. Return type mismatch: base method returns type "tuple[Any, Any]", override returns type "tuple[Any, Any, Any]". "tuple[Any, Any, Any]" is not assignable to "tuple[Any, Any]". Tuple size mismatch; expected 2 but received 3.
return arg1, arg2, arg3
Of course, The obvious solution is to modify the parent classes my_method
to fit the return type and parameters. Or just use a different name.
class foo:
def my_method(self, arg1: Any, arg2: Any, arg3: Any) -> tuple[Any, Any, Any]:
return arg1, arg2, None
class bar(foo):
def my_method(self, arg1: Any, arg2: Any, arg3: Any) -> tuple[Any, Any, Any]:
return arg1, arg2, arg3
The problem now is the solution itself. Type checking is supposed to make your code more readable and easier to maintain. Having a extra return value and additional argument for no real reason gets confusing quickly. As for changing the name, In the real script where the problem arose, One method builds on top the other, using the name local variables and namespace. Changing to a different name for the subclassed method would break this functionality. Programming like this, In hindsight was probably a bad idea, but I'm not interested in a complete rewrite.
Does anyone have a better solution?
There's no way to make this type safe without major changes to your classes. This is because your code as written violates the Liskov substitution principle:
f: foo = foo()
f.my_method("spam", "eggs") # Works
b: foo = bar()
b.my_method("spam", "eggs") # Errors even though b is an instance of foo
The best option is to rename the method. I'm not entirely sure how the method is written, but if there's any way to rename it without "breaking the functionality", that's the only way to ensure type safety.
The other reasonable option is to slap a # type: ignore
on the declaration, or just abandon static analysis entirely. Static type checking isn't a good fit for every project; if this sort of non–type safe functionality appears regularly in the code, this might be a bad use case for type hints.
I wouldn't recommend altering the definition of foo.my_method
. It looks error-prone and hard to maintain.