Abstraction in Python

By the Syncfusion DevTutorials team · Last updated

Hiding how something works behind what it does, abstract classes with the abc module, and abstract methods every child must implement.

Think about driving a car.

You turn the key, press a pedal, and the car moves. You do not need to know how the engine, fuel system or gears work. The controls let you use the car without learning all its inner workings.

Every driver can use a car by learning the same basic controls. The details behind those controls can differ from car to car.

That is abstraction: provide a clear way to use something without making the user understand how it works inside.

What You Will Learn

In this lesson, you will learn:

  • What abstraction is and how it makes a class easier to use
  • How a class can offer a simple set of methods while keeping its working details internal
  • What an abstract class is and why Python will not let you create one directly
  • How the abc module can require child classes to provide particular methods

Start With a Working Example

Press Run.

Simple controls, complicated insides

class ReportCard:
    def __init__(self, marks):
        self._marks = marks

    def grade(self):
        average = self._average()
        if average >= 80:
            return "A"
        if average >= 60:
            return "B"
        return "C"

    def _average(self):
        return sum(self._marks) / len(self._marks)

card = ReportCard([78, 85, 60])

print(card.grade())

The person using this class calls grade(). The method handles the averaging, so the caller does not need to know those steps.

Abstraction means providing a clear way to use something without requiring the user to know how it works inside.

Abstraction

What Is Abstraction?

Abstraction gives people a small set of clear ways to use something while keeping its internal steps free to change. For a class, these ways to use it are usually its public methods.

There are two parts:

  • What the class does: the methods other code is meant to use.
  • How it does it: the internal steps that make those methods work.

Benefits of Abstraction

BenefitWhat it means
Less to learnusers can learn the methods they need without reading every internal step
Free to changeinternal code can change while the public methods stay the same
Fewer accidental mistakesusers are guided toward the public methods instead of changing internal details directly
A shared interfacedifferent classes can offer the same methods and be used in similar ways

You have used abstraction since lesson 1. These functions give you a simple way to work with data:

Abstraction you already use

marks = [78, 85, 60]

print(sorted(marks))
print(len("Asha"))
print(sum(marks))

sorted() arranges the values for you. You call it without needing to know the sorting steps Python uses. Python could change those steps without changing how you call sorted().

Hiding Implementation Details

If other code directly uses a class's internal values, those values become part of what that code depends on. Changing them can then break the other code.

Everything exposed

class ReportCard:
    def __init__(self, marks):
        self.marks = marks
        self.total = sum(marks)
        self.count = len(marks)
        self.average = self.total / self.count

card = ReportCard([78, 85, 60])

print(card.average)
print(card.total, card.count)

Any other code can now rely on total, count or average having these names. There is another problem: these values are calculated only once, so changing the marks can make them inconsistent.

Attributes that drift apart

class ReportCard:
    def __init__(self, marks):
        self.marks = marks
        self.total = sum(marks)
        self.average = self.total / len(marks)

card = ReportCard([78, 85, 60])

card.marks.append(92)

print("Marks:", card.marks)
print("Total still says:", card.total)
print("Average still says:", round(card.average, 1))

The marks changed, but the total and average did not. They were calculated when the object was created.

A class can instead provide methods that update or calculate values when needed:

A surface that cannot go stale

class ReportCard:
    def __init__(self, marks):
        self._marks = list(marks)

    def add(self, mark):
        self._marks.append(mark)

    def total(self):
        return sum(self._marks)

    def average(self):
        return round(self.total() / len(self._marks), 1)

card = ReportCard([78, 85, 60])

print(card.average())

card.add(92)

print(card.average())

average() calculates the answer from the marks each time it is called, so the result reflects any marks added through add(). The underscore in _marks is a Python convention meaning other code should treat it as internal; it does not prevent access. list(marks) makes a separate copy, so later changes to the caller's original list do not change this report card. The list is copied when the object is created; each mark must be added through the class's methods if its average should update.

Real-World Examples

ThingWhat you seeWhat is hidden
A carkey, pedals, wheelengine, gearbox, electronics
A phoneicons to tapradios, memory, the operating system
print()one namebuffering, encoding, the console
A bank cardtap itthe account, the network, the checks
A kettleone switchelement, thermostat, cut-out

In each example, you use a small set of controls without dealing with every detail behind them. Those internal details can change while the basic controls stay familiar.

Abstract Classes

What Are Abstract Classes?

An abstract class sets out methods that its child classes must provide. It may also contain ordinary methods and shared data. Python does not let you create an object directly from a class that still has abstract methods.

A Shape is a useful example. A circle and a square are shapes with different ways to calculate area. The general category Shape does not have one specific area formula of its own.

Creating Abstract Classes

A regular class can be used to create an object, even if one of its methods does no useful work:

An ordinary base class is not abstract

class Shape:
    def area(self):
        pass

shape = Shape()

print(shape)
print(shape.area())

pass does nothing, so area() returns None. The class let us create a Shape, but it did not provide a usable area calculation.

One alternative is to raise an error inside the method:

Refusing by raising

class Shape:
    def area(self):
        raise NotImplementedError("every shape must say how to work out its area")

class Square(Shape):
    pass

square = Square()

print(square.area())

This gives a more useful message when area() is called. However, Python still lets us create Square; the problem appears only if code later calls the missing method.

Abstract Base Classes

An abstract base class makes Python reject an object if its class has not provided all required abstract methods. The problem is caught when the object is created.

The abc Module

The abc module provides tools for abstract base classes. To make one, inherit from ABC and mark each required method with @abstractmethod.

An abstract class refuses to be instantiated

from abc import ABC, abstractmethod

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

shape = Shape()

Python raises a TypeError because Shape still has no implementation of area(). The exact wording of the message can vary between Python versions.

A child that implements the method works

from abc import ABC, abstractmethod

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

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

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

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

for shape in [Square(4), Rectangle(3, 5)]:
    print(type(shape).__name__, "area:", shape.area())

An abstract class can also have ordinary methods. Child classes inherit and can use those methods as usual:

Abstract and ordinary methods together

from abc import ABC, abstractmethod

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

    @abstractmethod
    def area(self):
        pass

    def describe(self):
        return f"{self.name} has an area of {self.area()}"

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

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

print(Square(4).describe())

describe() is defined once in Shape. When it calls self.area(), Python runs the area() method provided by the particular child object. This lets the parent share common work while each child supplies its own area calculation.

Both parts are needed for this pattern: inherit from ABC and mark the required method with @abstractmethod. Without ABC, Python does not use the abstract-method marker to prevent object creation. Without @abstractmethod, no method is marked as required.

Abstract Methods

What Are Abstract Methods?

An abstract method is a method a parent class marks as required. A concrete child class must provide an implementation before Python will let you create its objects.

Defining Abstract Methods

Several required methods

from abc import ABC, abstractmethod

class Report(ABC):
    @abstractmethod
    def heading(self):
        pass

    @abstractmethod
    def body(self):
        pass

    def render(self):
        return self.heading() + "\n" + self.body()

class MarkReport(Report):
    def __init__(self, name, marks):
        self.name = name
        self.marks = marks

    def heading(self):
        return f"MARKS - {self.name}"

    def body(self):
        return ", ".join(str(mark) for mark in self.marks)

print(MarkReport("Asha", [78, 85]).render())

An abstract method often uses pass as its body because the child supplies the working version. A child can still call a useful body from an abstract parent method with super(), as shown later.

Implementing Abstract Methods

A concrete child must implement every abstract method it inherits. If it does not, Python treats the child as abstract too:

One method missing is enough

from abc import ABC, abstractmethod

class Report(ABC):
    @abstractmethod
    def heading(self):
        pass

    @abstractmethod
    def body(self):
        pass

class HalfReport(Report):
    def heading(self):
        return "MARKS"

HalfReport()

The error names the missing method. Python raises it when someone tries to create a HalfReport object, rather than waiting for a later call to that method.

A child that implements none of the required methods is still an abstract class. This can be useful when it supplies part of the behavior for classes below it:

An abstract class in the middle

from abc import ABC, abstractmethod

class Report(ABC):
    @abstractmethod
    def heading(self):
        pass

    @abstractmethod
    def body(self):
        pass

class SchoolReport(Report):
    def heading(self):
        return "GREEN VALLEY SCHOOL"

class MarkReport(SchoolReport):
    def __init__(self, marks):
        self.marks = marks

    def body(self):
        return f"Marks: {self.marks}"

report = MarkReport([78, 85])

print(report.heading())
print(report.body())

SchoolReport supplies heading() and leaves body() for a more specific child class to implement.

An abstract method can also have a useful body. A child can call that body with super():

An abstract method with something in it

from abc import ABC, abstractmethod

class Report(ABC):
    @abstractmethod
    def heading(self):
        return "REPORT"

class MarkReport(Report):
    def heading(self):
        return super().heading() + " - MARKS"

print(MarkReport().heading())

The child must still define heading(). The abstract marker requires the child to provide its own method, even though it can reuse the parent's implementation.

Make a class abstract when it describes a category, such as Shape, Report or Payment, but should not be used to create an object by itself. If the parent is useful as an object on its own, a regular base class may be enough.

Ordinary base classAbstract base class
Can you create an object?yesno, while required methods are missing
Required methodsnothing enforced@abstractmethod is enforced through ABC
When a missing method is foundwhen it is called, if everwhen you try to create an object
Use whenthe parent is useful on its ownthe parent describes a category and requires methods from children

Why the timing matters

from abc import ABC, abstractmethod

class LooseShape:
    def area(self):
        raise NotImplementedError

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

class LooseSquare(LooseShape):
    pass

class StrictSquare(StrictShape):
    pass

loose = LooseSquare()
print("The loose object was created with no complaint:", loose)

try:
    StrictSquare()
except TypeError as error:
    print("The strict one was refused:", error)

Python creates the LooseSquare object; its area() method will raise an error only if called. Python refuses to create StrictSquare because it has not implemented the abstract area() method.

Common mistakes

Each row shows a mistake, what appears, why it happens, and how to fix it.

MistakeWhat you seeWhy it happensFix
Forgetting to inherit from ABCThe class can still be instantiatedThe marker is not enforced by the class's metaclassInherit from ABC
Forgetting @abstractmethodA child can be created without defining the methodNo method was marked as requiredAdd the decorator
Implementing only some abstract methodsTypeError when creating the child objectEvery required method must be implementedImplement them all, or keep the child abstract
Misspelling the method in the childTypeError about the abstract methodThe name must match exactlyCheck the name against the parent
Trying to create an abstract class with required methodsTypeError when creating the objectThe required methods have no implementationCreate a concrete child instead
Making every method abstractChild classes repeat the same shared codeNo shared implementation is availableKeep common behavior in ordinary parent methods
Saving worked-out values as attributesValues no longer match after data changesThey were calculated once in __init__Calculate them in a method when needed
Keeping the caller's list directlyLater changes to the caller's list change the report cardBoth names refer to the same listCopy it with list(marks) in the constructor

What to Remember

  • Abstraction means providing a clear way to use something without requiring the user to know how it works inside.
  • A small public interface can make a class easier to learn and leave its internal code free to change.
  • A value calculated once in the constructor does not update when the data changes. A method can calculate it when asked.
  • Copy a list you receive with list(...) if later changes to the original list should not affect your object.
  • An abstract class with unimplemented abstract methods cannot be used to create an object.
  • To require methods, inherit from ABC and mark them with @abstractmethod.
  • A concrete child must implement every abstract method it inherits. A child that does not is abstract too.
  • Python reports a missing implementation when you try to create an object, rather than waiting for a later method call.
  • An abstract class can also contain ordinary methods, and those methods can call the required methods implemented by child classes.

Next: Encapsulation