Python Abstract Classes
Object-Oriented Python Object-Oriented Python

Python Abstract Classes (ABC)

1Python Abstract Classes: overview

An abstract class is a base class that lists the methods every child must provide without saying how they work, and Python refuses to create objects from it until all of those methods have real code.

GoalBy the end you can define an abstract base class with ABC and @abstractmethod, explain why it cannot be instantiated, write subclasses that implement every abstract method, share code through concrete and template methods, declare an abstract property, and decide between an ABC and duck typing.

Before this lesson: Python Inheritance, Python Polymorphism and Duck Typing

2Python Abstract Classes: diagram

class Shape(ABC) declares @abstractmethod area()class Shape(ABC) declares @abstractmethod area()classarea()Can you create objects?Shape(ABC)abstract: promise onlyNo: TypeErrorCircle(Shape)implementedYesSquare(Shape)implementedYesTriangle(Shape)forgottenNo: still abstractObjects can only be created when no abstract method is left unimplemented.
The abstract parent states the promise (area) but cannot make objects itself. Each child is checked when you create an object: Circle and Square kept the promise, Triangle forgot it, so Python stops it.

3Python Abstract Classes: explained step by step

In the inheritance lesson, Shape.area() returned 0 so that children could override it. That hides a quiet flaw: if someone writes a new Triangle and forgets area(), nothing complains. The triangle just reports an area of 0, and you discover the bug far away from the line that caused it.

An abstract class closes that gap. It is a parent class that says what every child must be able to do and leaves how to the children. Unlike a placeholder method, the promise is enforced by Python.

When a parent is only a promise

Some parents are never meant to be used on their own. There is no such thing as "just a shape", only circles, squares and triangles. There is no "just a payment method", only cards, bank transfers and vouchers. The parent exists to state a contract: every Shape has an area(), every PaymentMethod can pay(amount), every Exporter can export(rows). Code that knows only the contract can then work with any child, including ones written next year.

Your first abstract class

Inherit from ABC (short for Abstract Base Class) and put @abstractmethod above each method the children must provide. The body is usually just a docstring, because it is not meant to do the work:

Python
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        """Every shape must say how to work out its area."""

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2

print(Square(4).area())

try:
    Shape()
except TypeError:
    print("Cannot create a Shape: it is abstract")

Shape() fails because area() has no real code yet. In Python 3.13 the message reads TypeError: Can't instantiate abstract class Shape without an implementation for abstract method 'area'. Notice when it happens: the moment you try to create the object, not later when some distant code calls area().

Every abstract method must be filled in

A child becomes concrete, meaning you may create objects from it, only when it replaces every abstract method with a real one. If it fills in just some, it is still abstract and fails in the same way. That is the safety net you want when a teammate adds a new subclass and forgets a method. Each abstract class also keeps the names still missing in __abstractmethods__, which is handy for debugging:

Python
from abc import ABC, abstractmethod

class PaymentMethod(ABC):
    @abstractmethod
    def pay(self, amount):
        """Take the money and return a receipt line."""

    @abstractmethod
    def refund(self, amount):
        """Give the money back and return a receipt line."""

class CardPayment(PaymentMethod):
    def pay(self, amount):
        return f"Charged ${amount} to the card"

    def refund(self, amount):
        return f"Returned ${amount} to the card"

class GiftVoucher(PaymentMethod):
    def pay(self, amount):
        return f"Used ${amount} of voucher"

print(CardPayment().pay(30))

try:
    GiftVoucher()
except TypeError:
    print("GiftVoucher is still abstract")

print(sorted(GiftVoucher.__abstractmethods__))

Abstract classes can still hold real code

Being abstract does not change anything else about a class. It can have an __init__, attributes and ordinary methods, and its children inherit them unchanged. Only the methods marked @abstractmethod have to be replaced.

The template method pattern

This is where abstract classes really pay off. Write one concrete method that fixes the order of the steps, and make the individual steps abstract. The parent owns the recipe; each child supplies its own ingredients. Below, export() never changes, but what a header and a row look like is decided by the child:

Python
from abc import ABC, abstractmethod

class Exporter(ABC):
    def __init__(self, title):
        self.title = title

    def export(self, rows):
        lines = [self.header()]
        for row in rows:
            lines.append(self.format_row(row))
        return "\n".join(lines)

    @abstractmethod
    def header(self):
        """First line of the output."""

    @abstractmethod
    def format_row(self, row):
        """One output line for one row."""

class CsvExporter(Exporter):
    def header(self):
        return "name,price"

    def format_row(self, row):
        return f"{row[0]},{row[1]}"

class MarkdownExporter(Exporter):
    def header(self):
        return f"# {self.title}"

    def format_row(self, row):
        return f"- {row[0]}: ${row[1]}"

rows = [("Lamp", 24.5), ("Mug", 8)]
print(CsvExporter("Stock").export(rows))
print(MarkdownExporter("Stock").export(rows))

Nothing in export() knows about CSV or Markdown, so adding a JsonExporter later would not touch it.

Abstract properties

A property can be abstract too, which forces children to provide a value instead of a method. Stack the decorators in this order: @property on the outside and @abstractmethod directly above the def.

Python
from abc import ABC, abstractmethod

class Employee(ABC):
    def __init__(self, name):
        self.name = name

    @property
    @abstractmethod
    def monthly_pay(self):
        """Each kind of employee works its pay out differently."""

    def summary(self):
        return f"{self.name} earns {self.monthly_pay} a month"

class Salaried(Employee):
    def __init__(self, name, salary):
        super().__init__(name)
        self.salary = salary

    @property
    def monthly_pay(self):
        return self.salary

class Hourly(Employee):
    def __init__(self, name, rate, hours):
        super().__init__(name)
        self.rate = rate
        self.hours = hours

    @property
    def monthly_pay(self):
        return self.rate * self.hours

print(Salaried("Asha", 3200).summary())
print(Hourly("Ben", 20, 150).summary())

isinstance() and abstract classes

An abstract class works as a type for everything that keeps its promise, so isinstance(obj, Shape) is True for every child. A function can use that to check its input once, at the door.

Python
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        """Return the area."""

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2

class Tile:
    def area(self):
        return 9

for thing in [Square(2), Tile()]:
    print(type(thing).__name__, isinstance(thing, Shape), thing.area())

ABC or duck typing?

Tile has no Shape parent, yet its area() works, so any code that just calls .area() is happy with it. That is duck typing: "if it has the method, it can do the job". It needs no base class and it is Python's default style.

Reach for an abstract class when: - you own a family of classes and want a forgotten method to be an error at creation time, not a crash halfway through the program; - the children share real code, such as a template method or a common __init__; - you want isinstance() to mean "this object keeps the contract".

Stay with duck typing when only one or two functions use the objects, when the classes come from different libraries you cannot change, or when a base class would add ceremony without any shared code.

One limit to remember: Python only checks that the names exist. It does not check the parameters or whether the method does anything sensible, so a child that defines area(self, extra) still slips through.

4Python Abstract Classes: syntax

from abc import ABC, abstractmethod

class Base(ABC):                        # inherit from ABC
    def __init__(self, name):           # normal code is allowed too
        self.name = name

    @abstractmethod
    def step(self):                     # every child MUST replace this
        """Describe the contract here."""

    @property
    @abstractmethod                     # abstract property: @property goes on the outside
    def size(self):
        """Children must provide a value."""

    def run(self):                      # concrete method built from abstract steps
        return f"{self.name}: {self.step()}"

class Child(Base):
    def step(self):
        return "done"

    @property
    def size(self):
        return 3

Base("x")                               # TypeError: can't instantiate abstract class
Child("x").run()                        # works: nothing abstract is left
isinstance(Child("x"), Base)            # True

5Python Abstract Classes: run the example

Press Run (or Ctrl + Enter). The output (step 6) appears directly below the code. Change anything and run it again.

Interactive example
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    def describe(self):
        return f"{type(self).__name__}: area {self.area()}"

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w, self.h = w, h

    def area(self):
        return self.w * self.h

try:
    Shape()
except TypeError:
    print("Shape is abstract")
print(Rectangle(3, 4).describe())
print(isinstance(Rectangle(1, 1), Shape))
Expected outputShape is abstract
Rectangle: area 12
True

7Python Abstract Classes: how the example works

Hover or tap a step to highlight its lines in the example above.

  1. Line 1ABC and abstractmethod come from the standard abc module.
  2. Line 3-6Shape inherits from ABC, and area() is marked @abstractmethod. Its body (pass) never has to do anything: it only records the promise that every shape will have an area().
  3. Line 8-9describe() is an ordinary, concrete method that every child inherits. It calls self.area(), so it uses whichever area() the object's own class provides.
  4. Line 11-16Rectangle is a concrete child: it stores its sides and replaces area() with real code. Because no abstract method is left, you can create Rectangle objects.
  5. Line 18-21Shape() raises TypeError because area() has no implementation. The except branch prints a fixed message, so the program carries on.
  6. Line 22The inherited describe() calls Rectangle's area() (3 * 4 = 12), and type(self).__name__ gives the name of the object's class.
  7. Line 23A Rectangle counts as a Shape for isinstance(), so this prints True.

8Python Abstract Classes: common mistakes

Forgetting to inherit from ABC

✗ Wrong

from abc import abstractmethod

class Shape:
    @abstractmethod
    def area(self):
        pass

class Triangle(Shape):
    pass

tri = Triangle()
print("Triangle created, area:", tri.area())
No error at all: Python prints 'Triangle created, area: None'

✓ Right

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Triangle(Shape):
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return self.base * self.height / 2

print(Triangle(3, 4).area())

@abstractmethod only has an effect inside a class built from ABC (or with metaclass=ABCMeta). Without it the decorator is ignored, so nothing stops you from creating incomplete objects. Add ABC to the parent.

Misspelling or forgetting an abstract method in the child

✗ Wrong

from abc import ABC, abstractmethod

class Exporter(ABC):
    @abstractmethod
    def export(self, rows):
        pass

class CsvExporter(Exporter):
    def exprot(self, rows):
        return ",".join(rows)

exporter = CsvExporter()
TypeError: Can't instantiate abstract class CsvExporter without an implementation for abstract method 'export'

✓ Right

from abc import ABC, abstractmethod

class Exporter(ABC):
    @abstractmethod
    def export(self, rows):
        pass

class CsvExporter(Exporter):
    def export(self, rows):
        return ",".join(rows)

exporter = CsvExporter()
print(exporter.export(["Lamp", "Mug"]))

A child only counts as implementing a method when the name matches exactly. exprot is just a new method, so export is still abstract. The error message names the missing method, so read it carefully.

Creating an object from the abstract class itself

✗ Wrong

from abc import ABC, abstractmethod

class PaymentMethod(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

method = PaymentMethod()
method.pay(20)
TypeError: Can't instantiate abstract class PaymentMethod without an implementation for abstract method 'pay'

✓ Right

from abc import ABC, abstractmethod

class PaymentMethod(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

class CardPayment(PaymentMethod):
    def pay(self, amount):
        return f"Paid ${amount} by card"

method = CardPayment()
print(method.pay(20))

An abstract class is a blueprint for its children, not something you use directly. Create an object of a concrete child instead, and pass it around wherever a PaymentMethod is expected.

Stacking @abstractmethod and @property in the wrong order

✗ Wrong

from abc import ABC, abstractmethod

class Employee(ABC):
    @abstractmethod
    @property
    def monthly_pay(self):
        pass
AttributeError: attribute '__isabstractmethod__' of 'property' objects is not writable

✓ Right

from abc import ABC, abstractmethod

class Employee(ABC):
    @property
    @abstractmethod
    def monthly_pay(self):
        pass

class Intern(Employee):
    @property
    def monthly_pay(self):
        return 900

print(Intern().monthly_pay)

Decorators apply from the bottom up. @property must be the outer decorator and @abstractmethod the inner one, so that abstractmethod marks the plain function before property wraps it.

9Python Abstract Classes: exercises

Write your answer, then press Check. The checks run in your browser.

1. Write an abstract Greeter

Write code Easy

Create an abstract class Greeter with one abstract method greet(self, name). Then write two children:

  • FormalGreeter, whose greet returns "Good day, <name>."
  • CasualGreeter, whose greet returns "Hey <name>!"

For example CasualGreeter().greet("Ben") returns "Hey Ben!".

Your code
from abc import ABC, abstractmethod

# 1. class Greeter(ABC) with an abstract greet(self, name)

# 2. class FormalGreeter(Greeter): greet returns "Good day, <name>."

# 3. class CasualGreeter(Greeter): greet returns "Hey <name>!"

2. Predict the notifier

Predict the output Medium

One abstract class, two children. Type exactly what the program prints.

Read this code
from abc import ABC, abstractmethod

class Notifier(ABC):
    @abstractmethod
    def send(self, text):
        pass

    @abstractmethod
    def channel(self):
        pass

    def notify(self, text):
        return f"[{self.channel()}] {self.send(text)}"

class EmailNotifier(Notifier):
    def send(self, text):
        return text.upper()

    def channel(self):
        return "email"

class HalfNotifier(Notifier):
    def send(self, text):
        return text

try:
    HalfNotifier()
    print("half created")
except TypeError:
    print("half is abstract")

print(EmailNotifier().notify("hi"))
print(sorted(HalfNotifier.__abstractmethods__))
print(isinstance(EmailNotifier(), Notifier))

2 more exercises → 6 more examples

Finished this lesson?
Progress is saved in this browser — create a free account to keep it across devices.

Questions learners ask

What is the difference between an abstract class and an interface in Python?

Python has no separate interface keyword. An abstract class whose methods are all abstract plays the role of an interface: it lists what children must provide. Unlike interfaces in some other languages, a Python abstract class may also hold normal methods and attributes, which is what makes patterns like the template method possible.

Should I use @abstractmethod or raise NotImplementedError?

@abstractmethod stops you from creating an object of an incomplete class, so the mistake appears immediately and the error names the missing method. Raising NotImplementedError inside a base method only fails when that method is finally called, which can be much later. Prefer @abstractmethod for classes you design to be subclassed.

Can an abstract class have an __init__ and normal methods?

Yes. An abstract class is an ordinary class apart from the rule that you cannot create objects from it while abstract methods remain. Children can call its init with super().init(...) and inherit every concrete method.

Further reading

10Next lesson

Up nextPython DataclassesA dataclass is a normal class where you list the fields once, with type hints, and Python writes __init__, __repr__ and…

Related:
Python InheritancePython Polymorphism and Duck TypingPython Composition: Has-A vs Is-A

← Python Composition: Has-A vs Is-A · All Object-Oriented Python lessons · Practise in the IDE