Chapter 4. Decorators
Python supports a powerful tool called the decorator. Decorators let you add rich features to groups of functions and methods, without modifying them at all; untangle distinct, frustratingly intertwined concerns in your code, in ways not otherwise possible; and build powerful, extensible software frameworks. Many of the most popular and important Python libraries in the world leverage decorators. This chapter teaches you how to do the same.
A decorator is something you apply to a function or method. You’ve
probably seen decorators before. There’s a decorator called
property
often used in classes:
class
Person
:
def
__init__
(
self
,
first_name
,
last_name
):
self
.
first_name
=
first_name
self
.
last_name
=
last_name
@property
def
full_name
(
self
):
return
self
.
first_name
+
" "
+
self
.
last_name
>>>
person
=
Person
(
"John"
,
"Smith"
)
>>>
(
person
.
full_name
)
John Smith
Note that it is printing person.full_name
, not person.full_name()
.
For another example: in the Flask web framework, here is how you define a simple home page:
@app
.
route
(
"/"
)
def
hello
():
return
"<html><body>Hello World!</body></html>"
The app.route("/")
is a decorator, applied here to the function
called hello()
. So an HTTP GET request to the root URL (“/”) will be
handled by the hello()
function.
A decorator works by adding behavior around a function1—meaning, lines of code which are executed before that function begins, after it returns, or both. It does not alter any lines of code inside the function. Typically, when you go to the trouble to define a decorator, you plan to use it on at least two different functions, usually more. Otherwise you’d just put the extra code inside the lone function, and not bother writing a decorator.
Using decorators is simple and easy; even someone new to programming can learn to use them quickly. Our objective in this chapter is different: to give you the ability to write your own decorators, in many different useful forms. This is not a beginner topic; it barely qualifies as intermediate. Writing decorators requires a deep understanding of several sophisticated Python features and how they play together. Most Python developers never learn how to create them. In this chapter, you will.
The Basic Decorator
Once a decorator is written, using it is easy. You just write @
and
the decorator name, on the line before you define a function:
@some_decorator
def
some_function
(
arg
):
# blah blah
This applies the decorator called some_decorator
to
some_function()
.2 Now, it turns out this syntax with the @
symbol is a shorthand. In essence, when byte-compiling your code,
Python will translate the above into this:
def
some_function
(
arg
):
# blah blah
some_function
=
some_decorator
(
some_function
)
This is valid Python code too; it is what people did before the @
syntax came along. The key here is the last line:
some_function
=
some_decorator
(
some_function
)
First,
understand that a decorator is just a function. That’s it. It
happens to be a function taking one argument, which is the function
object being decorated. It then returns a different function. In the
code snippet above you are defining a function, initially called
some_function
. (Remember that some_function
is a variable holding
a function object, because that is what the def
statement does.)
That function object is passed to some_decorator()
, which returns a
different function object, which is finally stored in
some_function
.
To keep us sane, let’s define some terminology:
-
The decorator is what comes after the
@
. It’s a function. -
The bare function is what is
def
‘ed on the next line. It is, obviously, also a function. -
The end result is the decorated function. It’s the final function that you actually call in your code.3
Your mastery of decorators will be most graceful if you remember one thing: a decorator is just a normal, boring function. It happens to be a function taking exactly one argument, which is itself a function. And when called, the decorator returns a different function.
Let’s make this concrete. Here’s a simple decorator which logs a message to stdout every time the decorated function is called.
def
printlog
(
func
):
def
wrapper
(
arg
):
(
"CALLING: "
+
func
.
__name__
)
return
func
(
arg
)
return
wrapper
@printlog
def
foo
(
x
):
(
x
+
2
)
This decorator creates a new function, called wrapper()
,
and returns that. This is then assigned to the variable
foo
, replacing the undecorated, bare function:
# Remember, this...
@printlog
def
foo
(
x
):
(
x
+
2
)
# ...is the exact same as this:
def
foo
(
x
):
(
x
+
2
)
foo
=
printlog
(
foo
)
Here’s the result:
>>>
foo
(
3
)
CALLING: foo
5
At a high level, the body of printlog()
does two things: define a
function called wrapper()
, then return it. Most decorators
follow that structure. Notice printlog()
does not modify the behavior
of the original function foo
itself; all wrapper()
does is print a
message to standard output, before calling the original (bare)
function.
Once you’ve applied a decorator, the bare function isn’t directly
accessible anymore; you can’t call it in your code. Its name now
applies to the decorated version. But that decorated function
internally retains a reference to the bare function, calling it inside
wrapper()
.
Generic Decorators
This version of printlog()
has a shortcoming, though. Look what
happens when I apply it to a different function:
>>>
@printlog
...
def
baz
(
x
,
y
):
...
return
x
**
y
...
>>>
baz
(
3
,
2
)
Traceback (most recent call last):
File"<stdin>"
, line1
, in<module>
TypeError
:wrapper() takes 1 positional argument but 2 were given
Can you spot what went wrong?
printlog()
is built to wrap a function taking exactly one
argument. But baz
has two, so when the decorated function is called,
the whole thing blows up. There’s no reason printlog()
needs to have
this restriction; all it’s doing is printing the function name. You
can fix it by declaring wrapper()
with variable arguments:
# A MUCH BETTER printlog.
def
printlog
(
func
):
def
wrapper
(
*
args
,
**
kwargs
):
(
"CALLING: "
+
func
.
__name__
)
return
func
(
*
args
,
**
kwargs
)
return
wrapper
This decorator is compatible with any Python function:
>>>
@printlog
...
def
foo
(
x
):
...
(
x
+
2
)
...
>>>
@printlog
...
def
baz
(
x
,
y
):
...
return
x
**
y
...
>>>
foo
(
7
)
CALLING: foo
9
>>>
baz
(
3
,
2
)
CALLING: baz
9
A decorator written this way, using variable arguments, will potentially work with functions and methods written years later—code the original developer never imagined. This structure has proven to be powerful and versatile.
# The prototypical form of Python decorators.
def
prototype_decorator
(
func
):
def
wrapper
(
*
args
,
**
kwargs
):
return
func
(
*
args
,
**
kwargs
)
return
wrapper
We don’t always do this, though. Sometimes you are writing a decorator that only applies to a function or method with a very specific kind of signature, and it would be an error to use it anywhere else. So feel free to break this rule when you have a reason.
Decorating Methods
Decorators apply to methods just as well as to functions. You
often don’t need to change anything: when the wrapper has a
signature of wrapper(*args, **kwargs)
, like printlog()
does, it
works just fine with any object’s method. But sometimes you will see
code like this:
# Not really necessary.
def
printlog_for_method
(
func
):
def
wrapper
(
self
,
*
args
,
**
kwargs
):
(
"CALLING: "
+
func
.
__name__
)
return
func
(
self
,
*
args
,
**
kwargs
)
return
wrapper
This wrapper()
has one required argument, named self
. It works
fine when applied to a method. But for the decorator I’ve written
here, self
is completely unnecessary, and in fact, it has a downside.
Simply defining wrapper(*args, **kwargs)
causes self
to be
considered one of the args
; such a decorator works just as well with
both functions and methods. But if a wrapper is defined to require
self
, that means it must always be called with at least one
argument. Suddenly you have a decorator that cannot be applied to
functions which do not take at least one argument. (The fact that it’s named self
does not matter; it’s just a temporary name for that first argument,
inside the scope of wrapper()
.) You can apply this decorator to any
method, and to some functions. But if you apply it to a function that
takes no arguments, you’ll get a runtime error.
Now, here’s a different decorator:
# Using self makes sense in this case:
def
enhanced_printlog_for_method
(
func
):
def
wrapper
(
self
,
*
args
,
**
kwargs
):
(
"CALLING:
{}
on object ID
{}
"
.
format
(
func
.
__name__
,
id
(
self
)))
return
func
(
self
,
*
args
,
**
kwargs
)
return
wrapper
It could be applied like this:
class
Invoice
:
def
__init__
(
self
,
id_number
,
total
):
self
.
id_number
=
id_number
self
.
total
=
total
self
.
owed
=
total
@enhanced_printlog_for_method
def
record_payment
(
self
,
amount
):
self
.
owed
-=
amount
inv
=
Invoice
(
42
,
117.55
)
(
f
"ID of inv:
{
id
(
inv
)
}
"
)
inv
.
record_payment
(
55.35
)
Here’s the output when you execute:
ID of inv: 4320786472 CALLING: record_payment on object ID 4320786472
This is a different story, because this wrapper()
’s body
explicitly uses the current object—a concept that only makes sense
for methods. That makes the self
argument perfectly appropriate. It
prevents you from using this decorator on some functions. But since
you intend this decorator to only be applied to methods, it would be
an error to apply it to a function anyway.
When writing a decorator for methods, I recommend you get in the habit
of making your wrapper only take *args
and **kwargs
, except when
you have a clear reason to include self
. After you’ve written
decorators for a while, you’ll be surprised at how often you end up
using old decorators on new callables—both functions and methods—in ways you never imagined at first. A signature of wrapper(*args,
**kwargs)
preserves that flexibility. If the decorator turns out to
need an explicit self
argument, it’s easy enough to put that in.
Data in Decorators
Some valuable decorator patterns rely on using variables inside the decorator function itself. This is not the same as using variables inside the wrapper function. Let me explain.
Imagine you need to keep a running average of what a chosen function
returns. And further, you need to do this for a family of functions or
methods. We can write a decorator called running_average
to handle
this. As you read, note carefully how data
is defined and used:
def
running_average
(
func
):
data
=
{
"total"
:
0
,
"count"
:
0
}
def
wrapper
(
*
args
,
**
kwargs
):
val
=
func
(
*
args
,
**
kwargs
)
data
[
"total"
]
+=
val
data
[
"count"
]
+=
1
(
"Average of
{}
so far:
{:.01f}
"
.
format
(
func
.
__name__
,
data
[
"total"
]
/
data
[
"count"
]))
return
val
return
wrapper
Each time the function is called, the average of all calls so far is
printed out.4 The decorator itself is called once for each
bare function it is applied to. Then, each time the resulting
decorated function is called in the code, the wrapper()
function is
what’s actually executed.
So imagine applying running_average
to a function like this:
@running_average
def
foo
(
x
):
return
x
+
2
This executes running_average()
as a function once, which creates an
internal dictionary, named data
, used to keep track of foo
’s
metrics.
But when you run foo()
, you do NOT execute running_average()
again. Instead, each time you run the decorated function foo()
, it
is calling the wrapper()
which running_average()
created internally.
This means it can access data
from its containing scope.
This may not all make sense yet, so let’s step through it. Running
foo()
several times produces:
>>>
foo
(
1
)
Average of foo so far: 3.00
3
>>>
foo
(
10
)
Average of foo so far: 7.50
12
>>>
foo
(
1
)
Average of foo so far: 6.00
3
>>>
foo
(
1
)
Average of foo so far: 5.25
3
The placement of data
is important. Pop quiz:
-
What happens if you move the line defining
data
up one line, outside therunning_average()
function? -
What happens if you move that line down, into the
wrapper()
function?
Looking at the code above, decide on your answers to these questions before reading further.
To answer the first question, here’s what it looks like if you create
data
outside the decorator:
# This version has a bug.
data
=
{
"total"
:
0
,
"count"
:
0
}
def
outside_data_running_average
(
func
):
def
wrapper
(
*
args
,
**
kwargs
):
val
=
func
(
*
args
,
**
kwargs
)
data
[
"total"
]
+=
val
data
[
"count"
]
+=
1
(
"Average of
{}
so far:
{:.01f}
"
.
format
(
func
.
__name__
,
data
[
"total"
]
/
data
[
"count"
]))
return
func
(
*
args
,
**
kwargs
)
return
wrapper
If you do this, every decorated function shares the exact same data
dictionary! This actually doesn’t matter if you only ever decorate
just one function. But you never bother to write a decorator unless
it’s going to be applied to at least two:
@outside_data_running_average
def
foo
(
x
):
return
x
+
2
@outside_data_running_average
def
bar
(
x
):
return
3
*
x
And that produces a problem:
>>>
# First call to foo...
...
foo
(
1
)
Average of foo so far: 3.0
3
>>>
# First call to bar...
...
bar
(
10
)
Average of bar so far: 16.5
30
>>>
# Second foo should still average 3.00!
...
foo
(
1
)
Average of foo so far: 12.0
Because outside_data_running_average()
uses the same data
dictionary for all the functions it decorates, the statistics are
conflated.
Now, for the other question: what if you define data
inside wrapper()
?
# This version has a DIFFERENT bug.
def
running_average_data_in_wrapper
(
func
):
def
wrapper
(
*
args
,
**
kwargs
):
data
=
{
"total"
:
0
,
"count"
:
0
}
val
=
func
(
*
args
,
**
kwargs
)
data
[
"total"
]
+=
val
data
[
"count"
]
+=
1
(
"Average of
{}
so far:
{:.01f}
"
.
format
(
func
.
__name__
,
data
[
"total"
]
/
data
[
"count"
]))
return
func
(
*
args
,
**
kwargs
)
return
wrapper
@running_average_data_in_wrapper
def
foo
(
x
):
return
x
+
2
Look at the average as we call this decorated function multiple times:
>>>
foo
(
1
)
Average of foo so far: 3.0
3
>>>
foo
(
5
)
Average of foo so far: 7.0
7
>>>
foo
(
20
)
Average of foo so far: 22.0
22
Do you see why the running average is wrong? The data
dictionary is
reset every time the decorated function is called, because it is
reset every time wrapper()
is called. This is why it’s important to
consider the scope when implementing your decorator. Here’s the
correct version again (repeated so you don’t have to skip back):
def
running_average
(
func
):
data
=
{
"total"
:
0
,
"count"
:
0
}
def
wrapper
(
*
args
,
**
kwargs
):
val
=
func
(
*
args
,
**
kwargs
)
data
[
"total"
]
+=
val
data
[
"count"
]
+=
1
(
"Average of
{}
so far:
{:.01f}
"
.
format
(
func
.
__name__
,
data
[
"total"
]
/
data
[
"count"
]))
return
func
(
*
args
,
**
kwargs
)
return
wrapper
So when exactly is running_average()
executed? The decorator function
itself is executed exactly once for every function it
decorates. If you decorate N functions, running_average()
is executed
N times, so we get N different data
dictionaries, each tied to one
of the resulting decorated functions. This has nothing to do with how
many times a decorated function is executed. The decorated function
is, basically, one of the created wrapper()
functions. That wrapper()
can be executed many times, using the same data
dictionary that was
in scope when that wrapper()
was defined.
This is why running_average
produces the correct behavior:
@running_average
def
foo
(
x
):
return
x
+
2
@running_average
def
bar
(
x
):
return
3
*
x
>>>
# First call to foo...
...
foo
(
1
)
Average of foo so far: 3.0
3
>>>
# First call to bar...
...
bar
(
10
)
Average of bar so far: 30.0
30
>>>
# Second foo gives correct average this time!
...
foo
(
1
)
Average of foo so far: 3.0
3
Accessing Inner Data
What if you want to peek into data
? The way we’ve written
running_average
, you can’t—at least not with ordinary Python
code. data
persists because of the reference inside of wrapper()
,
and in a sense is veiled from outside access.
But there is an easy solution: simply assign data
as an attribute to
the wrapper
object. For example:
# collectstats is much like running_average, but lets
# you access the data dictionary directly, instead
# of printing it out.
def
collectstats
(
func
):
data
=
{
"total"
:
0
,
"count"
:
0
}
def
wrapper
(
*
args
,
**
kwargs
):
val
=
func
(
*
args
,
**
kwargs
)
data
[
"total"
]
+=
val
data
[
"count"
]
+=
1
return
val
wrapper
.
data
=
data
return
wrapper
See that line wrapper.data = data
? Yes, you can do that. A function
in Python is just an object, and in Python, you can add new attributes
to objects by just assigning them. This conveniently annotates the
decorated function:
@collectstats
def
foo
(
x
):
return
x
+
2
>>>
foo
.
data
{'total': 0, 'count': 0}
>>>
foo
(
1
)
3
>>>
foo
.
data
{'total': 3, 'count': 1}
>>>
foo
(
2
)
4
>>>
foo
.
data
{'total': 7, 'count': 2}
It’s clear now why collectstats
doesn’t contain any print
statement: you don’t need one! We can check the accumulated numbers at
any time, because this decorator annotates the function itself, with
that data
attribute.
Nonlocal Decorator State
Let’s switch to another problem you might run into. Here’s a decorator that counts how many times a function has been called:
# Watch out, this has a bug...
count
=
0
def
countcalls
(
func
):
def
wrapper
(
*
args
,
**
kwargs
):
global
count
count
+=
1
(
f
"# of calls:
{
count
}
"
)
return
func
(
*
args
,
**
kwargs
)
return
wrapper
@countcalls
def
foo
(
x
):
return
x
+
2
@countcalls
def
bar
(
x
):
return
3
*
x
This version of countcalls()
has a bug. Do you see it?
Here it is: this version stores count
as global, meaning every
function that is decorated will use the same variable:
>>>
foo
(
1
)
# of calls: 1
3
>>>
foo
(
2
)
# of calls: 2
4
>>>
bar
(
3
)
# of calls: 3
9
>>>
bar
(
4
)
# of calls: 4
12
>>>
foo
(
5
)
# of calls: 5
7
No good can come from this, so we need a better way. But the solution is trickier than it seems. Here’s one attempt:
# Move count inside countcalls, and remove the
# "global count" line. But it still has a bug...
def
countcalls
(
func
):
count
=
0
def
wrapper
(
*
args
,
**
kwargs
):
count
+=
1
(
f
"# of calls:
{
count
}
"
)
return
func
(
*
args
,
**
kwargs
)
return
wrapper
But that just creates a different problem:
>>>
foo
(
1
)
Traceback (most recent call last):
File"<stdin>"
, line1
, in<module>
File
"<stdin>"
, line6
, inwrapper
UnboundLocalError
:local variable 'count' referenced before assignment
We can’t use global
, because it’s not global. But we
can use the nonlocal
keyword:
# Final working version!
def
countcalls
(
func
):
count
=
0
def
wrapper
(
*
args
,
**
kwargs
):
nonlocal
count
count
+=
1
(
f
"# of calls:
{
count
}
"
)
return
func
(
*
args
,
**
kwargs
)
return
wrapper
This finally works correctly:
>>>
foo
(
1
)
# of calls: 1
3
>>>
foo
(
2
)
# of calls: 2
4
>>>
bar
(
3
)
# of calls: 1
9
>>>
bar
(
4
)
# of calls: 2
12
>>>
foo
(
5
)
# of calls: 3
Applying nonlocal
gives the count
variable a
special scope that is part way between local and global. Essentially,
Python will search for the nearest enclosing scope that defines a
variable named count
, and use it like it’s a
global.
You may be wondering why we didn’t need to use nonlocal
with the
first version of running_average
above—here it is again, for
reference:
def
running_average
(
func
):
data
=
{
"total"
:
0
,
"count"
:
0
}
def
wrapper
(
*
args
,
**
kwargs
):
val
=
func
(
*
args
,
**
kwargs
)
data
[
"total"
]
+=
val
data
[
"count"
]
+=
1
(
"Average of
{}
so far:
{:.01f}
"
.
format
(
func
.
__name__
,
data
[
"total"
]
/
data
[
"count"
]))
return
func
(
*
args
,
**
kwargs
)
return
wrapper
The line count += 1
is actually modifying the value of the count
variable itself, because it really means count = count + 1
. And
whenever you modify (instead of just read) a variable that was created
in a larger scope, Python requires you to declare that’s what you
actually want, with global
or nonlocal
.
Here’s the sneaky thing: when you write data["count"] += 1
, you are
not actually modifying data
! Or rather, you’re not modifying the
variable named data
, which points to a dictionary object.
Instead, the statement data["count"] += 1
invokes a method on the
data
object. (Specifically, __setitem__()
.5)
This does change the state of the dictionary. But it doesn’t make
data
point to a different dictionary. In contrast, count += 1
makes count
point to a different integer, so use nonlocal
there.
Decorators That Take Arguments
Early in this chapter, I showed you an example decorator from the Flask framework:
@app
.
route
(
"/"
)
def
hello
():
return
"<html><body>Hello World!</body></html>"
This is different from any decorator we’ve implemented so far, because it actually takes an argument. How do we write decorators that can do this? For example, imagine a family of decorators adding a number to the return value of a function:
def
add2
(
func
):
def
wrapper
(
n
):
return
func
(
n
)
+
2
return
wrapper
def
add4
(
func
):
def
wrapper
(
n
):
return
func
(
n
)
+
4
return
wrapper
@add2
def
foo
(
x
):
return
x
**
2
@add4
def
bar
(
n
):
return
n
*
2
There is literally only one character difference between add2
and
add4
. Wouldn’t it be better if we can do something like this:
@add
(
2
)
def
foo
(
x
):
return
x
**
2
@add
(
4
)
def
bar
(
n
):
return
n
*
2
We can. The key is to understand that add
is actually not a
decorator; it is a function that returns a decorator. In other
words, add
is a function that returns another function—since the
returned decorator is, itself, a function.
To make this work, we write a function called add()
, which creates
and returns the decorator:
def
add
(
increment
):
def
decorator
(
func
):
def
wrapper
(
n
):
return
func
(
n
)
+
increment
return
wrapper
return
decorator
It’s easiest to understand from the inside out:
-
The
wrapper()
function is just like in the other decorators. Ultimately, when you callfoo()
(the original function name), it’s actually callingwrapper()
. -
Moving up, we have the aptly named
decorator
. It is a function. -
At the top level is
add
. This is not a decorator. It’s a function that returns a decorator.
Notice the closure here. The increment
variable is encapsulated
in the scope of the add()
function. We cannot access its value outside
the decorator, in the calling context. But we don’t need to, because
wrapper()
itself has access to it.
Suppose the Python interpreter is parsing your program and encounters the following code:
@add
(
2
)
def
f
(
n
):
# ....
Python takes everything between the @
symbol and the end-of-line
character as a single Python expression—add(2)
in this
case. That expression is evaluated. This all happens at compile
time. Evaluating the decorator expression means executing add(2)
,
which will return a function object. That function object is the
decorator. It’s named decorator
inside the body of the add()
function, but it doesn’t really have a name at the top level; it’s
just applied to f()
.
What can help you see more clearly is to think of functions as things
that are stored in variables. In other words, if I write def foo(x):
in my code, I can say to myself: “I’m creating a function
called foo”.
But there is another way to think about it. I could instead say: “I’m creating a function object, and storing it in a variable called foo”. Believe it or not, this is closer to how Python actually works. So things like this are possible:
>>>
def
foo
():
...
(
"This is foo"
)
>>>
baz
=
foo
>>>
baz
()
This is foo
>>>
# foo and baz have the same id()... so they
...
# refer to the same function object.
>>>
id
(
foo
)
4301663768
>>>
id
(
baz
)
4301663768
Now, back to add
. As you realize add(2)
returns a function object,
it’s easy to imagine storing that in a variable named add2
. As a
matter of fact, you can do this:
add2
=
add
(
2
)
@add2
def
foo
(
x
):
return
x
**
2
Remember that @
is a shorthand:
# This...
@some_decorator
def
some_function
(
arg
):
# blah blah
# ... is translated by Python into this:
def
some_function
(
arg
):
# blah blah
some_function
=
some_decorator
(
some_function
)
So for add
, the following are all equivalent:
add2
=
add
(
2
)
# Store the decorator in the add2 variable
# This function definition...
@add2
def
foo
(
x
):
return
x
**
2
# ... is translated by Python into this:
def
foo
(
x
):
return
x
**
2
foo
=
add2
(
foo
)
# But also, this...
@add
(
2
)
def
foo
(
x
):
return
x
**
2
# ... is translated by Python into this:
def
foo
(
x
):
return
x
**
2
foo
=
add
(
2
)(
foo
)
Look over these variations, and trace through what’s going on in your
mind, until you understand how they are all equivalent. The expression
add(2)(foo)
in particular is interesting. Python parses this
left-to-right. So it first executes add(2)
, which returns a function
object. In this expression, that function has no name; it is temporary
and anonymous. The very next character is “(
”. This causes Python to
take that anonymous function object, and immediately call it. It is
called with the argument foo
(which is the bare function—the
function which we are decorating). The anonymous function then returns
a different function object, which we finally store in the variable
called foo
, reassigning that variable name.
Study that previous paragraph until you fully get it. This is important.
Notice that in the line foo = add(2)(foo)
, the name foo
means
something different each time it’s used. When you write something like
n = n + 3
, the name n
refers to something different on either side
of the equals sign. In the exact same way, in the line foo =
add(2)(foo)
, the variable foo
holds two different function objects
on the left and right sides.
Class-Based Decorators
I repeatedly told you that a decorator is just a function. Well, decorators are usually implemented as functions; that is true. But it is also possible to implement a decorator as a class. In fact, any decorator that you can implement as a function can be implemented with a class instead.
Why would you do this? For certain kinds of decorators, classes are better suited. They can be more readable, and otherwise easier to work with. In addition, they let you use the full feature set of Python’s object system. For example, if you have a collection of related decorators, you can leverage inheritance to collect their shared code.
Most decorators are probably better implemented as functions, though it depends on whether you prefer object-oriented or functional abstractions. It is best to learn both ways, then decide which you prefer in your own code on a case-by-case basis.
Implementing Class-Based Decorators
The secret to decorating with classes is a magic method: __call__()
.
Any object can implement __call__()
to make itself callable—meaning,
it can be called like a function. Here’s
an example:
class
Prefixer
:
def
__init__
(
self
,
prefix
):
self
.
prefix
=
prefix
def
__call__
(
self
,
message
):
return
self
.
prefix
+
message
We can call instances of this class like a function:
>>>
simonsays
=
Prefixer
(
"Simon says: "
)
>>>
simonsays
(
"Get up and dance!"
)
'Simon says: Get up and dance!'
Just looking at simonsays("Get up and dance!")
in isolation, you’d
never guess it is anything other than a normal function. In fact, it’s
an instance of Prefixer
.
What’s happening is that any time you type an identifier name,
followed by the “(
” (left parenthesis) character, Python immediately
translates that into the __call__()
method. In fact, amazingly
enough, this is true even for regular functions!
>>>
def
f
(
x
):
...
return
x
+
1
>>>
# This...
>>>
f
(
2
)
3
>>>
# ... is the EXACT SAME as this:
>>>
f
.
__call__
(
2
)
3
When you use __call__()
, you are hooking into something very
fundamental in how Python works.
You can use __call__()
to implement decorators. Before proceeding,
think back to the @printlog
decorator.Using this information about
__call__()
, how might you implement printlog()
as a class instead
of a function? I encourage you to pause and try implementing it
yourself, before you proceed.
The basic approach is to pass func
(the bare function) to the
constructor of a decorator class, and adapt wrapper()
to be the
__call__()
method. Like this:
class
PrintLog
:
def
__init__
(
self
,
func
):
self
.
func
=
func
def
__call__
(
self
,
*
args
,
**
kwargs
):
(
f
"CALLING:
{
self
.
func
.
__name__
}
"
)
return
self
.
func
(
*
args
,
**
kwargs
)
# Compare to the function version you saw earlier:
def
printlog
(
func
):
def
wrapper
(
*
args
,
**
kwargs
):
(
"CALLING: "
+
func
.
__name__
)
return
func
(
*
args
,
**
kwargs
)
return
wrapper
>>>
@PrintLog
...
def
foo
(
x
):
...
(
x
+
2
)
...
>>>
@printlog
...
def
baz
(
x
,
y
):
...
return
x
**
y
...
>>>
foo
(
7
)
CALLING: foo
9
>>>
baz
(
3
,
2
)
CALLING: baz
9
From the user’s point of view, @Printlog
and @printlog
work
exactly the same.
Benefits of Class-Based Decorators
Class-based decorators have some advantages over function-based decorators. For one thing, the decorator is a class, which means you can leverage inheritance. So if you have a family of related decorators, you can reuse code between them. Here’s an example:
import
sys
class
ResultAnnouncer
:
stream
=
sys
.
stdout
prefix
=
"RESULT"
def
__init__
(
self
,
func
):
self
.
func
=
func
def
__call__
(
self
,
*
args
,
**
kwargs
):
value
=
self
.
func
(
*
args
,
**
kwargs
)
self
.
stream
.
write
(
f
"
{
self
.
prefix
}
:
{
value
}
\n
"
)
return
value
class
StdErrResultAnnouncer
(
ResultAnnouncer
):
stream
=
sys
.
stderr
prefix
=
"ERROR"
Another benefit is when you prefer to accumulate state in object
attributes, instead of a closure. For example, the countcalls
function decorator (discussed in “Nonlocal Decorator State”) could be implemented as a class:
class
CountCalls
:
def
__init__
(
self
,
func
):
self
.
func
=
func
self
.
count
=
0
def
__call__
(
self
,
*
args
,
**
kwargs
):
self
.
count
+=
1
(
f
"# of calls:
{
self
.
count
}
"
)
return
self
.
func
(
*
args
,
**
kwargs
)
@CountCalls
def
foo
(
x
):
return
x
+
2
Notice this also lets us access foo.count
, if we want to check the
count outside of the decorated function. The function version didn’t
let us do this.6
When creating decorators which take arguments, the structure is a
little different. In this case, the constructor accepts not the func
object to be decorated, but the parameters on the decorator line. The
__call__()
method must take the func
object, define a wrapper
function, and return it—similar to simple function-based decorators:
# Class-based version of the "add" decorator above.
class
Add
:
def
__init__
(
self
,
increment
):
self
.
increment
=
increment
def
__call__
(
self
,
func
):
def
wrapper
(
n
):
return
func
(
n
)
+
self
.
increment
return
wrapper
You then use it like you would any other argument-taking decorator:
>>>
@Add
(
2
)
...
def
foo
(
x
):
...
return
x
**
2
...
>>>
@Add
(
4
)
...
def
bar
(
n
):
...
return
n
*
2
...
>>>
foo
(
3
)
11
>>>
bar
(
77
)
158
Any function-based decorator can be implemented as a class-based
decorator; you simply adapt the decorator function itself to __init__()
,
and wrapper()
to __call__()
. It’s possible to design class-based
decorators which cannot be translated into a function-based form, though.
For certain complex decorators, some people feel that class-based decorators are easier to read than function-based ones. In particular, many people seem to
find multinested def
statements hard to reason about. Others
(including your author) feel the opposite. This is a matter of
preference,7 and I recommend you practice with both styles before
coming to your own conclusions.
In this section, I have capitalized the names of class-based decorators. I did this because I think it makes this section easier to understand. But that is not what I actually do in real code.
We have conflicting conventions here. Python class names are capitalized, and function names start with a lowercase letter. But whether a decorator is implemented as a function or class is an implementation detail. In fact, you may create a decorator as a function, and then later refactor it as a class—or vice versa. People using your decorator probably do not know or care whether it’s a class or a function; to change its casing each time is a waste of effort, and breaks all the code using that decorator already.
So in my own code, I always use function-name conventions for decorators, even if they are implemented as a class. Essentially creating a higher-priority naming convention. I suggest you do this also; it will make it easier for everyone using the decorators you write, including you.
Decorators for Classes
I lied to you again. I said decorators are applied to functions and methods. Well, they can also be applied to classes.
Understand this has nothing to do with the last section’s topic, on implementing decorators as classes. A decorator can be implemented as a function, or as a class; and that decorator can be applied to a function, or to a class. They are independent ideas. Here, we are talking about how to decorate classes instead of functions.
To introduce an example, let me explain Python’s built-in repr()
function. When called with one argument, this returns a string, meant
to represent the passed object. It’s similar to str()
; the
difference is that while str()
returns a human-readable string,
repr()
is meant to return a string version of the Python code needed
to recreate it. So imagine a simple Penny
class:
class
Penny
:
value
=
1
penny
=
Penny
()
Ideally, repr(penny)
returns the string "Penny()"
. But that’s not
what happens by default:
>>>
class
Penny
:
...
value
=
1
>>>
penny
=
Penny
()
>>>
repr
(
penny
)
'<__main__.Penny object at 0x10229ff60>'
You can fix this by implementing a __repr__()
method on your
classes, which repr()
will use:
>>>
class
Penny
:
...
value
=
1
...
def
__repr__
(
self
):
...
return
"Penny()"
>>>
penny
=
Penny
()
>>>
repr
(
penny
)
'Penny()'
Let’s create a decorator that will automatically add a __repr__()
method to any class. You might be able to guess how it works.
Instead of a wrapper function, the decorator returns a class:
>>>
def
autorepr
(
cls
):
...
def
cls_repr
(
self
):
...
return
f
"
{
cls
.
__name__
}
()"
...
cls
.
__repr__
=
cls_repr
...
return
cls
...
>>>
@autorepr
...
class
Penny
:
...
value
=
1
...
>>>
penny
=
Penny
()
>>>
repr
(
penny
)
'Penny()'
It’s suitable for classes with no-argument constructors, like Penny
.
Note how the decorator modifies cls
directly. The original class
is returned; that original class just now has a __repr__()
method. Can you see how this is different from what we did with
decorators of functions? With those, the decorator returned a new,
different function object.
Another strategy for decorating classes is closer in spirit: creating a new subclass within the decorator, returning that subclass in place of the original:
def
autorepr_subclass
(
cls
):
class
NewClass
(
cls
):
def
__repr__
(
self
):
return
f
"
{
cls
.
__name__
}
()"
return
NewClass
This has the disadvantage of creating a new type:
>>>
@autorepr_subclass
...
class
Nickel
:
...
value
=
5
...
>>>
nickel
=
Nickel
()
>>>
type
(
nickel
)
<class '__main__.autorepr_subclass.<locals>.NewClass'>
The resulting object’s type is not obviously related to the decorated class. That makes debugging harder, creates unclear log messages, and throws a greasy wrench into the middle of your inheritance hierarchy. For this reason, I recommend the first approach.
Class decorators tend to be less useful in practice than those for functions and methods. One real use is to automatically add dynamically generated methods. But they are more flexible than that. You can even express the singleton pattern using class decorators:
def
singleton
(
cls
):
instances
=
{}
def
get_instance
():
if
cls
not
in
instances
:
instances
[
cls
]
=
cls
()
return
instances
[
cls
]
return
get_instance
# There is only one Elvis.
@singleton
class
Elvis
:
pass
Note the IDs are the same:
>>>
elvis1
=
Elvis
()
>>>
elvis2
=
Elvis
()
>>>
id
(
elvis1
)
4410742144
>>>
id
(
elvis2
)
4410742144
This singleton
class decorator does not return a class; it returns a
function object (get_instance
). This means that in your code,
Elvis
will actually be that function object, rather than a
class.
This is not without consequences; writing code like
isinstance(elvis1, Elvis)
will raise a TypeError
, for
example. This and other effects create code situations which are
confusing, to say the least. It illustrates once again why we want to
be careful about what is actually returned when we write decorators
for classes.
Conclusion
Decorators are a powerful tool for metaprogramming in Python. It is no mistake that some of the most successful and famous Python libraries use them extensively in their codebase. When you learn to write decorators, not only do you add this power tool to your toolbox, but you also permanently level up your deep understanding of Python itself.
1 Or method. When talking about the target of a decorator, “function” will always mean “function or method”, unless I say otherwise.
2 For Java people: this looks just like Java annotations. However, it is completely different. Python decorators are not in any way similar.
3 Some authors use the phrase “decorated function” to mean “the function that is decorated”—what I’m calling the “bare function”. If you read a lot of blog posts, you’ll find the phrase used both ways. (Sometimes in the same article.) This book will consistently use the definitions I give here.
4 In a real application, you’d write the average to some kind of log sink, but we’ll use print()
here because it’s convenient for learning.
5 When you write data["count"] += 1
, Python reads that as data.__setitem__("count", data["count"] + 1)
.
6 This would also be a way to expose the data
dict of the @collectstats
decorator, from earlier in the chapter.
7 I believe it may have more to do with what language you “grew up” with. Someone who first learned to code in Java will tend to think best in terms of classes; someone with a mathematical background, or whose first coding language was more function oriented, will tend to think better in terms of nested functions.
Get Powerful Python now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.