pytype
pytype copied to clipboard
Mixin resolved as Any causes false positive not-instantiable
Example
a.py
import abc
from b import Mixin # pytype: disable=import-error
class Base(abc.ABC):
@abc.abstractmethod
def foo(self):
raise NotImplementedError
class Derived(Mixin, Base):
pass
x = Derived()
print(x.foo())
b.py
class Mixin:
def foo(self):
return 'Mixin foo'
Expected Behavior
I run pytype-single a.py
, simulating a third-party import that pytype cannot resolve to a specific type (so Mixin is typed as Any). There should be no error when Derived is instantiated. I expect this because pytype "allows all operations that succeed at runtime and don't contradict annotations".
Actual Behavior
$ python a.py
Mixin foo
$ pytype-single a.py
File "a.py", line 13, in <module>: Can't instantiate Derived with abstract methods foo [not-instantiable]
For more details, see https://google.github.io/pytype/errors.html#not-instantiable
This is the generated .pyi:
import abc
from typing import Any
Mixin: Any
x: Derived
class Base(abc.ABC):
@abstractmethod
def foo(self) -> Any: ...
class Derived(Any, Base): ...
There is no error if the body of Base is replaced with "pass", so pytype appears to recognize that it cannot know whether foo is implemented when no abstract methods are involved.
Software Versions
pytype 2022.03.08 Python 3.9.9
I'm a little conflicted on this one. On one hand, it's true that Mixin
may implement Base.foo
, so reporting not-instantiable is perhaps overly strict. On the other hand, a class sometimes has Any
in its MRO for unrelated reasons, and pytype suddenly failing to detect not-instantiable errors because an unrelated Any
crept in also seems like somewhat unfriendly behavior.
There is no error if the body of Base is replaced with "pass", so pytype appears to recognize that it cannot know whether foo is implemented when no abstract methods are involved.
FYI the reason pytype allows this is that Python itself allows abstract base classes to be instantiated if they have no abstract methods.
@rchen152 My point about the contents of Base making a difference is that pytype already gives the program the benefit of the doubt for attribute-error, because Any is in MRO, but not for not-instantiable, despite Any coming before the abstract class in the MRO. pytype brands itself as lenient, but it is currently stricter than mypy in this case - even with --strict, mypy will complain about subclassing Any but won't report "Cannot instantiate abstract class".
My point about the contents of Base making a difference is that pytype already gives the program the benefit of the doubt for attribute-error, because Any is in MRO, but not for not-instantiable, despite Any coming before the abstract class in the MRO.
Ah ok, I see your point. I'm still a little iffy on this, because we do sometimes get bug reports about the attribute-error behavior, too. The problem is that when the Any
in the MRO is unexpected, it's an unpleasant surprise when obvious errors are missed.
pytype brands itself as lenient, but it is currently stricter than mypy in this case - even with --strict, mypy will complain about subclassing Any but won't report "Cannot instantiate abstract class".
I don't want to spend too much time on this, since I don't think it'll be a productive avenue of discussion, but since it's come up a second time: I'm perfectly aware of the contents of our README without it being quoted to me. It's an explanation of pytype's general philosophy, not a legal contract.