Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I use Mypy stubs as interfaces?

Tags:

python

mypy

Mypy allows us to write class stubs which can be placed in the same directory as the actual class. This stub is very similar to an interface as known from other languages. Is it possible to have a client use the stub and the implementation strictly follow the stub?

Example I would like to work:

class IDependency:
  def do_something(self) -> None: ...
  def do_something_else(self) -> None: ...

class Service:
  def __init__(self, dependency: IDependency):
    dependency.do_something()
    dependency.do_something_else() # this fails silently

class DependencyImplementation(IDependency):
  def do_something(self) -> None:
    print("doing something")

  # Note there is no `do_something_else` here.

This works. However, if DependencyImplementation doesn't implement the do_something method, there is no error from Mypy and no error from Python itself. The call just doesn't do anything. Do I have to write raise NotImplementedException() or annotate each method with @abc.abstractmethod for this to work? Is there some special flags in Mypy or the Python interpreter?

Is this a use case for Mypy Protocols? It seems to be coming soon (maybe Python 4?)

like image 675
Hubert Grzeskowiak Avatar asked Oct 19 '25 12:10

Hubert Grzeskowiak


1 Answers

This is indeed something you can do using either @abc.abstractmethod or protocols. The former is akin to using Java's abstract classes; the latter will be kin to using Go's interfaces or Rust traits.

Here is an example that uses ABCs:

from abc import abstractmethod

class Parent:
    @abstractmethod
    def foo(self) -> None: ...

# Missing an implementation for 'foo'!
class Child(Parent): pass

print(Child())  # Error: Cannot instantiate abstract class 'Child' with abstract attribute 'foo'

A few things to note about this example:

  1. You get an error on the instantiation of the Child class, not on the declaration. This is to support the use case where you never instantiate Child, but instead subclass it again and define foo in that second subclass.
  2. We don't need to add the usual abc metaclass to Parent ( e.g. class Parent(metaclass=ABCMeta)): mypy will understand what @abc.abstractmethod means with or without it. Include the metaclass only if you want the Python interpreter to also enforce that you've correctly overridden anything marked as being abstract at runtime.
  3. ABCs are not quite interfaces -- you can still define fields and non-abstract methods. They're more akin to Java-style abstract classes.

You can also use protocols, though for now you'll need to first pip install typing_extensions to use it. Here's an example:

from typing_extensions import Protocol

class CanFoo(Protocol):
    def foo(self) -> None: ...

class Child: pass

def expects_fooable(x: CanFoo) -> None: ...

x = Child()
expects_fooable(x)  # Error: Argument 1 to "expects_fooable" has incompatible type "Child"; expected "CanFoo"

A few notes:

  1. Here, Child deliberately does not inherit from CanFoo: there is no explicit link between a class and the protocol(s) it implements: protocols are very similar to Go-style interfaces and can be more ad-hoc. Contrast this to languages like Java, where you do need to include a "implements Blah" in the class definition.
  2. Unlike the previous error, we do not get an error on the instantiation of Child: there's nothing inherently wrong with it. Rather, we get an exception when we try using it improperly.

A few final notes:

  1. Stub files may superficially look like interfaces, but they're really not: they're more just a way to bring types to code which we cannot easily modify and add type hints to. You can think of them being vaguely similar to C-style header files: it's a way of storing the signatures of existing objects independently from the source code.
  2. "Typeshed" is the name of a specific project which includes stubs for the standard library and a few popular 3rd party modules. That word is not a synonym for "stub files". Similarly, the term "class stub" is also a bit of a misnomer: there are only stub files, which may or may not contain definitions for classes. (If the original Python or C extension library you're trying to type contains only functions, the corresponding stub file would also likely only contain signatures for those functions.)
like image 120
Michael0x2a Avatar answered Oct 22 '25 07:10

Michael0x2a



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!