Mon 16 September 2019 by Moshe Zadka

The following post was originally published on OpenSource.com as part of a series on seven libraries that help solve common problems.

Imagine you have a "shapes" library. We have a `Circle` class, a `Square` class, etc.

A `Circle` has a `radius`, a `Square` has a `side`, and maybe `Rectangle` has `height` and `width`. The library already exists: we do not want to change it.

However, we do want to add an `area` calculation. If this was our library, we would just add an `area` method, so that we can call `shape.area()`, and not worry about what the shape is.

While it is possible to reach into a class and add a method, this is a bad idea: nobody expects their class to grow new methods, and things might break in weird ways.

Instead, the `singledispatch` function in `functools` can come to our rescue:

```@singledispatch
def get_area(shape):
raise NotImplementedError("cannot calculate area for unknown shape",
shape)
```

The "base" implementation for the `get_area` function just fails. This makes sure that if we get a new shape, we will cleanly fail instead of returning a nonsense result.

```@get_area.register(Square)
def _get_area_square(shape):
return shape.side ** 2
@get_area.register(Circle)
def _get_area_circle(shape):
return math.pi * (shape.radius ** 2)
```

One nice thing about doing things this way is that if someone else writes a new shape that is intended to play well with our code, they can implement the `get_area` themselves:

```from area_calculator import get_area

@attr.s(auto_attribs=True, frozen=True)
class Ellipse:
horizontal_axis: float
vertical_axis: float

@get_area.register(Ellipse)
def _get_area_ellipse(shape):
return math.pi * shape.horizontal_axis * shape.vertical_axis
```

Calling `get_area` is straightforward:

```print(get_area(shape))
```

This means we can change a function that has a long `if isintance()/elif isinstance()` chain to work this way, without changing the interface. The next time you are tempted to check `if isinstance`, try using `singledispatch`!