0xe3aad

0xe3aad

Python Decorators

Introduction#

When we want to do something before or after a function is executed, we can write it like this:

def foo():
    print('foo()!!!')

def bar():
    print('Before')
    foo()
    print('After')

bar()

Implementation of Decorators#

But this looks awkward, so we can write it like this:

def foo():
    print('foo()!!!')

def bar(func):
    def inner():
        print(f'Before {func.__name__}')
        func()
        print(f'After {func.__name__}')
    return inner

bar(foo)()

In Python, functions are first-class citizens, so they can be used as parameters.

This way, we have implemented a simple decorator. We can see that it is cumbersome to call it. Python provides a syntax sugar for this:

def bar(func):
    def inner():
        print(f'In Bar Before {func.__name__}')
        func()
        print(f'In Bar After {func.__name__}')
    return inner

@bar
def foo():
    print('foo()!!!')

foo()

In addition, Python decorators can have multiple layers, which is equivalent to wrapping layer by layer:

def bar(func):
    def inner():
        print(f'In Bar Before {func.__name__}')
        func()
        print(f'In Bar After {func.__name__}')
    return inner

def baz(func):
    def inner():
        print(f'In Baz Before {func.__name__}')
        func()
        print(f'In Baz After {func.__name__}')
    return inner


@bar
@baz
def foo():
    print('foo()!!!')

foo()

The output of the above code is:

In Bar Before inner
In Baz Before foo
foo()!!!
In Baz After foo
In Bar After inner

An Application Scenario#

We have a function like this:

import time

def slow_method():
    time.sleep(2)
    print('Done!')

We want to know the running time of this function, so we can use a decorator to implement it:

import time

def timeit(func):
    def inner():
        s = time.time()
        func()
        e = time.time()
        print(f'{func.__name__} Finished in {e - s}s.')
    return inner

@timeit
def slow_method():
    time.sleep(2)
    print('Done!')

slow_method()

The output is:

Done!
slow_method Finished in 2.0159008502960205s.

This way, we have achieved our goal.

When the decorated function has parameters, it can also be done like this:

import time

def timeit(func):
    def inner(*args):
        s = time.time()
        func(*args)
        e = time.time()
        print(f'{func.__name__} Finished in {e - s}s.')
    return inner

@timeit
def slow_method(a, b):
    time.sleep(2)
    print(f'{a} + {b} = {a+b}')
    print('Done!')

slow_method(1, 2)

The output is:

1 + 2 = 3
Done!
slow_method Finished in 2.0069477558135986s.

Using Decorators to Implement Memoization#

First, we have a naive Fibonacci function:

def fib(n):
    if n <= 1: return 1
    return fib(n - 1) + fib(n - 2)

It is obvious that this function is very inefficient. We can use memoization to optimize this function.

cache = {}

def fib(n):
    if n in cache: return cache[n]
    if n <= 1: return 1
    cache[n] = fib(n - 1) + fib(n - 2)
    return cache[n]

print(fib(333))

This way, we can make the fib function much faster. However, the implementation is a bit cumbersome. We can use a decorator to simplify the code:

class MyCache(object):
    def __init__(self, func):
        self.func = func
        self.cache = {}

    def __call__(self, *args):
        if args not in self.cache:
            self.cache[args] = self.func(*args)
        return self.cache[args]

@MyCache
def fib(n):
    if n <= 1: return 1
    return fib(n - 1) + fib(n - 2)


print(fib(333))

This way, we have elegantly implemented the fib function.

Reference#

装饰器 Decorator - Python Weekly EP3

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.