Implementing an Interface in Python

Implementing Interfaces in Python: ABCs and Protocols

by Leodanis Pozo Ramos Updated Reading time estimate 19m advanced python

In object-oriented programming (OOP), an interface describes the methods a class must implement to play a specific role. This set of methods expresses the interface contract. Python has no dedicated syntax for interfaces, but it gives you two mechanisms for modeling them: abstract base classes and protocols. Python’s everyday duck typing takes advantage of both.

By the end of this tutorial, you’ll understand that:

  • abc.ABC combined with @abstractmethod enforces the contract at instantiation time, raising a TypeError when a subclass is missing required methods.
  • typing.Protocol defines a structural interface that classes satisfy without inheriting from the protocol, verified by static type checkers.
  • Duck typing treats any object exposing the expected methods as valid, leveraging Python’s dynamic nature.
  • isinstance() and issubclass() verify contracts at runtime through ABC inheritance, virtual subclass registration, or @runtime_checkable protocols.

In this tutorial, you’ll write code examples that model interfaces with ABCs and protocols, rely on duck typing for behavior-based contracts, and run explicit isinstance() and issubclass() checks when you need formal verification at runtime.

Take the Quiz: Test your knowledge with our interactive “Implementing Interfaces in Python: ABCs and Protocols” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Implementing Interfaces in Python: ABCs and Protocols

Check your understanding of Python interfaces with abstract base classes, protocols, and duck typing, and how to enforce method contracts.

Python’s Approach to Interfaces

In object-oriented programming, an interface acts as a blueprint for implementing concrete classes. Interfaces define methods that are typically abstract, which means that the interface declares them, but doesn’t implement them.

The implementation is the job of any class that implements the interface. By prescribing a set of methods, interfaces let you write code that works with many different types without caring about their concrete implementations.

Python doesn’t have an interface keyword like Java or Go, or a close equivalent like C++’s pure virtual classes. Instead, it offers two techniques for modeling interfaces:

  1. Inheritance-based interfaces with abstract base classes (ABCs)
  2. Structural subtyping interfaces with protocols

Python’s approach is just different. Where Java, Go, or C#—each with a dedicated interface keyword—require a class to explicitly declare which interface it implements, Python relies on inheritance or structural matching.

Inheritance-Based Interfaces With Abstract Base Classes (ABCs)

The abc module in Python’s standard library gives you several tools for working with abstract base classes (ABCs) and modeling inheritance-based interfaces. ABCs were Python’s first formal interface mechanism, and they’re built on inheritance. A concrete class declares its intent to satisfy an interface by subclassing the corresponding ABC.

ABCs enforce the interface contract. Any subclass that’s missing a required method fails with a TypeError the moment you try to instantiate it. This makes ABCs a solid choice when you control the hierarchy and want to strictly enforce a specific interface at instantiation time.

Defining an ABC-Based Interface

An ABC declares abstract methods using the @abstractmethod decorator from the abc module. Any subclass must implement every abstract method. Otherwise, Python raises an error the moment you try to instantiate the subclass.

Say you need an interface for file readers. Readers for different formats like PDFs and emails will require the same methods but with different internal logic. The shared contract is to load a file from a path and return the extracted text.

Here’s that interface modeled as an ABC:

Language: Python Filename: readers_abc.py
from abc import ABC, abstractmethod

class FileReaderInterface(ABC):
    """Interface for file readers."""

    @abstractmethod
    def load_file(self, path: str) -> None:
        """Load a file for text extraction."""

    @abstractmethod
    def extract_text(self) -> str:
        """Return text extracted from the loaded file."""

A few things to notice in this code snippet:

Now, create two concrete classes, PdfReader and EmailReader, which inherit from FileReaderInterface:

Language: Python Filename: readers_abc.py
# ...

class PdfReader(FileReaderInterface):
    """Extract text from a PDF."""

    def load_file(self, path: str) -> None:
        """Load a PDF file for text extraction."""
        print(f"Loading PDF from {path}")

    def extract_text(self) -> str:
        """Return text extracted from the loaded PDF."""
        return "Extracted PDF text"

class EmailReader(FileReaderInterface):
    """Extract text from an Email."""

    def load_file(self, path: str) -> None:
        """Load an EML file for text extraction."""
        print(f"Loading email from {path}")

    def extract_email_text(self) -> str:
        """Return text extracted from the loaded email."""
        return "Extracted email text"

PdfReader implements both abstract methods, so it satisfies the interface. EmailReader defines .extract_email_text() instead of the required .extract_text(), so it doesn’t satisfy the contract.

Watch what happens when you try to instantiate these classes:

Language: Python
>>> from readers_abc import PdfReader, EmailReader

>>> pdf_reader = PdfReader()
>>> email_reader = EmailReader()
Traceback (most recent call last):
  ...
TypeError: Can't instantiate abstract class EmailReader
⮑ without an implementation for abstract method 'extract_text'

You can instantiate PdfReader, but you get a TypeError when you try to instantiate EmailReader because this class doesn’t implement the expected method, .extract_text(). That’s the value of using abc.ABC: forgetting a method isn’t a runtime surprise buried deep in your code. The failure happens at instantiation, before anything else runs.

To fix the error, rename .extract_email_text() to .extract_text() in EmailReader, and you’ll be able to instantiate it. Go ahead and give it a try.

Interfaces appear in many programming languages, and their implementation varies greatly from language to language. Here’s a quick look at Java, C++, and Go interfaces.

Java

Unlike Python, Java has an interface keyword, and concrete classes use the implements keyword to declare that they satisfy a given interface. Here’s the FileReaderInterface in Java:

Language: Java Filename: Main.java
interface FileReaderInterface {
    void loadFile(String path);
    String extractText();
}

class PdfReader implements FileReaderInterface {
    public void loadFile(String path) {
        // Code to load the file
    }

    public String extractText() {
        // Code to extract the text
    }
}

class EmailReader implements FileReaderInterface {
    public void loadFile(String path) {
        // Code to load the file
    }

    public String extractText() {
        // Code to extract the text
    }
}

To declare the interface, you use the interface keyword. Then, you use the implements keyword to explicitly declare that a given class implements the specified interface.

C++

Like Python, C++ uses abstract base classes to model interfaces. You mark methods as pure virtual by adding = 0 to signal that concrete classes must override them, and you implement the interface by following the class name with a colon (:), an access specifier like public, and the interface name:

Language: C++ Filename: main.cpp
class FileReaderInterface {
    public:
        virtual void loadFile(std::string path) = 0;
        virtual std::string extractText() = 0;
        virtual ~FileReaderInterface() = default;
};

class PdfReader : public FileReaderInterface {
    public:
        void loadFile(std::string path) override;
        std::string extractText() override;
};

class EmailReader : public FileReaderInterface {
    public:
        void loadFile(std::string path) override;
        std::string extractText() override;
};

Python and C++ are similar in that they both use abstract base classes to model interfaces.

Go

Go also has an interface keyword, like Java. A big difference is that Go doesn’t have classes. Instead, it uses the struct keyword to create structures that pair data with methods. Concrete structs can satisfy one or more interfaces:

Language: Go Filename: main.go
package main

type fileReaderInterface interface {
    loadFile(path string)
    extractText() string
}

type pdfReader struct {
    // Data goes here ...
}

func (p pdfReader) loadFile(path string) {
    // Method definition ...
}

func (p pdfReader) extractText() string {
    // Method definition ...
}

type emailReader struct {
    // Data goes here ...
}

func (e emailReader) loadFile(path string) {
    // Method definition ...
}

func (e emailReader) extractText() string {
    // Method definition ...
}

Unlike in Python, you create a Go interface using the explicit interface keyword. Then, you implement the interface on concrete structs.

Type-Checking ABC Subclasses

Static type checkers verify ABC subclass relationships through inheritance with no extra setup. At runtime, you can explicitly type-check an ABC subclass using the built-in isinstance() and issubclass() functions:

Language: Python
>>> from readers_abc import FileReaderInterface, PdfReader

>>> isinstance(PdfReader(), FileReaderInterface)
True

>>> issubclass(PdfReader, FileReaderInterface)
True

Both functions return True because PdfReader has a direct inheritance relationship with FileReaderInterface.

Registering Virtual Subclasses

Sometimes, you want a Python class to satisfy your interface without actually inheriting from it. Maybe the class comes from a third-party library you can’t modify, or maybe you want to document a structural relationship without changing the inheritance chain. The abc module supports this with virtual subclass registration.

Stepping outside the file-reader example for a moment, here’s how you register the built-in float class as a virtual subclass of a Double interface:

Language: Python Filename: virtual_subclass.py
from abc import ABC

class Double(ABC):
    """Double precision floating point number."""

Double.register(float)

Now both issubclass() and isinstance() treat float as a Double subclass:

Language: Python
>>> from virtual_subclass import Double

>>> issubclass(float, Double)
True
>>> isinstance(1.2345, Double)
True

You can also use .register() as a class decorator:

Language: Python
>>> @Double.register
... class Double64:
...     """A 64-bit double-precision floating-point number."""
...

>>> issubclass(Double64, Double)
True

Use registration sparingly, though. Virtual subclasses don’t enforce any method requirements. They just declare a relationship. For shape-based matching that actually inspects methods, reach for typing.Protocol, which you’ll learn about next.

Structural Subtyping Interfaces With Protocols

Introduced in Python 3.8, typing.Protocol brings structural subtyping to the standard library and stays closer to Python’s duck-typing style than ABCs do.

Protocols suggest the contract rather than enforce it. A static type checker can verify the contract before your code runs, but Python itself won’t stop the code from executing if that contract isn’t met.

Unlike ABCs, a concrete class doesn’t have to inherit from the target protocol. If it has the expected methods with the right signatures, then it counts as a class that implements the protocol.

Protocols are often the best fit when you don’t need interface enforcement at runtime, or when the classes involved don’t share a natural inheritance relationship.

Defining a Protocol-Based Interface

Here’s a FileReaderProtocol class that parallels FileReaderInterface:

Language: Python Filename: readers_protocol.py
from typing import Protocol

class FileReaderProtocol(Protocol):
    """Protocol for file readers."""

    def load_file(self, path: str) -> None:
        """Load a file for text extraction."""
        ...

    def extract_text(self) -> str:
        """Return text extracted from the loaded file."""
        ...

In this example, FileReaderProtocol inherits from typing.Protocol instead of from abc.ABC.

You’ll also notice that each method body ends with ... after the docstring, rather than the docstring-only bodies used in the ABC example. That’s a convention difference: ABCs typically rely on docstrings alone to mark abstract method bodies, while protocols often add an ellipsis after the docstring or use the ellipsis on its own.

A concrete class satisfies the protocol purely by implementing the protocol’s methods:

Language: Python Filename: readers_protocol.py
# ...

class PdfReader:
    """Extract text from a PDF."""

    def load_file(self, path: str) -> None:
        """Load a PDF file for text extraction."""
        print(f"Loading PDF from {path}")

    def extract_text(self) -> str:
        """Return text extracted from the loaded PDF."""
        return "Extracted PDF text"

class EmailReader:
    """Extract text from an Email."""

    def load_file(self, path: str) -> None:
        """Load an EML file for text extraction."""
        print(f"Loading email from {path}")

    def extract_text(self) -> str:
        """Return text extracted from the loaded email."""
        return "Extracted email text"

Notice that PdfReader and EmailReader don’t inherit from FileReaderProtocol. However, static type checkers like mypy still recognize them as valid FileReaderProtocol subtypes because they implement the expected methods with the right signatures.

Type-Checking Protocol Subtypes Statically

Consider the following read() function that expects a reader object as an argument:

Language: Python
def read(reader, path: str) -> str:
    reader.load_file(path)
    return reader.extract_text()

read(PdfReader(), "/reports/report.pdf")
read(EmailReader(), "/mail/message.eml")

Without protocols or inheritance, you won’t have an easy way to type-hint the reader argument. If you declare it as a PdfReader, your static type checker will complain about the second call to read() because EmailReader isn’t a PdfReader.

To fix this, use FileReaderProtocol to annotate the reader parameter:

Language: Python
def read(reader: FileReaderProtocol, path: str) -> str:
    reader.load_file(path)
    return reader.extract_text()

# Accepted by the type checker
read(PdfReader(), "/reports/report.pdf")
read(EmailReader(), "/mail/message.eml")

Now, the static type checker verifies that the argument to read() satisfies the FileReaderProtocol protocol by inspecting its methods and signatures rather than by checking for an inheritance relationship. That’s the practical payoff of structural subtyping.

Type-Checking Protocol Subtypes at Runtime

If you need the type check to happen at runtime through isinstance() and issubclass(), then decorate the Protocol class with @runtime_checkable:

Language: Python Filename: readers_protocol.py
from typing import Protocol, runtime_checkable

@runtime_checkable
class FileReaderProtocol(Protocol):
    # ...

With this addition, you can check types with the isinstance() and issubclass() functions:

Language: Python
>>> from readers_protocol import FileReaderProtocol, PdfReader

>>> isinstance(PdfReader(), FileReaderProtocol)
True

>>> issubclass(PdfReader, FileReaderProtocol)
True

Note that runtime-checkable protocols verify only that methods exist, but they don’t validate their signatures. For full signature-level verification at runtime, reach for a third-party runtime type-checking library like beartype or typeguard.

If you want to see protocols at work in more scenarios, Real Python’s Exploring Protocols in Python course walks through structural subtyping, runtime checks, and common protocol patterns step by step.

Duck Typing, ABCs, and Protocols

Python is well known for using duck typing, which is based on the idea: if it walks like a duck and quacks like a duck, then it must be a duck.

Duck typing relies on the contract at runtime. It doesn’t matter whether an object inherits from an ABC, satisfies a Protocol, or just happens to expose the right methods. In a duck typing context, completely unrelated classes can be used interchangeably, as long as they expose the methods the caller expects.

In idiomatic Python, duck typing is generally preferred over explicit isinstance() and issubclass() checks. The principle is EAFP (easier to ask for forgiveness than permission): just call the methods you need, and let Python raise an error if the object doesn’t support them. Explicit type checks add extra steps that often aren’t necessary.

Duck typing keeps your code flexible at the cost of giving up the early signal you’d get from an ABC or a protocol. The clearest way to see that trade-off is to write the same reader classes without either mechanism.

Applying Duck Typing

To see duck typing in action, consider these plain reader classes with a common set of methods:

Language: Python Filename: readers.py
class PdfReader:
    """Extract text from a PDF."""
    def load_file(self, path: str) -> None:
        print(f"Loading PDF from {path}")

    def extract_text(self) -> str:
        return "Extracted PDF text"

class EmailReader:
    """Extract text from an Email."""
    def load_file(self, path: str) -> None:
        print(f"Loading email from {path}")

    def extract_text(self) -> str:
        return "Extracted email text"

def read(reader, path: str) -> str:
    reader.load_file(path)
    return reader.extract_text()

Notice what’s not there: no FileReaderInterface, no FileReaderProtocol, no shared base class. PdfReader and EmailReader have nothing in common type-wise. They just happen to expose the same two methods.

As long as read() gets an object with .load_file() and .extract_text(), it doesn’t care what class that object belongs to:

Language: Python
>>> from readers import PdfReader, EmailReader, read

>>> read(PdfReader(), "/reports/report.pdf")
Loading PDF from /reports/report.pdf
'Extracted PDF text'

>>> read(EmailReader(), "/mail/message.eml")
Loading email from /mail/message.eml
'Extracted email text'

That’s duck typing. The contract is behavior rather than type. Python never checks what reader is, only what it does when you call a method on it.

To see the trade-off in action, rename .extract_text() to .extract_email_text() in EmailReader:

Language: Python Filename: readers.py
# ...

class EmailReader:
    """Extract text from an Email."""
    def load_file(self, path: str) -> None:
        print(f"Loading email from {path}")

    def extract_email_text(self) -> str:
        return "Extracted email text"

# ...

Now read() calls .extract_text() on an object that no longer provides it, and you get an AttributeError. Restart your Python session and run this code snippet:

Language: Python
>>> from readers import EmailReader, read

>>> read(EmailReader(), "/mail/message.eml")
Loading email from /mail/message.eml
Traceback (most recent call last):
    ...
AttributeError: 'EmailReader' object has no attribute 'extract_text'

The mismatch isn’t caught until read() actually tries to call the missing method at runtime. That’s the trade-off of duck typing: freedom from formal type declarations, but no safety net before you run the code.

Combining Duck Typing With ABCs and Protocols

Duck typing works seamlessly with both ABCs and protocols. When you use duck typing with ABCs, the contract is enforced at instantiation time, so you get an early failure if a class doesn’t meet the requirements. The type checker can also verify the type based on the inheritance relationship:

Language: Python
>>> from readers_abc import FileReaderInterface, PdfReader, EmailReader

>>> def read(reader: FileReaderInterface, path: str) -> str:
...     reader.load_file(path)
...     return reader.extract_text()
...

>>> read(PdfReader(), "/reports/report.pdf")
Loading PDF from /reports/report.pdf
'Extracted PDF text'

>>> read(EmailReader(), "/mail/message.eml")
Loading email from /mail/message.eml
'Extracted email text'

When you use duck typing with protocols, the contract isn’t enforced at instantiation time, but a static type checker can verify it before you run the code. You can revisit the Type-Checking Protocol Subtypes Statically section for a refresher on how this works.

ABCs and protocols layer structure on top of duck typing to make it safer. ABCs enforce the contract at instantiation, and protocols catch missing methods at type-check time.

Conclusion

Python gives you two main ways to define an interface: abstract base classes and protocols. Reach for abc.ABC when you’re in an inheritance context with control over the hierarchy and need interface enforcement at instantiation time. When you want solid static checking without using inheritance, choose typing.Protocol.

Once you have an interface contract in place, favor duck typing, which relies on behavior and is widely used in Python. Fall back to explicit type checks only when you genuinely need to branch on type.

To keep going from here, Real Python’s Object-Oriented Programming learning path organizes tutorials and video courses on related topics like inheritance, composition, and class design.

In this tutorial, you’ve learned how to:

  • Define inheritance-based interfaces with abstract base classes using abc.ABC and @abstractmethod
  • Define structural interfaces with typing.Protocol
  • Use interfaces idiomatically with duck typing and the EAFP principle
  • Verify contracts at runtime with isinstance() and issubclass()

Whichever approach you pick, your code can rely on what your classes do rather than on their types.

Frequently Asked Questions

Now that you’ve explored Python’s interface mechanisms and their usage modes, you can use the questions and answers below to check your understanding and recap what you’ve learned.

These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.

Python doesn’t have a dedicated interface keyword like Java or C#. Instead, it offers two ways to define interfaces: abstract base classes through abc.ABC, and structural interfaces through typing.Protocol. There are also two ways to use them: duck typing, or explicit isinstance() and issubclass() checks.

In Python, the distinction is mostly terminological. An abstract class built with abc.ABC where every method is decorated with @abstractmethod is effectively an interface. Python has no separate interface construct, so developers often use “interface” and “abstract class” interchangeably.

Use typing.Protocol when you want a contract that concrete classes can satisfy without inheriting from it, which keeps modules loosely coupled. Use abc.ABC when you control the hierarchy and want Python to catch missing methods at instantiation time.

Duck typing is the idea that an object’s suitability is determined by its methods and attributes rather than its declared type. If an object has the methods your code needs, it works, no matter what class it belongs to.

Inherit from abc.ABC and decorate the method with @abstractmethod. Any subclass that doesn’t override the abstract method raises a TypeError when you try to instantiate it.

Take the Quiz: Test your knowledge with our interactive “Implementing Interfaces in Python: ABCs and Protocols” quiz. You’ll receive a score upon completion to help you track your learning progress:


Interactive Quiz

Implementing Interfaces in Python: ABCs and Protocols

Check your understanding of Python interfaces with abstract base classes, protocols, and duck typing, and how to enforce method contracts.

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Leodanis Pozo Ramos

Leodanis is a self-taught Python developer, educator, and technical writer with over 10 years of experience.

» More about Leodanis

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!