Videos
Hello, if one finds interfaces useful in Python (>=3.8) and is convinced that static type-checking is a must, then why not ditch ABC and always use Protocols? I understand that the fundamental idea of a protocol is slightly different from an interface, but in practice, I had great success replacing abc's with Protocols without regrets.
With abc you would write (https://docs.python.org/3/library/abc.html) :
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def eat(self, food) -> float:
passWhereas with Protocols it's gonna be (good tutorial):
from typing import Protocol
class Animal(Protocol):
def eat(self, food) -> float:
...Scores in my subjective scoring system :)
| Capability | ABC | Protocols |
|---|---|---|
| Runtime checking | 1 | 1 (with a decorator) |
| Static checking with mypy | 1 | 1 |
Explicit interface (class Dog(Animal):) | 1 | 1 |
Implicit interface with duck-typing (class Dog:) | 0.5 (kind of with register, but it doesn't work with mypy yet) | 1 |
Default method implementation (def f(self): return 5) | -1 (implementations shouldn't be in the interfaces) | -1 (same, and mypy doesn't catch this) |
| Callback interface | 0 | 1 |
| Number of code lines | -1 (requires ABC inheritance and abstracmethod for every method) | 0 (optionalProtocol inheritance) |
| Total score | 1.5 | 4 |
So I do not quite see why one should ever use ABC except for legacy reasons. Other (IMHO minor) points in favour of ABC I've seen were about interactions with code editors.
Did I miss anything?
I put more detailed arguments into a Medium. There are many tutorials on using Protocols, but not many on ABC vs Protocols comparisons. I found a battle of Protocols vs Zope, but we are not using Zope, so it's not so relevant.
I've used this before in cases where it was possible to have the concrete implementation, but I wanted to force subclass implementers to consider if that implementation is appropriate for them.
One specific example: I was implementing an abstract base class with an abstract factory method so subclasses can define their own __init__ function but have a common interface to create them. It was something like
class Foo(ABC):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
@classmethod
@abstractmethod
def from_args(cls, a, b, c) -> "Foo":
return cls(a, b, c)
Subclasses usually only need a subset of the arguments. When testing, it's cumbersome to access the framework which actually uses this factory method, so it's likely someone will forget to implement the from_args factory function since it wouldn't come up in their usual testing. Making it an abstractmethod would make it impossible to initialize the class without first implementing it, and will definitely come up during normal testing.
The short answer is: Yes.
As described in the Python Documentation of abc:
The abstract methods can be called using any of the normal ‘super’ call mechanisms
PEP3119 also discussed this behavior, and explained it can be useful in the super-call:
Unlike Java’s abstract methods or C++’s pure abstract methods, abstract methods as defined here may have an implementation. This implementation can be called via the super mechanism from the class that overrides it. This could be useful as an end-point for a super-call in framework using cooperative multiple-inheritance [7], [8].
Actually, what the @abstractmethod decorator all do is setting the function's __isabstractmethod__ attribute to True (you can see the implementation code here). And the cooperation of both ABC and @abstractmethod can prevent the instantiation of an abstract class.
So, you can do whatever a normal method can do in an abstract method.