Introduction to Python Decorators

Introduction to Python Decorators

What are Decorators?

In Python, functions are first-class objects. They can be passed as variables, and have attributes,

just like any other object. They can also be returned from other functions!

<span class="lineno">1</span> <span class="k">def</span> <span class="nf">outer_func</span><span class="p">():</span>
<span class="lineno">2</span>     <span class="k">def</span> <span class="nf">inner_func</span><span class="p">():</span>
<span class="lineno">3</span>         <span class="k">print</span><span class="p">(</span><span class="s">'Inner funk!'</span><span class="p">)</span>
<span class="lineno">4</span>     <span class="k">return</span> <span class="n">inner_func</span>  <span class="c"># notice, no parens!</span>

Note the absence of parentheses when returning inner_func above. Here we are not calling the function,

but rather returning a reference to the function. We can use this reference to invoke inner_func as follows:

>>> inner = outer_func()
>>> inner()
Inner funk!
>>> 

The call to outer_func above simply returns a reference to inner_func, then we invoke inner_func by

calling its reference as inner() (with parentheses this time).

A decorator is simply a function that returns a function.

Creating a Decorator

Let’s start with the simplest decorator possible – one that does virtually nothing. We will call this construct the “Identity Decorator”:

<span class="lineno">1</span> <span class="k">def</span> <span class="nf">identity</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
<span class="lineno">2</span>     <span class="k">def</span> <span class="nf">_identity</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="lineno">3</span>         <span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="lineno">4</span>     <span class="k">return</span> <span class="n">_identity</span>

This decorator simply returns what ever function and argument(s) was passed to it, without modification. To use a decorator, prefix the method of your choice with the @ symbol followed by the decorator function name:

<span class="lineno">1</span> <span class="nd">@identity</span>
<span class="lineno">2</span> <span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">bar</span><span class="p">):</span>
<span class="lineno">3</span>     <span class="k">pass</span>

The decorator identity will take the function foo as its parameter, and return it unmodified. Now let’s try something concrete.

Say Hello

We can create a decorator to print “Hello” to the console whenever the function it decorates is called.

<span class="lineno">1</span> <span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
<span class="lineno">2</span>     <span class="k">def</span> <span class="nf">inner</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="lineno">3</span>         <span class="k">print</span><span class="p">(</span><span class="s">"Hello"</span><span class="p">)</span>
<span class="lineno">4</span>         <span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="lineno">5</span>     <span class="k">return</span> <span class="n">inner</span>

Here, the hello function is the decorator. Within this function is a nested function called inner which prints “Hello” to the console. It then calls the function passed into hello, along with its arguments, if any.

<span class="lineno">1</span> <span class="nd">@hello</span>
<span class="lineno">2</span> <span class="k">def</span> <span class="nf">foo</span><span class="p">():</span>
<span class="lineno">3</span>     <span class="k">print</span><span class="p">(</span><span class="s">"Sweet Charlie"</span><span class="p">)</span>

I chose inner for the nested function arbitrarily. You can use any name you like. Now, when we call foo we will get the following output:

In [1]: def hello(func):
   ...:     def inner(*args, **kwargs):
   ...:         print("Hello")
   ...:         return func(*args, **kwargs)
   ...:     return inner
   ...: @hello
   ...: def foo():
   ...:     print("Sweet Charlie")
   ...: foo()
   ...: 
Hello
Sweet Charlie

Now, for any function decorated with @hello, The string “Hello” will be printed to the console. We can build a more complex – and useful – example on top of this decorator.

Use Case: Logging with Decorators

Let’s create a logging decorator, which will print the name of the called function, along with any parameters passed to it:

<span class="lineno"> 1</span> <span class="k">def</span> <span class="nf">logger</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
<span class="lineno"> 2</span>     <span class="k">def</span> <span class="nf">inner</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="lineno"> 3</span> 	    <span class="n">res</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="lineno"> 4</span> 	    <span class="k">print</span> <span class="n">func</span><span class="o">.</span><span class="n">__name__</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">kwargs</span>
<span class="lineno"> 5</span> 	    <span class="k">return</span> <span class="n">res</span>
<span class="lineno"> 6</span>     <span class="k">return</span> <span class="n">inner</span>      
<span class="lineno"> 7</span> 
<span class="lineno"> 8</span> <span class="nd">@logger</span>
<span class="lineno"> 9</span> <span class="k">def</span> <span class="nf">do_something</span><span class="p">(</span><span class="n">foo</span><span class="p">,</span> <span class="n">bar</span><span class="p">):</span>
<span class="lineno">10</span>   <span class="k">pass</span>

Console output:

>>> do_something(1,2)
do_something (1, 2) {}

Our logger function, upon a call to do_something(1,2) simply printed out the function name do_something and its arguments as the tuple (1,2). No keyword (named) arguments were supplied, so the empty dictionary {} was printed.

Replacing the positional arguments with keyword arguments to do_something emits the following log statement:

>>> do_something(foo=1,bar=2)
do_something () {'foo': 1, 'bar': 2}

Now, anytime you want to do a little poor man’s debugging you can use the @log decorator without having to modify any of your existing code. Consider a more robust version of this logger using the built in logging module.

Use Case: Timing a Function

We can implement another handy tool to measure the performance of a function.

<span class="lineno">1</span> <span class="k">def</span> <span class="nf">timeit</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
<span class="lineno">2</span>     <span class="sd">"""A decorator that prints the time a function takes to execute."""</span>
<span class="lineno">3</span>     <span class="kn">import</span> <span class="nn">time</span>
<span class="lineno">4</span>     <span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="lineno">5</span>         <span class="n">t</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
<span class="lineno">6</span>         <span class="n">res</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="lineno">7</span>         <span class="k">print</span> <span class="n">func</span><span class="o">.</span><span class="n">__name__</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">t</span>
<span class="lineno">8</span>         <span class="k">return</span> <span class="n">res</span>
<span class="lineno">9</span>     <span class="k">return</span> <span class="n">wrapper</span>

Let’s simulate a slow-running function:

<span class="lineno">1</span> <span class="kn">import</span> <span class="nn">time</span>
<span class="lineno">2</span> 
<span class="lineno">3</span> <span class="nd">@timeit</span>
<span class="lineno">4</span> <span class="k">def</span> <span class="nf">slow</span><span class="p">():</span>
<span class="lineno">5</span>     <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>

When we call slow we introduce an artificial delay of 5 seconds using sleep(5):

<span class="o">>>></span> <span class="n">slow</span><span class="p">()</span>
<span class="n">slow</span> <span class="mf">5.00087594986</span>
<span class="o">>>></span> <span class="n">slow</span><span class="p">()</span>
<span class="n">slow</span> <span class="mf">5.0011920929</span>
<span class="o">>>></span> <span class="n">slow</span><span class="p">()</span>
<span class="n">slow</span> <span class="mf">5.00118708611</span>

The operating system’s high resolution timer shows that “5 seconds” can vary just a bit!

Use Case: Stacking Multiple Decorators

Multiple decorators can be applied to any function. Simply stack them like this:

<span class="lineno">1</span> <span class="nd">@decorator3</span>
<span class="lineno">2</span> <span class="nd">@decorator2</span>
<span class="lineno">3</span> <span class="nd">@decorator1</span>
<span class="lineno">4</span> <span class="k">def</span> <span class="nf">foo</span><span class="p">():</span>
<span class="lineno">5</span>     <span class="k">pass</span>

It is important to note that Python will apply the decorators from innermost to outermost. In our example above, decorator1 is executed first.

Standard Library Decorators

The Python standard library has a number of built-in decorators, available anywhere.

@classmethod

Class methods are bound to a class, but not to a specific instance of a class. For example:

<span class="lineno">1</span> <span class="k">class</span> <span class="nc">Circle</span><span class="p">:</span>
<span class="lineno">2</span> 	<span class="n">diameter</span> <span class="o">=</span> <span class="mi">12</span>
<span class="lineno">3</span> 
<span class="lineno">4</span> 	<span class="nd">@classmethod</span>
<span class="lineno">5</span> 	<span class="k">def</span> <span class="nf">get_diameter</span><span class="p">(</span><span class="n">cls</span><span class="p">):</span>
<span class="lineno">6</span>         <span class="k">return</span> <span class="n">cls</span><span class="o">.</span><span class="n">diameter</span>

In the Circle class above, notice the diameter variable is a class member. That is, it belongs to the class, not any particular instance. There is also no self. prepended to diameter.

The classmethod decorator applies the same structure to a function. The get_diameter function is a member of the Circle class. Notice the first parameter is called cls. This is an implicit reference to the Circle class.

We can call get_diameter as follows:

>>> Circle.get_diameter()
12

Notice we called get_diameter directly on the Circle class, and not on an instance.

We can create an instance of Circle and call get_diameter, producing the same result:

>>> circle = Circle()
>>> circle.get_diameter()
12

Using classmethod also has the benefit of working with inheritance. All subclasses will be able to access the function decorated with classmethod.

Here’s an example of a class method being invoked by a subclass:

<span class="lineno">1</span> <span class="k">class</span> <span class="nc">Parent</span><span class="p">:</span>
<span class="lineno">2</span>     <span class="n">member</span> <span class="o">=</span> <span class="s">'x'</span>
<span class="lineno">3</span> 
<span class="lineno">4</span>     <span class="nd">@classmethod</span>
<span class="lineno">5</span>     <span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">cls</span><span class="p">):</span>
<span class="lineno">6</span>         <span class="k">print</span><span class="p">(</span><span class="n">cls</span><span class="o">.</span><span class="n">member</span><span class="p">)</span>
<span class="lineno">7</span> 
<span class="lineno">8</span> <span class="k">class</span> <span class="nc">Child</span><span class="p">(</span><span class="n">Parent</span><span class="p">):</span>
<span class="lineno">9</span>     <span class="k">pass</span>

We can call foo directly from the Child class:

>>> Child.foo()
x

@staticmethod

Static methods in Python behave a bit differently than class methods, and are more similar to those found in Java or C++, for example. Unlike class methods. static methods have no knowledge of the class in which they are defined.

<span class="lineno">1</span> <span class="k">class</span> <span class="nc">Foo</span><span class="p">:</span>
<span class="lineno">2</span>     <span class="nd">@staticmethod</span>
<span class="lineno">3</span>     <span class="k">def</span> <span class="nf">bar</span><span class="p">():</span>
<span class="lineno">4</span>         <span class="k">pass</span>

Notice that bar has no cls or self arguments. Call static methods similarly to class methods:

>>> Foo.bar()

@property

The property decorator can be used to control access to a variable. Applying this decorator gives a function getter, setter, and deleter attributes. You can use just the property decorator on its own:

<span class="lineno">1</span> <span class="k">class</span> <span class="nc">Foo</span><span class="p">:</span>
<span class="lineno">2</span>     <span class="k">def</span> <span class="nf">__init</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="lineno">3</span>         <span class="bp">self</span><span class="o">.</span><span class="n">_bar</span> <span class="o">=</span> <span class="bp">None</span>
<span class="lineno">4</span> 
<span class="lineno">5</span>     <span class="nd">@property</span>
<span class="lineno">6</span>     <span class="k">def</span> <span class="nf">bar</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="lineno">7</span>         <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_bar</span>

This makes the bar function act as if it were a property:

>>> f = Foo()
>>> f.bar = 'x'
>>> f.bar
'x'

or you can enhance the setter and deleter behavior:

<span class="lineno"> 1</span> <span class="k">class</span> <span class="nc">Foo</span><span class="p">:</span>
<span class="lineno"> 2</span>     <span class="k">def</span> <span class="nf">__init</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="lineno"> 3</span>         <span class="bp">self</span><span class="o">.</span><span class="n">_bar</span> <span class="o">=</span> <span class="bp">None</span>
<span class="lineno"> 4</span> 
<span class="lineno"> 5</span>     <span class="nd">@property</span>
<span class="lineno"> 6</span>     <span class="k">def</span> <span class="nf">bar</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="lineno"> 7</span>         <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_bar</span>
<span class="lineno"> 8</span> 
<span class="lineno"> 9</span>     <span class="nd">@bar.setter</span>
<span class="lineno">10</span>     <span class="k">def</span> <span class="nf">bar</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
<span class="lineno">11</span>         <span class="bp">self</span><span class="o">.</span><span class="n">_bar</span> <span class="o">=</span> <span class="n">value</span>
<span class="lineno">12</span> 
<span class="lineno">13</span>     <span class="nd">@bar.deleter</span>
<span class="lineno">14</span>     <span class="k">def</span> <span class="nf">bar</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="lineno">15</span>         <span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">_bar</span>

Notice the syntax of setter and deleter – the decorator is prefixed with bar.. This indicates that the decorator applies to the property bar. Decorated functions can also have decorators!

Conclusion

We’ve seen how decorators can be used to augment behavior with minimal code changes. This approach has limitless uses. Please leave a comment below, and let me know how you use decorators!

Comments

AWS migration and transformation programs reduce cost, risk, and complexity for rapid business innovation and agility at scale. I offer a number of AWS consulting services, including an AWS migration service to quickly get you up and running. Please contact me for details on how I can help your organization.