# Abstraction

Course: [Python](https://www.devtutorials.ai/learn/python) · Section: Object Oriented Programming
Source: https://www.devtutorials.ai/learn/python/oop/abstraction

> 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**.

```python runnable title="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

<div className="tscroll">

| Benefit | What it means |
|---|---|
| Less to learn | users can learn the methods they need without reading every internal step |
| Free to change | internal code can change while the public methods stay the same |
| Fewer accidental mistakes | users are guided toward the public methods instead of changing internal details directly |
| A shared interface | different classes can offer the same methods and be used in similar ways |

</div>

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

```python runnable title="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.

```python runnable title="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.

```python runnable title="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:

```python runnable title="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

<div className="tscroll">

| Thing | What you see | What is hidden |
|---|---|---|
| A car | key, pedals, wheel | engine, gearbox, electronics |
| A phone | icons to tap | radios, memory, the operating system |
| `print()` | one name | buffering, encoding, the console |
| A bank card | tap it | the account, the network, the checks |
| A kettle | one switch | element, thermostat, cut-out |

</div>

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:

```python runnable title="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:

```python runnable expect-error title="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`.

```python runnable expect-error title="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.

```python runnable title="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:

```python runnable title="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

```python runnable title="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:

```python runnable expect-error title="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:

```python runnable title="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()`:

```python runnable title="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.

<div className="tscroll">

| | Ordinary base class | Abstract base class |
|---|---|---|
| Can you create an object? | yes | no, while required methods are missing |
| Required methods | nothing enforced | `@abstractmethod` is enforced through `ABC` |
| When a missing method is found | when it is called, if ever | when you try to create an object |
| Use when | the parent is useful on its own | the parent describes a category and requires methods from children |

</div>

```python runnable title="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.

<div className="tscroll">

| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
| Forgetting to inherit from `ABC` | The class can still be instantiated | The marker is not enforced by the class's metaclass | Inherit from `ABC` |
| Forgetting `@abstractmethod` | A child can be created without defining the method | No method was marked as required | Add the decorator |
| Implementing only some abstract methods | `TypeError` when creating the child object | Every required method must be implemented | Implement them all, or keep the child abstract |
| Misspelling the method in the child | `TypeError` about the abstract method | The name must match exactly | Check the name against the parent |
| Trying to create an abstract class with required methods | `TypeError` when creating the object | The required methods have no implementation | Create a concrete child instead |
| Making every method abstract | Child classes repeat the same shared code | No shared implementation is available | Keep common behavior in ordinary parent methods |
| Saving worked-out values as attributes | Values no longer match after data changes | They were calculated once in `__init__` | Calculate them in a method when needed |
| Keeping the caller's list directly | Later changes to the caller's list change the report card | Both names refer to the same list | Copy it with `list(marks)` in the constructor |

</div>

## 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**
