Python decorators have a mysterious presence. This is true for a developer that has some Python knowledge, and definitely the case for someone new to programming.
The first time I used a decorator in Python was with the Flask web framework, and I kept looking at the line of code like it didn't belong.
Now that I have created a few functions using the decorator syntax I feel more comfortable with seeing it in Python.
Why Use a Decorator?
Using a decorator is (so I've heard) a common pattern in programming languages. A decorator can be used as a wrapper, around another function, to modify the inputs before they are passed into the function. Additionally, the outputs of a function can be modified before they are returned.
The decorator's usefulness appears to come from the need to add to a function without modifying the core purpose (or source code) of the function. Some use cases include:
- type checking
- data validation
- gather metadata
Decorators can be reused. So it may be easier, and cleaner, to use a decorator function instead of trying to implement a logging function inside of your existing or future functions.
Decorators in Class Definitions
This scenario is a bit beyond a beginner's level. However, conceptually, it demonstrates how a decorator can be useful.
In Python, you can define classes using the
class keyword. Classes have both properties and methods. Below, we define a basic class named
class Person: def __init__(self, first, last): self.name = first + " " + last def __repr__(self): return self.name
The class is initialized with a first name and last name. The
__init__ method initializes the class and sets the property
__repr__ method defines the method that will be called when we pass a Person object to the
>>> me = Person("Jarrett", "Retz") >>> print(me) # Jarrett Retz
Above, I simply created an object named
me. Then I passed the object to the
name property was printed as the output.
A class method receives the class as implicit first argument, just like an instance method receives the instance. Python Docs
We can use a classmethod to modify the inputs to the class before initialization. Take for example the case where we didn't factor in people who submit middle names to the person object. This would cause us to rewrite the class
Instead of doing that, we can extend the class to be initialized the original way, or initialized using our new classmethod.
class Person: def __init__(self, first, last): self.name = first + " " + last def __repr__(self): return self.name @classmethod # decorator def parse_name(cls, name): name_list = name.split(' ') first = name_list last = name_list[-1] return cls(first, last)
In the code above, we added the classmethod
parse_name. Inside of a class, decorators can be used to define classmethods or staticmethods with the syntax
Using the decorator
@classmethod we are given access to the Person class in the first argument:
Let's now use this new method to create the
me object again.
>>> me = Person.parse_name("Jarrett Danger Retz") >>> print(me) # Jarrett Retz
parse_name function allows us to initialize the class using a second option that parses the name string for the first and last words (first and last name).
@classmethod allows us to extend the ways that we initialize the
Decorators Outside Classes
Next, let's extend a function result with a decorator.
First, let's define the function
add in a Python file.
# example.py def add(num1, num2): return num1 + num2
It adds two numbers together. However, we want the result to be returned so a human can interpret the results.
Let's create a decorator function in the same file to do this.
# example.py def recite_answer(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return "The answer is " + str(result) return wrapper def add(num1, num2): return num1 + num2 print(add(4, 5)) # Output: # 9
We created the decorator function
- Takes a function as the only argument
- Defines an inner function
wrapperthat accepts all the arguments that it is passed (
- Calls the function it was passed with the arguments.
- Adds the string
"The answer is "to the result.
- Returns the inner function
recite_answer function has no effect on the
add function definition. We need to use the decorator
# example.py def recite_answer(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return "The answer is " + str(result) return wrapper @recite_answer def add(num1, num2): return num1 + num2 print(add(4, 5)) # Output # The answer is 9
This seems wildly different than our use of decorators in the class definition. Nonetheless, these are two ways that decorators are used in Python.
Finding good examples and explanations for decorators can be difficult for beginners. I would say that it's an intermediate-to-advanced topic. Don't be discouraged if you don't understand it at first.
After using it in a library, or defining your own Python class, you will start to see how they work.