Code Now

[Python基础] 装饰器

· LuYanFCP

装饰器是可调用的对象,其参数是另一个函数(被装饰函数)。装饰器可能会处理被装饰的函数,然后把它返回,或者替换成另一个函数或者可调用对象。

1@test
2def target():
3    print("run target function!")

其作用与以下代码一样

1def target():
2    print("run target function!")
3target = test(target) # 在test函数中,会加强函数或者替换函数

函数装饰器在导入模块时理解执行,而被装饰的函数只是在明确调用的时候才运行。

Python不需要声明变量,但是假定在函数定义体重赋值的变量是局部变量。这比javascript的行为好太多,javascript也不需要声明变量,但是如果忘记把变量声明为局部变量(使用var),可能会在不知请的时候获取到全局变量。

一个装饰器的例子—给程序打运行log

目标,每次程序运行都输出log,输出程序运行的时间,结果等等,将log输出到terminal

 1import time
 2def log(func):
 3    def ff(*args, **kwargs):
 4        t0 = time.time()
 5        run_time = time.asctime(time.localtime(t0))
 6        res = func(*args, **kwargs)
 7        cost = time.time() - t0
 8        name = func.__name__
 9        args_str = ', '.join(repr(arg) for arg in args)  # repr转换为人阅读的方式
10        print('[{}] {}({})->{} cust_time={} s'.format(run_time, name, args_str, res, cost))
11        return res
12    return f
13
14@log
15def f(n):
16    if n <= 1:
17        return 1
18    return n * f(n-1)
19
20>> f(5)
21[Fri Mar 20 16:04:05 2020] f(1)->1 cust_time=0.0 s
22[Fri Mar 20 16:04:05 2020] f(2)->2 cust_time=0.0 s
23[Fri Mar 20 16:04:05 2020] f(3)->6 cust_time=0.000997304916381836 s
24[Fri Mar 20 16:04:05 2020] f(4)->24 cust_time=0.000997304916381836 s
25[Fri Mar 20 16:04:05 2020] f(5)->120 cust_time=0.000997304916381836 s
26120

以上的例子就实现了这个功能,但是这个功能还不够完全,因为不支持关键词参数,同时器还会遮盖被装饰函数的__name____doc__等属性引入入functools.wraps来协助装饰器。

 1def log(func):
 2    @functools.wraps(func)
 3    def f(*args, **kwargs):
 4        t0 = time.time()
 5        run_time = time.asctime(time.localtime(t0))
 6        res = func(*args, **kwargs)
 7        cost = time.time() - t0
 8        name = func.__name__
 9        args_str = ', '.join(repr(arg) for arg in args)  # repr转换为人阅读的方式
10        print('[{}] {}({})->{} cust_time={} s'.format(run_time, name, args_str, res, cost))
11        return res
12    return f

参数化装饰器

我们将此见到很多wrap引入参数去辅助,类如

1from functools import lru_cache
2@lru_cache(max_size=16)
3def f(n):
4    if n <= 1:
5        return 1
6    return n * f(n-1)

这个就等价于

1f = lru_cache(max_size=16)(f)

因此如果要添加参数必须要让wraper的函数在多一层,还是以上面的例子,这个时候,可以将fmt当作参数

 1import functools, time
 2fmt='[{}] {}({})->{} cust_time={} s'
 3def log(fmt):
 4    def log_f(func):
 5        @functools.wraps(func)
 6        def f(*args, **kwargs):
 7            t0 = time.time()
 8            run_time = time.asctime(time.localtime(t0))
 9            res = func(*args, **kwargs)
10            cost = time.time() - t0
11            name = func.__name__
12            args_str = ', '.join(repr(arg) for arg in args)  # repr转换为人阅读的方式
13            print(fmt.format(run_time, name, args_str, res, cost))
14            return res
15        return f
16    return log_f

参考资料

《流畅的python》


查看原始Issue