Whereas with Protocols it's gonna be ( good tutorial ): I think that is not a good example of how to write programs. What he did by having protocols I would have done by using mixins. The way that I see objects is that they have various capabilities that can be mixed in. multiple inheritance in python would have been a much better way to implement that example in my opinion. I would also say that the author of this tutorial needs to learn a thing or 2 about an inversion of control and dependency injection. The author basically sets up a straw man problem and then solves his straw man problem. He had no business creating instances of the object outside of the class itself. If he had simply called a constructor methods within the classes then the other class wouldn't have been attempting to make instances of those other classes. Answer from thedeepself on reddit.com
🌐
Reddit
reddit.com › r/python › interfaces with protocols: why not ditch abc for good?
r/Python on Reddit: Interfaces with Protocols: why not ditch ABC for good?
January 22, 2023 -

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:
       pass

Whereas 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 :)

CapabilityABCProtocols
Runtime checking11 (with a decorator)
Static checking with mypy11
Explicit interface (class Dog(Animal):)11
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 interface01
Number of code lines-1 (requires ABC inheritance and abstracmethod for every method)0 (optionalProtocol inheritance)
Total score1.54

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.

🌐
Medium
medium.com › @pouyahallaj › introduction-1616b3a4a637
Python Protocols vs. ABCs: A Comprehensive Comparison of Interface Design | Medium
May 29, 2023 - ABCs, on the other hand, enforce the implementation of the area method by subclasses and provide a structured way to define a common interface. Choosing between Python Protocols ...
Discussions

python - Are abstract base classes redundant since Protocol classes were introduced? - Stack Overflow
I'm learning how to use Protocol classes that have been introduced in Python 3.8 (PEP 544). So typing.Protocol classes are subclasses from ABCMeta and they are treated just like abstract classes ar... More on stackoverflow.com
🌐 stackoverflow.com
Variance of arguments for Generic ABC vs Generic Protocol
I am trying to define a Generic base class, but see different type checking behavior when I inherit from abc.ABC versus Protocol. I am trying to figure out if it is really a difference of variance ... More on github.com
🌐 github.com
1
1
July 9, 2024
Python typing how to apply ABC or Protocol to multiple mixins - Stack Overflow
I am trying to extend a certain class, called a ModelSerializer from Django Rest Framework, but the exact class in not important. The following example code is to illustrates what I am trying to do... More on stackoverflow.com
🌐 stackoverflow.com
Python Interfaces: Choose Protocols Over ABC
Protocols and abstract classes are not interchangeable. You cannot simply choose one over the other. There is a substantial difference: abstract class ensures that an implementation meets certain requirements when a subclass is declared, while protocol checks if an instance meets certain requirements when it's being used. They are different in the same way as "They are of the same kind" and "There are things that both of them can do". Please, don't confuse people with such articles. More on reddit.com
🌐 r/Python
20
64
February 12, 2023
🌐
Justin A. Ellis
jellis18.github.io › post › 2022-01-11-abc-vs-protocol
Abstract Base Classes and Protocols: What Are They? When To Use Them?? Lets Find Out! - Justin A. Ellis
January 11, 2022 - Use ABCs if you will need several implementations of a class with several methods. Use Protocols for strict type annotations (i.e.only annotate the methods/attributes you need) ... Well, thats it for this time. Now go forth into our bold almost statically typed python future with confidence!
🌐
Sinavski
sinavski.com › home › interfaces abc vs. protocols
Interfaces: abc vs. Protocols - Oleg Sinavski
August 1, 2021 - There are many great articles on structural typing in Python (this, that and some discussion). Maybe protocols and interfaces are theoretically different beasts, but a protocol does the job. I had great success replacing abc with Protocols without any downsides.
🌐
Python
typing.python.org › en › latest › spec › protocol.html
Protocols — typing documentation
The semantics of @abstractmethod is not changed; all of them must be implemented by an explicit subclass before it can be instantiated. The general philosophy is that protocols are mostly like regular ABCs, but a static type checker will handle them specially.
🌐
Medium
medium.com › @kandemirozenc › understanding-interfaces-abc-protocol-and-duck-typing-in-python-866ca32ab2a0
Understanding Interfaces, ABC, Protocol and Duck Typing in Python | by kandemirozenc | Medium
December 7, 2024 - Introduced in Python 3.8, Protocol provides a lightweight and flexible way to define expected behaviors. Unlike abc, Protocols do not enforce method implementation at runtime. Instead, they are primarily used for static type checking.
🌐
Mypy
mypy.readthedocs.io › en › stable › protocols.html
Protocols and structural subtyping - mypy 1.19.1 documentation
In Python 3.9 and later, the aliases in typing don’t provide any extra functionality. You can define your own protocol class by inheriting the special Protocol class: from collections.abc import Iterable from typing import Protocol class SupportsClose(Protocol): # Empty method body (explicit '...') def close(self) -> None: ...
Find elsewhere
🌐
GitConnected
levelup.gitconnected.com › python-interfaces-choose-protocols-over-abc-3982e112342e
Python interfaces: abandon ABC and switch to Protocols | by Oleg Sinavski | Level Up Coding
January 19, 2023 - A protocol is a formalization of Python’s “duck-typing” ideology. There are many great articles on structural typing in Python (for example, see this tutorial). Protocols and interfaces are different beasts in theory, but a protocol does the job. I had great success replacing abc with Protocols without any downsides.
🌐
GitHub
github.com › python › typing › discussions › 1793
Variance of arguments for Generic ABC vs Generic Protocol · python/typing · Discussion #1793
July 9, 2024 - Comparing BadProtocolResultWriter and ABCResultWriter, do Generic ABCs just not get typed checked for variance? Or is there something about an ABC that restricts the variance of subclasses? import abc from typing import Generic, Protocol, TypedDict, TypeVar class BaseResult(TypedDict): """ Result """ R = TypeVar("R", bound=BaseResult) R_contra = TypeVar("R_contra", bound=BaseResult, contravariant=True) class BadProtocolResultWriter(Protocol[R]): """ Type variable "R" used in generic protocol "ProtocolResultWriter" should be contravariant (reportInvalidTypeVarUse) main.py:16: error: Invariant t
Author   python
Top answer
1 of 1
4

There are a few issues with the code you showed. I tried to go through those that I thought were most pressing in no particular order.

Avoid nested ABCs if possible

Since AbstractSerializer will be the abstract base class for your custom serializers, I would suggest defining the abstract methods like get_da_name on that class directly instead of having them in another, separate ABC like ThingsToImplement.

It makes the intent clearer because users of that AbstractSerializer will look at it and immediately see the work they will have to do.

The attributes that need to be present on every serializer subclass like constraints don't technically need to be declared on the ABC, but I think it makes sense for the same reason.

The purpose of Protocols

I would argue that the one of the main purposes of Protocol is to simplify doing exactly the things you are doing here. You define common behavior in a protocol that static type checkers can assume is available on a variable annotated with that Protocol.

In your specific case, it is up to you how finely grained your Protocol subclasses should be. If you want to be very pedantic, any Mix-in can have its own corresponding Protocol, but I would argue that is overkill most of the time. It really depends on how complex that "common behavior" becomes, which the Protocol is supposed to encapsulate.

In your example code I would only define one Protocol. (see below)

In addition, Protocol can be used in a generic way, which IMHO fits perfectly into the model serializer context since every serializer will have his instance set as can be seen in the type stubs for ModelSerializer (inheriting from BaseSerializer), which is also generic over a Model-bound type variable.

Allow ABCs to inherit from ConstraintsMixin

Since you set up your __init_subclass__ class method on ConstraintsMixin so strictly, you need to ensure that the actual ABC you want to create (i.e. AbstractSerializer) can inherit from it without triggering the error.

For this you simply add the ABCMeta check to __init_subclass__ first and avoid triggering the error on ABCs.

Use MySerializerProtocol in Mix-ins

Since your Mix-ins assume certain behavior in their instance methods, that is exactly where you can use MySerializerProtocol to annotate the self parameter.

Again, you may consider splitting the Protocol up further, if it gets too complex.

Solve the Metaclass conflict

Luckily, this is very easy in this case, since there are only two non-type Metaclasses involved here, namely the SerializerMetaclass from Django REST Framework and the ABCMeta from abc, and they don't actually conflict as far as I can see. You just need to define your own Metaclass that inherits from both and specify it in your serializer ABC.

Specify Django Model in subclasses

If you go the generic route (which seems more consistent to me), you should specify the concrete Django Model handled by the serializer, when you subclass AbstractSerializer.

If you don't want to go that route, mypy will complain in --strict mode upon subclassing ModelSerializer (that it is missing a type argument), but you can silence that. Also, you can omit the [M] everywhere in the code (see below) and instead just declare instance: Model on MySerializerProtocol.

Fully annotated example code

from abc import ABC, ABCMeta, abstractmethod
from dataclasses import dataclass
from typing import Any, Protocol, TypeVar

from django.db.models import Model
from rest_framework.serializers import ModelSerializer, SerializerMetaclass


M = TypeVar("M", bound=Model)


# Placeholder for a model to be imported from another module:
class ConcreteDjangoModel(Model):
    pass


@dataclass
class Constraints:
    width: int
    height: int


class MySerializerProtocol(Protocol[M]):
    """For type annotations only; generic over `M` like `ModelSerializer`"""
    my_number: int
    constraints: Constraints
    # From ModelSerializer:
    instance: M

    # From AbstractSerializer:
    def get_da_name(self, s: str) -> str: ...
    # From FooMixin:
    def get_foo(self) -> str: ...
    # From AnotherMixin:
    def get_number(self) -> int: ...
    # From ModelSerializer:
    def to_representation(self, instance: M) -> Any: ...


class ConstraintsMixin:
    # Class attributes that must be set by subclasses:
    constraints: Constraints

    def __init_subclass__(cls, **kwargs: Any) -> None:
        if not isinstance(cls, ABCMeta) and not hasattr(cls, "constraints"):
            raise NotImplementedError("Please add a constraints attribute")
        super().__init_subclass__(**kwargs)

    @classmethod
    def print_constraints(cls: type[MySerializerProtocol[M]]) -> None:
        print(cls.constraints.width, cls.constraints.height)


class FooMixin:
    def get_foo(self: MySerializerProtocol[M]) -> str:
        s = "something"
        return self.get_da_name(s) if self.constraints.width > 123 else "Too small to be named"

    def get_bar(self: MySerializerProtocol[M]) -> Any:
        return self.to_representation(self.instance)

    def from_another(self: MySerializerProtocol[M]) -> str:
        return f"from {self.get_number()}"


class AnotherMixin:
    def get_number(self: MySerializerProtocol[M]) -> int:
        return self.my_number

    def from_foo(self: MySerializerProtocol[M]) -> str:
        return f"from {self.get_foo()}"


class AbstractSerializerMeta(SerializerMetaclass, ABCMeta):
    """To avoid metaclass conflicts in `AbstractSerializer`"""
    pass


class AbstractSerializer(
    ABC,
    ConstraintsMixin,
    FooMixin,
    AnotherMixin,
    ModelSerializer[M],
    metaclass=AbstractSerializerMeta,
):
    # Class attributes that must be set by subclasses:
    constraints: Constraints

    @abstractmethod
    def get_da_name(self, s: str) -> str: ...


class MySerializer(AbstractSerializer[ConcreteDjangoModel]):
    my_number: int = 7
    constraints: Constraints = Constraints(1, 2)

    def get_da_name(self, s: str) -> str:
        self.my_number += 1
        return f"hi {s}"

If you have an older Python version (below 3.9 I think), you may need to replace type[MySerializerProtocol[M]] with typing.Type[MySerializerProtocol[M]] in the print_constraints method.


Thanks for the fun little exercise. Hope this helps.

Feel free to comment, if something is unclear. I will try to amend my answer if necessary.

🌐
Pybites
pybit.es › articles › typing-protocol-abc-alternative
Leveraging typing.Protocol: Faster Error Detection And Beyond Inheritance – Pybites
February 9, 2024 - It’s a great resource for those seeking to deepen their understanding of Python’s type hints and how to use them effectively to write robust, error-resistant code. Adopting typing.Protocol in the PyBites Search feature showcased not just an alternative to ABCs and abstract methods, but also illuminated a path toward more proactive error management and a deeper alignment with Python’s dynamic and flexible design ethos.
🌐
YouTube
youtube.com › arjancodes
Protocols vs ABCs in Python - When to Use Which One? - YouTube
💡 Learn how to design great software in 7 steps: https://arjan.codes/designguide.In this video, I’m revisiting Protocols and ABCs in Python, essential for c...
Published   March 29, 2024
Views   41K
🌐
YouTube
youtube.com › arjancodes
Protocol Or ABC In Python - When to Use Which One? - YouTube
💡 Learn how to design great software in 7 steps: https://arjan.codes/designguide.When should you use protocol classes vs abstract base classes? Here's an ex...
Published   October 29, 2021
Views   204K
🌐
Reddit
reddit.com › r/python › python interfaces: choose protocols over abc
r/Python on Reddit: Python Interfaces: Choose Protocols Over ABC
February 12, 2023 - When is a protocol "used?" The article assumes that you are using a type checker and that the code passes the type checker. If it does then whether or not you use the ABC or the protocol seems irrelevant, both assure you that the required methods exist where you have annotated that a particular object is either ABC or protocol.
🌐
how.wtf
how.wtf › abc-vs-protocol-in-python.html
ABC vs Protocol in Python | how.wtf
December 16, 2023 - Before typing was released for Python, the ABC class reigned as champion for describing shape and behaviors of classes. After type annotations, ABC and @abstractmethod were still used to describe the behaviors: they felt ‘interface-like’. Then, Protocol was released and introduced a new way for declaring class behaviors.
🌐
Real Python
realpython.com › python-protocol
Python Protocols: Leveraging Structural Subtyping – Real Python
July 25, 2024 - The type hint for the shape argument will work correctly because Circle and Square are subclasses of Shape. In this example, you’ve used an ABC and inheritance to enforce a specific interface in a group of classes. However, sometimes it’s desirable to have a similar result without inheritance. That’s when you can use a protocol:
🌐
Python
peps.python.org › pep-0544
PEP 544 – Protocols: Structural subtyping (static duck typing) | peps.python.org
March 5, 2017 - The semantics of @abstractmethod ... be instantiated. The general philosophy is that protocols are mostly like regular ABCs, but a static type checker will handle them specially....
Top answer
1 of 8
65

New in Python 3.8:

Some of the benefits of interfaces and protocols are type hinting during the development process using tools built into IDEs and static type analysis for detection of errors before runtime. This way, a static analysis tool can tell you when you check your code if you're trying to access any members that are not defined on an object, instead of only finding out at runtime.

The typing.Protocol class was added to Python 3.8 as a mechanism for "structural subtyping." The power behind this is that it can be used as an implicit base class. That is, any class that has members that match the Protocol's defined members is considered to be a subclass of it for purposes of static type analysis.

The basic example given in PEP 544 shows how this can be used.

Copyfrom typing import Protocol

class SupportsClose(Protocol):
    def close(self) -> None:
        # ...

class Resource:
    # ...
    def close(self) -> None:
        self.file.close()
        self.lock.release()

def close_all(things: Iterable[SupportsClose]) -> None:
    for thing in things:
        thing.close()

file = open('foo.txt')
resource = Resource()
close_all([file, resource])  # OK!
close_all([1])     # Error: 'int' has no 'close' method

Note: The typing-extensions package backports typing.Protocol for Python 3.5+.

2 of 8
21

In short, you probably don't need to worry about it at all. Since Python uses duck typing - see also the Wikipedia article for a broader definition - if an object has the right methods, it will simply work, otherwise exceptions will be raised.

You could possibly have a Piece base class with some methods throwing NotImplementedError to indicate they need to be re-implemented:

Copyclass Piece(object):

    def move(<args>):
        raise NotImplementedError(optional_error_message) 

class Queen(Piece):

    def move(<args>):
        # Specific implementation for the Queen's movements

# Calling Queen().move(<args>) will work as intended but 

class Knight(Piece):
    pass

# Knight().move() will raise a NotImplementedError

Alternatively, you could explicitly validate an object you receive to make sure it has all the right methods, or that it is a subclass of Piece by using isinstance or isubclass. Note that checking the type may not be considered "Pythonic" by some and using the NotImplementedError approach or the abc module - as mentioned in this very good answer - could be preferable.

Your factory just has to produce instances of objects having the right methods on them.

🌐
Andrewbrookins
andrewbrookins.com › technology › building-implicit-interfaces-in-python-with-protocol-classes
Building Implicit Interfaces in Python with Protocol Classes – Andrew Brookins
July 5, 2020 - If you wanted to hook into a protocol, you could use an ABC as documentation – but you couldn’t use mypy to check that an object implemented the protocol unless the object subclassed one of these ABCs. That goes against the nature of protocols because they’ve always relied on duck typing, or the ability of a Python program to check the behavior of an object – through its attributes and methods – rather than its type.