Search code examples
pythonpython-typingpyright

Why does Pylance not complain about missing members of a Protocol?


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.

  1. I want to document that Bar implements the Foo protocol
  2. 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:

enter image description here


Solution

  • 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