A decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it
@uppercase
def greet():
return 'Hello!'
>>> greet()
'HELLO!'
Decorators provide a simple syntax for calling higher-order functions.
def yell(text):
return text.upper() + '!'
>>> yell('hello')
HELLO!
>>> bark = yell
>>> bark('woof')
WOOF!
>>> del yell
>>> yell('hello?')
NameError: "name 'yell' is not defined"
>>> bark('hey')
'HEY!'
>>> bark.__name__
'yell'
>>> funcs = [bark, str.lower, str.capitalize]
>>> funcs
[<function yell at 0x10ff96510>,
<method 'lower' of 'str' objects>,
<method 'capitalize' of 'str' objects>]
>>> for f in funcs:
... print(f, f('hey there'))
<function yell at 0x10ff96510> 'HEY THERE!'
<method 'lower' of 'str' objects> 'hey there'
<method 'capitalize' of 'str' objects> 'Hey there'
You can even call a function object stored in the list without first assigning it to a variable:
>>> funcs[0]('heyho')
'HEYHO!'
def greet(func):
greeting = func('Hi, I am a Python program')
print(greeting)
You can influence the resulting greeting by passing in different functions:
>>> greet(bark)
'HI, I AM A PYTHON PROGRAM!'
def speak(text):
def whisper(t):
return t.lower() + '...'
return whisper(text)
>>> speak('Hello, World')
'hello, world...'
>>> whisper('Yo')
# ???
def speak(text):
def whisper(t):
return t.lower() + '...'
return whisper(text)
>>> speak('Hello, World')
'hello, world...'
>>> whisper('Yo')
NameError:
"name 'whisper' is not defined"
def speak(text):
def whisper(t):
return t.lower() + '...'
return whisper(text)
>>> speak('Hello, World')
'hello, world...'
>>> whisper('Yo')
NameError:
"name 'whisper' is not defined"
>>> speak.whisper
# ???
def speak(text):
def whisper(t):
return t.lower() + '...'
return whisper(text)
>>> speak('Hello, World')
'hello, world...'
But inner function 'whisper' does not exist outside 'speak':
>>> whisper('Yo')
NameError:
"name 'whisper' is not defined"
>>> speak.whisper
AttributeError:
"'function' object has no attribute 'whisper'"
Functions are objects — you can return the inner function to the caller of the parent function:
def get_speak_func(volume):
def whisper(text):
return text.lower() + '...'
def yell(text):
return text.upper() + '!'
if volume > 0.5:
return yell
else:
return whisper
get_speak_func doesn’t call any of its inner functions - it simply returns the function object:
>>> get_speak_func(0.3)
<function get_speak_func.<locals>.whisper at 0x10ae18>
>>> get_speak_func(0.7)
<function get_speak_func.<locals>.yell at 0x1008c8>
You can call the returned function:
>>> speak_func = get_speak_func(0.7)
>>> speak_func('Hello')
'HELLO!'
def make_adder(n):
def add(x):
return x + n
return add
>>> plus_3 = make_adder(3)
>>> plus_5 = make_adder(5)
>>> plus_3(4)
7
>>> plus_5(4)
9
Functions that do this are called lexical closures.
def my_decorator(func):
def wrapper():
print("Do something before the function is called.")
func()
print("Do something after the function is called.")
return wrapper
def say_whee():
print("Whee!")
say_whee = my_decorator(say_whee)
>>> say_whee()
Do something before the function is called.
Whee!
Do something after the function is called.
The name say_whee now points to the wrapper() inner function:
>>> say_whee
<function my_decorator.<locals>.wrapper at 0x7f3c5dfd42f0>
Using the @ syntax is just syntactic sugar and a shortcut for this commonly used pattern.
@my_decorator
def say_whee():
print("Whee!")
@my_decorator is just an easier way of saying:
say_whee = my_decorator(say_whee)
It’s how you apply a decorator to a function.
def strong(func):
def wrapper():
return '<strong>' + func() + '</strong>'
return wrapper
def emphasis(func):
def wrapper():
return '<em>' + func() + '</em>'
return wrapper
@strong
@emphasis
def greet():
return 'Hello!'
>>> greet()
# ???
def strong(func):
def wrapper():
return '<strong>' + func() + '</strong>'
return wrapper
def emphasis(func):
def wrapper():
return '<em>' + func() + '</em>'
return wrapper
@strong
@emphasis
def greet():
return 'Hello!'
>>> greet()
'<strong><em>Hello!</em></strong>'
def strong(func):
def wrapper():
return '<strong>' + func() + '</strong>'
return wrapper
def emphasis(func):
def wrapper():
return '<em>' + func() + '</em>'
return wrapper
@strong
@emphasis
def greet():
return 'Hello!'
>>> greet()
'<strong><em>Hello!</em></strong>'
The decorators were applied from bottom to top. The chain of decorator function calls looks like this:
decorated_greet = strong(emphasis(greet))
def do_twice(func):
def wrapper_do_twice():
func()
func()
return wrapper_do_twice
@do_twice
def greet(name):
print(f"Hello {name}")
>>> greet("World")
# ???
def do_twice(func):
def wrapper_do_twice():
func()
func()
return wrapper_do_twice
@do_twice
def greet(name):
print(f"Hello {name}")
>>> greet("World")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: wrapper_do_twice() takes 0 positional arguments
but 1 was given
The solution is to use *args and **kwargs in the inner wrapper function. Then it will accept an arbitrary number of positional and keyword arguments:
def do_twice(func):
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
func(*args, **kwargs)
return wrapper_do_twice
@do_twice
def greet(name):
print(f"Hello {name}")
>>> greet("World")
Hello World
Hello World
@do_twice
def return_greeting(name):
print("Creating greeting")
return f"Hi {name}"
>>> hi_adam = return_greeting("Adam")
Creating greeting
Creating greeting
>>> print(hi_adam)
# ???
@do_twice
def return_greeting(name):
print("Creating greeting")
return f"Hi {name}"
>>> hi_adam = return_greeting("Adam")
Creating greeting
Creating greeting
>>> print(hi_adam)
None
Because the do_twice_wrapper() doesn’t explicitly return a value, the call return_greeting("Adam") ended up returning None.
def do_twice(func):
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
return func(*args, **kwargs)
return wrapper_do_twice
@do_twice
def return_greeting(name):
print("Creating greeting")
return f"Hi {name}"
>>> return_greeting("Adam")
Creating greeting
Creating greeting
'Hi Adam'
>>> print
<built-in function print>
>>> print.__name__
'print'
>>> say_whee
<function do_twice.<locals>.wrapper_do_twice
at 0x7f43700e52f0>
>>> say_whee.__name__
'wrapper_do_twice'
import functools
def do_twice(func):
@functools.wraps(func)
def wrapper_do_twice(*args, **kwargs):
func(*args, **kwargs)
return func(*args, **kwargs)
return wrapper_do_twice
>>> say_whee
<function say_whee at 0x7ff79a60f2f0>
>>> say_whee.__name__
'say_whee'
>>> help(say_whee)
Help on function say_whee in module whee:
say_whee()
import functools
def decorator(func):
@functools.wraps(func)
def wrapper_decorator(*args, **kwargs):
# Do something before
value = func(*args, **kwargs)
# Do something after
return value
return wrapper_decorator
def repeat(num_times):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
return decorator_repeat
@repeat(num_times=3)
def greet(name):
print(f"Hello {name}")
>>> greet("World")
Hello World
Hello World
Hello World
def repeat(_func=None, *, num_times=2):
def decorator_repeat(func):
@functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_times):
value = func(*args, **kwargs)
return value
return wrapper_repeat
if _func is None:
return decorator_repeat
else:
return decorator_repeat(_func)
@repeat
def say_whee():
print("Whee!")
@repeat(num_times=3)
def greet(name):
print(f"Hello {name}")
>>> say_whee()
Whee!
Whee!
>>> greet("Penny")
Hello Penny
Hello Penny
Hello Penny
import functools
def count_calls(func):
@functools.wraps(func)
def wrapper_count_calls(*args, **kwargs):
wrapper_count_calls.num_calls += 1
print(
f"Call {wrapper_count_calls.num_calls}"
" of {func.__name__!r}"
)
return func(*args, **kwargs)
wrapper_count_calls.num_calls = 0
return wrapper_count_calls
@count_calls
def say_whee():
print("Whee!")
The state - the number of calls to the function - is stored in the function attribute .num_calls on the wrapper function. Here is the effect of using it:
>>> say_whee()
Call 1 of 'say_whee'
Whee!
>>> say_whee()
Call 2 of 'say_whee'
Whee!
>>> say_whee.num_calls
2
import functools
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(
f"Call {self.num_calls}"
" of {self.func.__name__!r}")
return self.func(*args, **kwargs)
@CountCalls
def say_whee():
print("Whee!")
import functools
def singleton(cls):
"""Make a class a Singleton class (only one instance)"""
@functools.wraps(cls)
def wrapper_singleton(*args, **kwargs):
if not wrapper_singleton.instance:
wrapper_singleton.instance = cls(*args, **kwargs)
return wrapper_singleton.instance
wrapper_singleton.instance = None
return wrapper_singleton
@singleton
class TheOne:
pass
>>> first_one = TheOne()
>>> another_one = TheOne()
>>> id(first_one)
140094218762280
>>> id(another_one)
140094218762280
>>> first_one is another_one
True