Effective Python 装饰器

在Python,函数属于First Class Object,而First Class Object可以:

  • 作为参数进行传递
  • 作为返回值
  • 将其赋值给其它变量
  • 存放在某种数据结构中,比如list、tuple等

1 Decorator

下面介绍三种类型的装饰器

1.1 装饰器没有参数,被装饰函数没有返回值

import time
import math

def calculate_time(func):
 
    # args is short for arguments, kwargs is short for keywords arguments
    def inner1(*args, **kwargs):
        begin = time.time()
        func(*args, **kwargs)
        end = time.time()
        print("Total time taken in {}: {}".format(func.__name__, end - begin))
    return inner1

@calculate_time
def factorial(num):
    time.sleep(3)
    print(math.factoria(num))

# calling function factorial
factorial(10)

# 等价形式
(calculate_time)(factorial)(10)
= inner1(10)

1.2 装饰器没有参数,被装饰函数有返回值

# 其实和上面的情况没有啥差别, 这个的inner1函数有返回值罢了
def hello_decorator(func):
    def inner1(*args, **kwargs):
        print("Before Execution")
        res = func(*args, **kwargs)
        print("After Execution")
        return res
    return inner1

@hello_decorator
def sum_two_numbers(a, b):
    print("Inside the function")
    return a + b

res = sum_two_numbers(1, 2)
= (hello_decorator)(sum_two_numbers)(1, 2)
=  inner1(1, 2)

1.3 装饰器带有参数

参考资料:
《Decorators with parameters in Python: Example 3》

# Python code to illustrate
# Decorators with parameters in Python  (Multi-level Decorators)
 
 
def decodecorator(dataType, message1, message2):
    def decorator(fun):
        print(message1)
        def wrapper(*args, **kwargs):
            print(message2)
            if all([type(arg) == dataType for arg in args]):
                return fun(*args, **kwargs)
            return "Invalid Input"
        return wrapper
    return decorator
 
 
@decodecorator(str, "Decorator for 'stringJoin'", "stringJoin started ...")
def stringJoin(*args):
    st = ''
    for i in args:
        st += i
    return st
 
 
@decodecorator(int, "Decorator for 'summation'\n", "summation started ...")
def summation(*args):
    summ = 0
    for arg in args:
        summ += arg
    return summ
 
 
print(stringJoin("I ", 'like ', "Geeks", 'for', "geeks"))
print()
print(summation(19, 2, 8, 533, 67, 981, 119))

理解带有参数的装饰器,需要明白以下两点

@decorator
def func_name():
    ...
# 和
decorator(func_name)是等价的

@decorator(param)
def func_name():
    ...
# 和
[decorator(param)](func_name)是等价的

记录个常用的计时装饰器

import time
from functools import wraps

def cal_func_time(show_time=True):
    """
    Calculate running time of functions, and print the time
    """
    def my_decorator(func):
        @wraps(func)
        def measure_time(*args, **kwargs):
            t1 = time.time()
            res = func(*args, **kwargs)
            t2 = time.time()
            if show_time:
                print("Running time of Function {}: {}".format(func.__name__, t2 - t1))
            return res
        return measure_time
    return my_decorator

对于functools.wraps 的作用,《What does functools.wraps do?》解释得非常清楚。

1.4 总结

  • 装饰器的典型行为是:把被装饰的函数替换成新的函数,二者接受相同的参数,而且(通常)返回被装饰的函数本该返回的值,同时还会做些额外的操作。所以上面第三部分的decorator(func)才是真正的装饰器,装饰器的参数是一个函数。
  • 由于装饰器是函数,所以可以组合起来使用。
@d1
@d2
def f():
	print('f')
	
# 等价于

def f():
	print('f')
	
f = d1(d2(f))
  • 当我们讨论decorator时,我们已经接触到一点函数式编程了。因为函数式编程的特点之一是使用高阶函数。高阶函数是接受函数作为参数,或者把函数作为结果返回的函数。

2 functools模块

functools模块作用于高阶函数,在介绍decorator时,我们使用了functools.wraps()装饰器来勾结行为良好的装饰器,这里再介绍《流畅的python》一书中重点介绍的functools.lru_cache()functools.singledispatch()两个装饰器。

2.1 functools.lru_cache

from functools import lru_cache, singledispatch
import time

# lru_cache()可以用来优化递归算法
@lru_cache(maxsize=30)
def fibonacci_with_lru_cache(num):
    return 1 if num < 2 else fibonacci_with_lru_cache(num - 2) + fibonacci_with_lru_cache(num - 1)

def fibonacci(num):
    return 1 if num < 2 else fibonacci(num - 2) + fibonacci(num - 1)


if __name__ ==  "__main__":
    num = 30

    st = time.time()
    fibonacci(num)
    ed = time.time()
    print("Time: {}".format(ed - st))

    st = time.time()
    fibonacci_with_lru_cache(num)
    ed = time.time()
    print("Time: {}".format(ed - st))

2.2 functools.singledispatch

3 property、classmethod和staticmethod

posted @ 2021-12-08 23:00  渐渐的笔记本  阅读(28)  评论(0编辑  收藏  举报