Design Patterns That Make Sense in Python: Simple Factory
Tags: python, tutorial, programming
Published: 2021-07-31 14:48:14

In the first post of this series, I'll talk about Design Patterns that make sense in Python. We'll see how to implement them and how they are used in the standard library and other third-party packages. In this post, we'll go over the Simple Factory Pattern and understand why it makes sense in Python.

![Design Patterns That Make Sense in Python: Simple Factory via GIPHY](https://64.media.tumblr.com/0420b027ed46bc71b06117eb7833fa3a/tumblr_mj9c1vjdL21qgfanco1_500.gifv)

## Introduction

Design Patterns has been a popular subject since the Design Patterns: Elements of Reusable Object-Oriented Software (a.k.a GoF) book was released back in 1994. GoF’s goals were to show techniques, a.k.a patterns, to improve an object-oriented design. In total, the book demonstrated 23 patterns, classified in 3 groups:

- Creational

- Behavioral

- Structural

Among the creational patterns, we have the Factory Method. According to the book, the goal of this pattern is to define an interface to create an object. The sub classes will then decide which class will be instantiated. There’s also another variation called Simple Factory. This pattern creates an instance of an object without exposing the details behind the construction. In this article, we’ll see how to do that in Python in an idiomatic way.

> When this is useful? Can we just call the constructor directly?

This pattern is helpful when you need to perform an extra setup before calling a constructor. In the next section we’ll see several examples on how they are used in the Python standard library and also in third-party packages such as [pandas](https://pandas.pydata.org/).

## Usage 

In this part, we’ll see how this pattern is used in practice and how you can implement it yourself.

### Python Standard Library

The `datetime` module is one of the most important ones in the standard library. It defines a few classes such as `date`, `datetime`, and `timedelta`. This module uses the simple factory pattern extensively. A real example is the `date` class. It has a method called `fromtimestamp` that creates `date` instances given a timestamp. 

```python
In [3]: from datetime import date

In [4]: date.fromtimestamp(time.time())
Out[4]: datetime.date(2020, 11, 10)
```
If we look at the implementation, we can see that it extracts the year, month and day from the `time` instance and the call the constructor (`cls`). This is the kind of setup that is abstracted away from the user.

```python
    @classmethod
    def fromtimestamp(cls, t):
        "Construct a date from a POSIX timestamp (like time.time())."
        y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
        return cls(y, m, d)
```

Another great example is the `fromisocalendar` method, which performs an extensive setup. Instead of leaving it to the user, the class provides the functionality “for free” by hiding that from you.

```python
# https://github.com/python/cpython/blob/c304c9a7efa8751b5bc7526fa95cd5f30aac2b92/Lib/datetime.py#L860-L893
...
    @classmethod
    def fromisocalendar(cls, year, week, day):
        """Construct a date from the ISO year, week number and weekday.
        This is the inverse of the date.isocalendar() function"""
        # Year is bounded this way because 9999-12-31 is (9999, 52, 5)
        if not MINYEAR <= year <= MAXYEAR:
            raise ValueError(f"Year is out of range: {year}")

        if not 0 < week < 53:
            out_of_range = True

            if week == 53:
                # ISO years have 53 weeks in them on years starting with a
                # Thursday and leap years starting on a Wednesday
                first_weekday = _ymd2ord(year, 1, 1) % 7
                if (first_weekday == 4 or (first_weekday == 3 and
                                           _is_leap(year))):
                    out_of_range = False

            if out_of_range:
                raise ValueError(f"Invalid week: {week}")

        if not 0 < day < 8:
            raise ValueError(f"Invalid weekday: {day} (range is [1, 7])")

        # Now compute the offset from (Y, 1, 1) in days:
        day_offset = (week - 1) * 7 + (day - 1)

        # Calculate the ordinal day for monday, week 1
        day_1 = _isoweek1monday(year)
        ord_day = day_1 + day_offset

        return cls(*_ord2ymd(ord_day))
....
```

### Pandas

`pandas` is one of the most used Python packages thanks to the rise of Data Science and Machine Learning. Just like Python, `pandas` also makes use of factory methods. A classic example is the `from_dict` method that belongs to the `DataFrame` class.

```python
        >>> data = {'row_1': [3, 2, 1, 0], 'row_2': ['a', 'b', 'c', 'd']}
        >>> pd.DataFrame.from_dict(data, orient='index')
               0  1  2  3
        row_1  3  2  1  0
        row_2  a  b  c  d
```
When we inspect the implementation we can also see a lot of setup and extra checks.
```python
    @classmethod
    def from_dict(cls, data, orient="columns", dtype=None, columns=None) -> DataFrame:
        ...
        index = None
        orient = orient.lower()
        if orient == "index":
            if len(data) > 0:
                # TODO speed up Series case
                if isinstance(list(data.values())[0], (Series, dict)):
                    data = _from_nested_dict(data)
                else:
                    data, index = list(data.values()), list(data.keys())
        elif orient == "columns":
            if columns is not None:
                raise ValueError("cannot use columns parameter with orient='columns'")
        else:  # pragma: no cover
            raise ValueError("only recognize index or columns for orient")

        return cls(data, index=index, columns=columns, dtype=dtype)
```

### How to Implement It

The most idiomatic way of implementing factory methods in Python is by decorating them as `classmethod`. In Python, regular methods are attached to an object instance. We can access the objects’ fields via the `self` argument. `classmethod`, on the other hand, are bound not to an instance but to a `class`. That means when we call `MyClass.factory_method` we are passing `MyClass` as the first argument, called `cls`. This property makes them an excellent alternative for factory methods since calling `cls(args)` inside a `classmethod` is the same as `MyClass(args)`.

To design your own factory methods, it’s sufficient to decorate it as a `classmethod` and return a new instance built with the `cls` argument. For example, presume that we want to implement a `Point` class and we want it also be constructed from Polar coordinates. The extra setup to convert from Polar to Cartesian is kept inside the method. Not simply it’s more readable, but also simplifies the constructor.

```python
class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    @classmethod
    def from_polar(cls, r: float, theta: float) -> "Point":
        """
        Converts a polar coordinate into cartesian point.

        >>> Point.from_polar(r=-2**0.5, theta=math.pi / 4)
        Point(x=-1.00, y=-1.00)
        """
        return cls(r * math.cos(theta), r * math.sin(theta))

    def __repr__(self):
        return f"{self.__class__.__name__}(x={self.x:.2f}, y={self.y:.2f})"

>>> Point.from_polar(r=-2**0.5, theta=math.pi / 4)
Point(x=-1.00, y=-1.00)
```

### Conclusion

That's pretty much it! I hope you’ve learned something different and useful. Simple Factory methods are very cool and can abstract a lot of boilerplate. Not to mention that it makes your code clean and readable. In this post I showed how this pattern is used in the standard library and in other packages such as `pandas`. 

Other posts you may like:

- [How to Pass Multiple Arguments to a map Function in Python](https://miguendes.me/how-to-pass-multiple-arguments-to-a-map-function-in-python)
- [73 Examples to Help You Master Python's f-strings](https://miguendes.me/73-examples-to-help-you-master-pythons-f-strings)
- [How to Check if an Exception Is Raised (or Not) With pytest](https://miguendes.me/how-to-check-if-an-exception-is-raised-or-not-with-pytest)
- [3 Ways to Test API Client Applications in Python](https://miguendes.me/3-ways-to-test-api-client-applications-in-python)
- [Everything You Need to Know About Python's Namedtuples](https://miguendes.me/everything-you-need-to-know-about-pythons-namedtuples)
- [The Best Way to Compare Two Dictionaries in Python](https://miguendes.me/the-best-way-to-compare-two-dictionaries-in-python)
- [How to Implement a Random String Generator With Python](https://miguendes.me/how-to-implement-a-random-string-generator-with-python)
- [7 pytest Features and Plugins That Will Save You Tons of Time](https://miguendes.me/7-pytest-features-and-plugins-that-will-save-you-tons-of-time)


This post was originally published at https://miguendes.me/design-patterns-that-make-sense-in-python-simple-factory

See you next time!