Python Decorator

What is a Python Decorator

Essentially, Python Decorator is a type of Python function.
A Python decorator enables additional features without changing the original function codes.
With a decorator, we may extract codes not irrelevant to the original functions and add flexibility.

Common decorators are:
- internal decorator
- class decorator
- function decorator
- function decorator with args

Function Decorator

For example, to add a logging feature to a function

Not so smart way

def foo():
print("foo is running")
print("This is a foo")

Here print out foo is not reusable.

Calling an External Function

We can build an external function and call it within foo

def use_logger(func_name):
print("{} is running".format(func_name))

def foo():
use_logger("foo")
print("This is foo")

But this method, an argument “foo” is still passed.

Decorator

Can build a wrapper method in the function


def use_logger(func):
def wrapper(*args, **kwargs):
print("{} is running".format(func.__name__))
return func(*args, **kwargs)
return wrapper

def foo():
print("This is foo")

foo = use_logger(foo)
foo()

The output would be

script
foo is running
This is foo

More commonly, decorator is used like this:

@use_logger
def bar():
print("This is a bar")

bar()

The results:

script
bar is running
This is a bar

Function Decorator with Parameters

It’s actually not that different from the function decorator without parameters.

def use_logger(level="debug"):
def decorator(func):
def wrapper(*args, **kwargs):
print("{a} {b} is running".format(a=level, b=func.__name__))
return func(*args, **kwargs)
return wrapper
return decorator

@use_logger("task")
def bar():
print("this is a bar")
bar()

The output will be

script
task bar is running
this is a bar

Downside of Using Decorators

  • The disadvantages of decorators is lack of original function’s info e.g. doctring, __name__, parameter list etc.
def use_logger(level="debug"):
def decorator(func):
def wrapper(*args, **kwargs):
print("{a} {b} is running".format(a=level, b=func.__name__))
return func(*args, **kwargs)
return wrapper
return decorator

@use_logger("task")
def bar():
print("this is a bar")

f = bar

# run f
f()

print(f.__name__)
print(f.__doc__)

the output is below

script
task bar is running
this is a bar

# f.__name__
wrapper

# f.__doc__
none

The name printed is wrapper but what we actually want is bar. One solution is to rewrite the function as below

from functools import wraps 
def use_logger(level="debug"):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("{a} {b} is running".format(a=level, b=func.__name__))
return func(*args, **kwargs)
return wrapper
return decorator

Let’s run

script
print(bar.__name__)

The output will be

script
bar

Class Decorator

  • Class Decorators can use __call__ method, when @ is applied on functions, this method will be invoked
class Foo:

def __init__(self, func):
self._func = func

def __call__(self):
print("Class decorator running")
self._func()
print("Class decorator ending")

@Foo
def bar():
print("bar")

bar()

The output will be

script
Class decorator running
bar
Class decorator ending

Internal Decorator

Some internal decorators can be used to restrict a value range, e.g.

class Student:
def __init__(self, name, gpa):
self.name = name
self.__gpa = gpa

# the method gpa is now can be accessed via propery
@property
def gpa(self):
return self.__gpa

# the gpa setter is limited by the assertion
@gpa.setter
def gpa(self, gpa):
assert gpa > 0 and gpa <= 4.0, "invalid gpa"
self.__gpa = gpa

s = Student("HBS", 4.0)
print(s.gpa)

s.gpa = 3.5
print(s.gpa)

The output will look like:

script
4.0
3.5

   Reprint policy


《Python Decorator》 by Isaac Zhou is licensed under a Creative Commons Attribution 4.0 International License
  TOC