装饰器
在增强原函数的功能的同时,不修改原函数的定义,这种在代码运行期间动态增加功能的方式,就称为装饰器(Decorator)。装饰器,本质是一个返回函数的高阶函数。在了解装饰器之前,也简单介绍下返回函数的相关内容。
返回函数
之前,我们讲过高阶函数,map()
,filter()
函数等高阶函数,能够接受函数作为参数,而函数同样也可以作为结果值返回。
先定义一个简单的函数:
>>> def func():
... print("return function")
上述代码只是普通的一个函数。若是,不需要立即输出,可以返回函数,而不是直接输出:
>>> def return_func():
... def func():
... print("return function")
... return func
当调用 return_func()
时,返回的不是输出结果,而是函数:
>>> f = return_func()
>>> f
<function return_func.<locals>.func at 0x000002C03AF091E0>
当调用 f
时,才输出内容:
>>> f()
return function
这是返回函数的简单应用。
闭包
闭包,是指在一个函数内部定义了另一个函数,而内部函数引用了外部函数的参数和局部变量,当返回内部函数时,相关参数和变量存储在返回的函数中。
下面代码实现一个闭包的操作:
>>> def lazy_sum(*args):
... def sum():
... num = 0
... for x in args:
... num += x
... return num
... return sum
...
这个例子中,内部函数调用了外部函数的参数 args
,当调用 lazy_sum
时,返回的函数存储着相关参数和变量。只有当再次调用返回函数,才会得出运算结果。
>>> f = lazy_sum(1,2,3,4,5)
>>> f
<function lazy_sum.<locals>.sum at 0x000002C03AF31488>
>>> f()
15
这里需要注意,每次调用外部函数,返回的函数都是新的函数,即使传入的参数都相同:
>>> f1 = lazy_sum(1,2,3,4,5)
>>> f2 = lazy_sum(1,2,3,4,5)
>>> f1 is f2
False
>>> f1 == f2
False
该例子中,f1()
和 f2()
的结果互不影响。
还有个需要注意的地方,返回函数不是立刻执行,而是调用了 f()
才执行。尝试用另外一个例子说明这种情况,示例如下:
>>> def count():
... lst = []
... for i in range(1, 4):
... def func():
... return i * i
... lst.append(func)
... return func
...
>>> f1, f2, f3 = count()
在这个例子中,每次循环,都创建一个新的函数,将创建的 3 个函数返回。
这里的结果,可能会猜测调用 f1(), f2(), f3()
结果分别是 1, 4, 9
,但实际结果却都是 9
:
>>> f1()
9
>>> f2()
9
>>> f3()
9
这里是因为返回的函数引用了变量 i
,但是没有立刻执行。等 3 个函数都返回时,引用的变量 i
的值已经全部变成了 3
,所以最终结果是 9
。
所以,返回闭包时,返回函数不要引用循环变量,或者后续会发生变化的值。
装饰器
前面已经说明,装饰器,本质上是一个返回函数的高阶函数。尝试用例子说明,
>>> def log(func):
... def warpper(*args, **kw):
... print('call {}():'.format(func.__name__)
... return func(*args, **kw)
... return wrapper
上面的 log
是一个装饰器,接受函数作为参数,返回函数。借助 Python 的 @
语法,把装饰器置于函数的定义处:
@log
def func():
print("function name")
调用 func()
函数,会运行 func()
本身函数,还会在运行 func()
前,打印一行日志:
>>> func()
call func():
function name
在这里,将 @log
放到 func()
函数的定义处,相当于下列语句:
func = log(func)
由于 log()
是装饰器,返回一个函数。但是,原来的 func()
还存在,只是现在同名的 func()
指向了新的函数,所以调用的 func()
时,执行的将是 wrapper()
函数。在 wrapper()
函数内,首先先打印日志,然后再调用原始函数。
这些就是装饰器的一些内容,至于更深入的部分,后续会继续更新介绍。
以上就是本篇的主要内容