Implementing the Decorator Pattern in Python
Tags: python, tutorial
Published: 2020-08-06 06:20:55

Good evening everyone, today we're going to learn one of my favorite design patterns, the decorator pattern. Please note that this is **_not_** about the Python language construct of the same name. This is about a design pattern, the concepts of which extend to most languages.

# What is the Decorator Pattern?
Put simply, the decorator pattern is a means to add functionality to an object at runtime. If you're into UML, here's Wikipedia's class diagram of the pattern:
![Decorator Pattern UML](https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/Decorator_UML_class_diagram.svg/400px-Decorator_UML_class_diagram.svg.png)

## When to Use the Decorator Pattern

Decorators are typically used in cases where it might seem like you want to reach for inheritance, but from a design perspective, it wouldn't make sense.

For example, you may have a class called `Dog` and you need it to have a method called `eat`, you could probably put `eat` in an `Animal` super-class that `Dog` and other animals inherit, thus gaining the `eat` functionality.

But what if you have a `Dog` that's on a leash? It might behave differently than a regular dog. We _could_ make a class called `LeashedDog` that inherited `Dog`. That would give us a `Dog` object with all the `Dog` functionality as well as the `LeashedDog` functionality.

However, let's think about what that implementation might look like. The classes would be like this:

```python
# Leashed dog with inheritance
class Dog:
  def __init__(self, name):
    self.name = name

  def bark(self):
    print("RUFF!")

class  LeashedDog(Dog):
  def __init__(name):
    super().__init__(name)

  def tug_on_leash(self):
    print("Let's go!!")
```
And using these classes:
```python
dog = Dog('Jeff')
dog.bark()
# outputs "RUFF!"
dog = LeashedDog(dog.name)
dog.bark()
# outputs "RUFF!"
dog.tug_on_leash()
# outputs "Let's go!!"
```
And in this case that might be fine. But in this writer's humble opinion, it's poor design and I'll tell you why.

## How I decide when to use a decorator

In the above example, we created a new instance of a dog. Our dog was still named Jeff, it could still bark, but creating a new instance kind of implies that it's a new dog.

The times when you should use decoration vs. inheritance is not always cut and dry. In fact, some might believe the above design is just fine, and really, it is. However, you may need this pattern for something more complex one day, so I'm keeping it simple so you'll understand it better.

If there was a rule-of-thumb for when to use decoration over inheritance, it would be when you have composite classes--classes that *have* other classes instead of classes that *are* other classes.

I usually opt for decoration when a new instance (like a new dog) doesn't seem quite right. Instead, I'm adding new abilities to an existing thing. Jeff can't tug on a leash until I put one on him.

# Time to get meta
We're about to lightly dip our toes into the ocean of metaprogramming; changing the default behavior of the Python interpreter. There are very, **very** few times that metaprogramming is the answer to your problems, so do not abuse this technique.

With that warning out of the way, as far as metaprogramming goes, we're actually not going to do anything too daring.

## Introducing `__getattr__` and `getattr`
In Python, classes will have what are called "built in" functions normally starting with double underscores (`__`). These functions have designated functionality and are used in ways normally hidden from us.

One such method is the `__getattr__` method that all classes have. This method is called anytime you try to call a non-existent attribute or method on an object. For example, if we said `dog.chase_tail()`, the Python interpreter tries to find `chase_tail` in the `dog` object, can't, and then calls `__getattr__`, raising an `AttributeError`.

Another method we're going to use is `getattr`. `getattr` takes as input parameters an object and stringified name of a function, then returns the function on the object. In our previous example, if we write `getattr(dog, 'bark')`, we'll get the function object back. Even better, we can do `getattr(dog, 'bark')()` and get the console to print "RUFF!". Isn't that cool?

## The decorator in action
How do we want our decorator to act? Well, we want all the methods of `Dog`, with an added one, `tug_on_leash`. We don't want to use inheritance though, so somehow we have to preserve the functionality the `Dog` class.

Here's the plan. We're going to make a class called `LeashedDogDecorator` (you should always name design-pattern-component classes with their name like 'decorator', 'observer', etc.). We're going to expect a `model` in the initialization--which in our case will be a `dog` object--, and we're going to use `__getattr__` to delegate calls to `Dog` methods to the `model` instance variable. That was a lot of words so let's just show it:

```python
# Leashed dog with decoration
class Dog:
  def __init__(self, name):
    self.name = name

  def bark(self):
    print("RUFF!")

class LeashedDogDecorator:
  def __init__(self, model):
    self.model = model
    self.model_attributes = [attribute for attribute in self.model.__dict__.keys()]
    self.model_methods = [m for m in dir(self.model) if not m.startswith('_') and m not in self.model_attributes]

  def __getattr__(self, func):
    if func in self.model_methods:
      def method(*args):
        return getattr(self.model, func)(*args)
      return method
    elif func in self.model_attributes:
      return getattr(self.model, func)
    else:
      raise AttributeError
  
  def tug_on_leash(self):
    print("Let's go!!")
```
**_YIKES!!_**

Before we talk about what's going on, let's make sure everything is working. At the bottom of my script I have:
```python
dog = Dog('Jeff')
dog = LeashedDogDecorator(dog)
dog.bark()
dog.tug_on_leash()
print(dog.name)
```
And when I run this in my console I get
```
RUFF!
Let's go!!
Jeff
```
So we know it works!

### What the heck is going on?
Let's go line by line starting with the constructor. We pass `model`, which we know is going to be an instance of a `Dog` class, which we then assign to the decorator class's `model` attribute.

Then, we gather the list of attributes associated with the model (which, again, is a `Dog` instance) by using the `__dict__` built in, which returns a dictionary of a class's attributes and values (we are only interested in the attribute names, hence the `keys()` call). Then, we gather all the methods in the class that are **_not_** in the `model_attributes` list, and do not start with an underscore (because those are usually Python built ins).

Then, we overwrite the built in `__getattr__` method for this class. Remember, any time a non-existent method is called on a class, `__getattr__` is called. That's why `func` is in the method signature, it collects the name of the attribute that was tried.

Now, since `LeashedDogDecorator` is technically its own class, methods like `bark()` and attributes like `name` won't exist on it. That's why we've overwritten the `__getattr__` method, to catch attempts to call `Dog` methods.

When the attempted call is in the `model_methods` list, we have to build a new method, call it with whatever arguments were passed along with it (hence the `*args`), and return it. We do this by using `getattr` and feeding it the model and name of the called function.

Conversely, when the attempted call is in the `model_attributes` list, we know we're looking for an attribute and thus don't want to _call_ it, so we simply return that value. We do this again by using `getattr(model, func)`.

The last case in the `if..elif..else` block is to raise an `AttributeError` any time the called attribute doesn't exist on either the `Dog` instance or the `LeashedDogDecorator`. If we don't do this, any calls to non-existent attributes on this class will simply fail silently, which we don't want.

Finally, we add the `tug_on_leash` method to the decorator, which is the actual decorated functionality. Now the class we're decorating has the new functionality.

### Step by Step in the Interpreter
Let's try to fully understand what happens when we call `bark()` on our decorated method. The `bark` method is called on the `LeashedDogDecorator` class, but it doesn't exist. By default, the `__getattr__` method is called. When it is, we feed the name `bark` and the `model` of our dog to `getattr` to run the method, giving us the appearance of a `Dog` that just happens to now have leash-specific-behavior.

# Technicalities, Warnings, and Conclusion
So this was definitely a doozy. You might be wondering why we'd go through all the trouble of writing that decorator class when the inheritance strategy worked just fine, and that's definitely a valid question.

Sometimes, inheritance is better, and in our example, it is probably the right way to go. But in complex systems, simple implementations aren't always viable, or they come at the cost of decreased "velocity" (a project-manager word for developer productivity).

This implementation, given our example, is certainly overkill, but there are ways to abstract away some of the tedious details and write simpler looking decorators. I recently wrote a three part series on Python decorators, with better examples and much **_much_** more detail on my personal blog. If you're interested and don't mind my shameless plug, part one can be found [here](http://erikscode.space/index.php/2020/08/01/delegate-and-decorate-in-python-part-1-the-delegation-pattern/).

A few warnings about using metaprogramming before we go. It is always dangerous to alter the default behavior of the language you're working with. Once you do this, you're essentially voiding the warranty and are on your own. Remember in our example we added the final `else` clause to raise an `AttributeError`? I only know to do that because when I implemented this in a project of my own and couldn't figure out why I was having errors that were going unreported. You have to be careful.

Another downside to the meta approach is that it slows down execution of your scripts. Sometimes this is acceptable; but all things come at a cost.

Anyway, if you have any questions, comments, or know a better implementation of this pattern (or just want to make fun of my code), feel free to leave a comment or @ me on Twitter. Happy decorating!