Python: Abstract Classes & Interfaces (ABC Module)

Learn the Python abstract class with the ABC (Abstract Base Class) module. Understand @abstractmethod, when to use abstract classes vs duck typing, and how to enforce method contracts across your class hierarchies.

“A good design principle: always program to an interface, not an implementation.”

Gang of Four, Design Patterns

Last Updated: July 2026 | Tested on: Python 3.14.6 | Difficulty: Intermediate | Reading Time: 12 minutes

Prerequisites: Inheritance, Polymorphism

A Python abstract class is a checklist that your subclasses are forced to complete. Think of a job application form with required fields. The website will not let you hit submit until every starred box is filled in. An abstract class works the same way. It lists the methods a subclass must provide, and Python will not let you create an object until they are all there.

You can fake this with raise NotImplementedError inside a base method, but that is a weak guard. The error only fires when someone actually calls the missing method, which might be days later in production. An abstract class catches the gap much earlier, at the moment you try to build the object. Forget a required method and Python refuses to make the instance at all. It is a contract the language enforces for you, instead of a comment you hope everyone reads.

Your First Abstract Class

Define ABCclass Shape(ABC):@abstractmethoddef area(self):class Circle(Shape):def area(self):return pi * r**2class BadShape(Shape):pass(no area() method)c = Circle(5)Works! All abstractmethods implementedb = BadShape()TypeError: Can’t instantiateabstract class BadShapewith abstract method areaThe Contract PatternABC says: ‘You MUSTimplement these methods’Python enforces it atinstantiation timeNot at class definition timePython Abstract Classes: How ABC Blocks an Incomplete Subclass at Instantiation

The flowchart shows how the Python abstract class enforces its contract. When a class inherits from an abstract base class, Python checks at the moment you create an object whether every abstract method has been filled in. If even one is missing, Python raises a TypeError before the object exists. That early check is the whole point of abstract classes. They force subclasses to implement the required methods, so you never ship a half-built object that blows up later when some forgotten method finally gets called.

📄 basic_abc.py: enforcing method contracts

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        """Every shape must calculate its area"""
        pass

    @abstractmethod
    def perimeter(self):
        """Every shape must calculate its perimeter"""
        pass

    def describe(self):  # Concrete method, not abstract
        return f"{self.__class__.__name__}: area={self.area():.2f}"

# Try to instantiate the abstract class
try:
    s = Shape()
except TypeError as e:
    print(f"Error: {e}")

# Implement ALL abstract methods
class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        from math import pi
        return pi * self.radius ** 2

    def perimeter(self):
        from math import pi
        return 2 * pi * self.radius

c = Circle(5)
print(c.describe())
print(f"Perimeter: {c.perimeter():.2f}")

▶ Output

Error: Can't instantiate abstract class Shape without an implementation for abstract methods 'area', 'perimeter'
Circle: area=78.54
Perimeter: 31.42

What happened here: ABC plus @abstractmethod together create the contract. Because of them, Shape cannot be turned into an object on its own. Any subclass has to provide both area() and perimeter(), or it stays abstract too. The describe() method is a normal, concrete method, so subclasses just inherit it as is. Notice the timing: the error showed up the instant we wrote Shape(), not later when some code tried to call area(). That early warning is exactly what you want.

What Happens When You Forget a Method

Here is the part people miss. Implementing most of the abstract methods is not good enough. Miss even one and the whole subclass stays abstract, exactly like that job form that still will not submit because you skipped a single starred field. Watch what happens when a database class implements two of the three required methods and forgets the last one.

📄 incomplete.py: a partial implementation still fails

from abc import ABC, abstractmethod

class Database(ABC):
    @abstractmethod
    def connect(self):
        pass

    @abstractmethod
    def query(self, sql):
        pass

    @abstractmethod
    def close(self):
        pass

# Only implements 2 of 3, so it stays abstract!
class PartialDB(Database):
    def connect(self):
        return "Connected"

    def query(self, sql):
        return f"Running: {sql}"

    # Forgot close()!

try:
    db = PartialDB()
except TypeError as e:
    print(f"Error: {e}")

▶ Output

Error: Can't instantiate abstract class PartialDB without an implementation for abstract method 'close'

What happened here: PartialDB wrote connect() and query() just fine, but skipped close(). Python still treats it as abstract and names the exact method you forgot in the error. That is the quiet strength of an abstract class: the message points straight at the missing piece, so you fix it in seconds instead of hunting through the code later.

Abstract Properties

Contracts are not just about methods. Think of a franchise agreement: every outlet must display its opening hours and its menu, but each branch fills in its own details. Abstract properties work the same way. Sometimes you want to force every subclass to expose a value, like a car’s fuel type or its top speed. Stack @property on top of @abstractmethod and you get an abstract property: a required read-only attribute that each subclass must define for itself.

📄 abstract_property.py: enforcing property implementation

from abc import ABC, abstractmethod

class Vehicle(ABC):
    @property
    @abstractmethod
    def fuel_type(self):
        pass

    @property
    @abstractmethod
    def max_speed(self):
        pass

    def describe(self):
        return f"{self.__class__.__name__}: {self.fuel_type}, max {self.max_speed} km/h"

class ElectricCar(Vehicle):
    @property
    def fuel_type(self):
        return "Electric"

    @property
    def max_speed(self):
        return 250

class DieselTruck(Vehicle):
    @property
    def fuel_type(self):
        return "Diesel"

    @property
    def max_speed(self):
        return 120

for v in [ElectricCar(), DieselTruck()]:
    print(v.describe())

▶ Output

ElectricCar: Electric, max 250 km/h
DieselTruck: Diesel, max 120 km/h

What happened here: Vehicle demands two properties, fuel_type and max_speed, but never says what they should be. Each subclass answers in its own way: the electric car returns “Electric” and 250, the diesel truck returns “Diesel” and 120. The shared describe() method reads those properties without caring which vehicle it is holding. If you ever created a subclass that forgot one of these properties, Python would block it at object creation, the same as with abstract methods.

Real-World Example: Plugin Architecture

This is where abstract classes earn their keep. Picture a notification system that can send messages over email, SMS, Slack, or whatever someone bolts on next year. You want every channel to behave the same way from the outside: send a message, check that a recipient is valid. Say an on-call engineer named Aditi wants both an email and an SMS the moment the server comes back up. The engine should reach her on either channel without special-casing anything. An abstract class spells out that contract once. Think of it like a wall plug. Any device with the right pins fits the socket, and the wall does not care whether you plugged in a lamp or a laptop charger.

📄 plugin_architecture.py: ABC as a plugin contract

from abc import ABC, abstractmethod

class NotificationPlugin(ABC):
    @abstractmethod
    def send(self, recipient, message):
        """Send a notification. Must return True on success."""
        pass

    @abstractmethod
    def validate_recipient(self, recipient):
        """Check if recipient address is valid."""
        pass

class EmailNotification(NotificationPlugin):
    def send(self, recipient, message):
        print(f"  Email to {recipient}: {message}")
        return True

    def validate_recipient(self, recipient):
        return "@" in recipient

class SMSNotification(NotificationPlugin):
    def send(self, recipient, message):
        print(f"  SMS to {recipient}: {message[:160]}")
        return True

    def validate_recipient(self, recipient):
        return recipient.startswith("+") and len(recipient) >= 10

# Notification engine works with ANY plugin
def notify_all(plugins, recipient_map, message):
    for plugin in plugins:
        name = plugin.__class__.__name__
        recipient = recipient_map.get(name, "")
        if plugin.validate_recipient(recipient):
            plugin.send(recipient, message)
        else:
            print(f"  {name}: invalid recipient '{recipient}'")

plugins = [EmailNotification(), SMSNotification()]
recipients = {"EmailNotification": "aditi@example.com", "SMSNotification": "+919876543210"}
notify_all(plugins, recipients, "Server is back online!")

▶ Output

  Email to aditi@example.com: Server is back online!
  SMS to +919876543210: Server is back online!

What happened here: The notify_all() engine never mentions email or SMS by name. It just trusts that any plugin it receives has send() and validate_recipient(), because the abstract base class guarantees it. That is the payoff. You can drop in a brand new SlackNotification next month, and as long as it inherits from NotificationPlugin and fills in the two methods, the engine works with it untouched. New behavior, zero changes to the code that uses it.

ABC vs Duck Typing: When to Use Which

Python often skips abstract classes entirely and leans on duck typing instead: if it walks like a duck and quacks like a duck, treat it like a duck. In code terms, that means you call plugin.send(...) and trust that the object has a send method, without forcing it to inherit from anything. So when do you reach for a strict abstract class over easygoing duck typing? This table sums up the trade-off.

CriteriaABCDuck Typing
Error timingAt instantiationAt method call
FlexibilityMust inherit from ABCNo inheritance needed
DocumentationContract is explicit in codeConvention/docs only
Best forPlugin systems, frameworksSmall teams, internal code
Python wayWhen you need enforcementDefault approach

Common Mistakes

Mistake 1: Using ABC when duck typing would suffice

For a small team working on internal code, duck typing is simpler and gets out of your way. Abstract classes add boilerplate that you may not need. Reach for an abstract class when outside developers will plug into your interface and you need the contract spelled out, not when you and one teammate already know how everything fits together.

Mistake 2: Putting implementation logic in abstract methods

An abstract method should be empty or carry only a little shared logic that subclasses can reach with super(). Do not bury 50 lines of real work inside an abstract method. If a method has that much logic, it probably is not abstract at all, so make it a concrete method or split the heavy part into its own helper.

Best Practices

  • DO use ABC when you need enforced contracts (plugin systems, frameworks)
  • DO mix abstract and concrete methods in ABCs
  • DO prefer duck typing for simple internal code
  • DON’T make every base class abstract. Use ABC only when enforcement actually matters
  • DON’T forget that @property + @abstractmethod enforces property implementation too

Conclusion

Think of an abstract class as a contract your subclasses must fulfill before Python will let them become objects. The abc module gives you @abstractmethod (and the @property plus @abstractmethod combo for properties) to block any incomplete implementation right at object creation. Reach for an abstract class when you need a guaranteed interface, like a plugin system or a framework. Stick with duck typing when flexibility matters more than enforcement, which for everyday internal code is most of the time.

Put simply: an abstract class tells subclasses what to implement. Magic methods tell Python how your objects should behave. In the magic methods tutorial, you will meet the dunder methods that make your objects work smoothly with print(), len(), the == operator, and more. And if you want the full roadmap, from Python basics all the way to AI and ML, visit the Python + AI/ML tutorial series home.

Practice Exercises

  1. Exercise 1: Build an abstract PaymentMethod class with abstract methods pay(amount) and refund(amount). Add two concrete subclasses, CreditCard and UpiPayment, then prove that a subclass missing refund() cannot be instantiated.
  2. Exercise 2: Give the same PaymentMethod class an abstract property provider_name. Make each subclass return its own name, then write a concrete summary() method on the base class that uses that property.
  3. Exercise 3: Solve the same problem twice, once with an abstract class and once with plain duck typing. Write down which version you would ship for a two-person internal tool, and which one for a public plugin SDK (Software Development Kit), and why.

Frequently Asked Questions

What is an abstract class in Python?

An abstract class (defined using ABC from the abc module) is a class that cannot be instantiated directly. It contains one or more @abstractmethod methods that subclasses must implement. It defines a contract that all subclasses must follow.

What is the difference between ABC and a regular base class?

A regular base class with raise NotImplementedError only catches missing methods at runtime when the method is called. ABC catches it at instantiation time, so the object cannot even be created while abstract methods are missing. ABC enforces the contract earlier.

Can an abstract class have non-abstract methods?

Yes. Abstract classes can have both abstract methods (must be implemented by subclasses) and concrete methods (inherited as-is). Concrete methods in ABCs can even call abstract methods, and they will use the subclass implementation at runtime.

When should I use ABC vs duck typing?

Use ABC for plugin architectures, framework extension points, and code that external developers will implement. Use duck typing for internal code, small teams, and rapid prototyping. Most Python code prefers duck typing, so reach for ABC only when you truly need enforcement.

Can I make abstract properties in Python?

Yes. Stack @property and @abstractmethod decorators: @property on top, @abstractmethod below. Subclasses must implement the property using @property. This enforces that every subclass provides certain read-only attributes.

Interview Questions on Python Abstract Classes

Interviewers rarely ask for definitions. They ask what happens in situations like these.

Q: Why does Python raise the TypeError at instantiation time instead of when the incomplete class is defined?

Because an incomplete subclass is perfectly legal, it just stays abstract. You often build layered hierarchies where an intermediate class implements some abstract methods and adds new ones, leaving the rest for its own children. Python only needs to guarantee that a real object is never created with missing methods, so the check happens exactly at that moment: when you call the class to make an instance.

Q: You join a team whose base classes all use raise NotImplementedError, and bugs from missing methods keep reaching production. What do you change and why?

Convert the base classes to inherit from ABC and mark the required methods with @abstractmethod. With NotImplementedError, the crash happens only when the missing method is finally called, which can be weeks later on a rare code path. With ABC, any subclass missing a method fails the moment someone tries to instantiate it, so the bug is caught in development or in the first test run, not in production.

Q: Can an abstract class define __init__ and hold state?

Yes. An abstract class can have a full __init__, instance attributes, and concrete helper methods. Subclasses call super().__init__() to reuse that setup, just like with any base class. The only restriction is that the abstract class itself cannot be instantiated while it still has unimplemented abstract methods.

Q: Your plugin system requires inheriting from your ABC, but a third-party class you cannot modify already has the right methods. How do you make isinstance() checks pass for it?

Use YourABC.register(ThirdPartyClass) to declare it a virtual subclass. After that, isinstance() and issubclass() checks pass without any inheritance. The catch: registration skips enforcement, Python will not verify that the registered class actually implements the abstract methods, so you are back to trusting the duck. If you want static checking instead, typing.Protocol is the structural alternative.

Q: What happens if a subclass of an ABC adds its own new @abstractmethod?

That subclass becomes abstract too, even if it implements every method it inherited. It cannot be instantiated until some further descendant implements the new abstract method as well. This is how you build layered contracts, for example a generic Storage ABC and a stricter CloudStorage ABC that adds a required get_region() on top.

Q: When would you pick typing.Protocol over an ABC?

Pick a Protocol when you want duck typing with static checks: any class with the matching methods satisfies the Protocol automatically, no inheritance needed, and tools like mypy verify it at type-check time. Pick an ABC when you want runtime enforcement at instantiation, shared concrete methods in the base class, or an explicit inheritance relationship that shows up in isinstance() checks. Plugin SDKs usually lean toward ABC, internal codebases with type checking lean toward Protocol.

Further reading: the official Python documentation is the authoritative source on this.

Previous: Python Encapsulation: Private, Protected & Name Mangling

Next: Python: Magic Methods (__str__, __repr__, __len__, __eq__)

Series Home: Python + AI/ML Tutorial Series

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *