I am using Pylance with vscode and have set the type checking mode to ‘strict’
My understanding is that Pylance should flag Bar as not conforming to the Foo Protocol because of the commented foo method in Bar, but it does not.
I if uncomment the line it does correctly complain that ‘Method “foo” overrides class “Foo” in an incompatible manner’
Is there something I am missing?
from typing import Protocol
class Foo(Protocol):
def foo(self, a: str): ...
class Bar(Foo):
# def foo(self, b: str): ...
...
# The above code generates no errors, however the following line does
a: Foo = Bar() # this generates a 'Cannot instantiate abstract class "Bar"' error in pylance
# but the file does run cleanly
The bottom line here is all I want to do is say that Bar implements Foo and have Pylance tell me if it does not, before I let someone try to create an instance of it.
I am subclassing Foo for 2 reasons.
- I want to document that Bar implements the Foo protocol
- I want to type check Bar where it is defined rather than when it is instantiated.
I am basing this on part of mypy documentation found here
As I said above, this works fine and flags incorrect method signatures right here in the file defining the Bar class.
The only thing it does not do is flag the missing method.
For that I can add something like the following:
bar: Foo = Bar()
This will highlight the missing method (because the Protocol makes Bar abstract), however it seems hacky to include this in the file defining Bar and also gets complicated if Bar has an __init__
function that takes several argument.
portion of mypy documentation linked above:
4
@larsks’s answer is partly incorrect: Inheriting a Protocol
means to either implement or extend it, depending on whether Protocol
itself is used as an explicit base class and whether the abstract members have default implementations.
As for implementing:
To explicitly declare that a certain class implements a given protocol, it can be used as a regular base class. In this case a class could use default implementations of protocol members.
— Protocols § Explicitly declaring implementation | Python typing spec
This does not apply to your example, however, since Foo.foo()
does not have a default implementation.
As for extending:
The general philosophy is that protocols are mostly like regular ABCs, but a static type checker will handle them specially. Subclassing a protocol class would not turn the subclass into a protocol unless it also has
typing.Protocol
as an explicit base class. Without this base, the class is “downgraded” to a regular ABC that cannot be used with structural subtyping.— Protocols § Merging and extending protocols | Python typing spec
In other words, neither your Bar
has Protocol
as one of its explicit base classes, nor does it implement the .foo()
method, so it is considered an abstract class.
You can verify this by trying to instantiate it:
(playgrounds: Mypy, Pyright)
Bar() # error: Cannot instantiate abstract class "Bar"
I found the solution I was looking for here.
Importing final
from typing and decorating the class that I expect to implement the full protocol with @final
does flag the missing method.
from typing import Protocol, final
class Foo(Protocol):
def foo(self, a: str): ...
@final
class Bar(Foo): #pylance error here
# def foo(self, b: str): ...
...
If I ever actually want to subclass Bar I can just ignore that type error.
class FooBar(Bar): # type: ignore - Bar is only marked final to ensure all protocol members are implemented
pass