全栈工程师开发手册 (作者:陈玓玏)
python教程全解
学python过程中,常会看到一类用法,就是在函数前面加个@***,类似以下:
@log
def convert(a):
return str(a)
很多的python库也有这个用法,比如Ray,比如pysnooper,都只需要引入这个python库,然后在需要进行并行加速或跟踪执行的函数前面加上@pysnooper.snoop之类的就可以。
那么我们自然会想到三个问题:1. 这种用法学名是什么?2. 这种用法有什么好处?3. 我们平时自己可以怎么用?
这种用法,在python中称为装饰器,decorator,它最大的好处就是可以让我们在基本不改动现有代码的情况下,附加一些功能,而它实现这个目的的方式,就是在将函数作为参数传递给另一个函数,在这个接受函数为入参的函数中,我们可以对入参函数进行一系列代码操作,同时调用此函数,从而实现源代码不改动的情况下,只加一行装饰器代码即可添加一系列功能,并执行原有所有功能的目的。
具体来看栗子。
如果我们想实现跟踪当前调用的函数的函数名,最简单的方法是直接打印:
import logging
def print_log(func):
logging.warning("%s is calling" % func.__name__)
func()
def add_f():
print(1+2)
add_f()
print_log(add_f)
这样我们可以看到输出结果为
add_f函数实现两数相加的功能,print_log接收函数为入参,实现打印目前执行函数的函数名的功能。
但这样操作有几个坏处,第一,我们执行add_f和打印是分开调用的,我们还是希望能只是执行add_f就同时完成两个功能。第二我们只能传入add_f,如果它是一个有入参的函数,我们这样写就会出现错误。第三,我们的print_log能不能接受函数入参之外的入参呢?这样我们能够实现更灵活的附加功能。
关于第一点,我们可以用装饰器来完成。上面的栗子,还不算是个装饰器,只是把函数作为了入参。
import logging
def print_log(func):
def return_f():
logging.warning("%s is calling" % func.__name__)
return func()
return return_f
def add_f():
print(1+2)
add_f = print_log(add_f)
add_f()
在第二个栗子里,我们将add_f作为参数传递给print_log,又将print_log的返回值赋给了add_f。而print_log中,除了第一个栗子中,打印在调用的函数信息外,还加入了调用func的功能,在这里也就是调用add_f,这就同时实现了在调用原来的add_f的基础上,加入了跟踪调用函数的函数名的功能。这就是装饰器了,你看起来我最终调用的还是add_f,但实际我调用的是在add_f的基础上已经加了其他功能的print_log。
更通俗的装饰器写法是使用@
import logging
def print_log(func):
def return_f():
logging.warning("%s is calling" % func.__name__)
return func()
return return_f
@print_log
def add_f():
print(1+2)
add_f()
第一个问题解决了,那么第二个问题,如果add_f是带参数的呢?
import logging
def print_log(func):
def return_f(*args,**kwargs):
logging.warning("%s is calling" % func.__name__)
return func(*args,**kwargs)
return return_f
@print_log
def add_f(a,b):
print(a+b)
add_f(1,2)
使用*args和**kwarges来获取位置参数和关键字参数等可变参数即可。
现在我们来看第三个问题,如何向装饰器传入除func和func的参数之外的参数?
import logging
def deco(info):
def print_log(func):
def return_f(*args,**kwargs):
logging.warning("%s function %s is calling" % (info,func.__name__))
return func(*args,**kwargs)
return return_f
return print_log
@deco('add')
def add_f(a,b):
print(a+b)
add_f(1,2)
我们可以在原来的装饰器之外再封装一层函数,构成一个新的装饰器,最外层的函数即可接受传入装饰器的参数。最后这个示例,我们添加了记录函数的操作类型的功能,当然这个类似是人为传入的参数,执行结果如下:
利用装饰器,我们能够用尽可能少的改动,向所有的函数添加一些比较固定的功能,很方便用来进行代码的测试/跟踪/并行,介绍到这里,enjoy~