什么是装饰器呢?
我的理解python中的装饰器就是对一个函数调用前后增加一些新的代码。
装饰器是一个函数,但是它里面重新定义了一个新的函数,这个新的函数里面包含对一个指定函数的调用。最终装饰器返回这个函数的引用。当然装饰器不仅可以作用于一个函数,也可以作用于一个类。
举一个简单的装饰器的例子
def log_func_name(func):
'''打印函数名'''
def wrapper(*args, **kwargs):
print('函数{}正在运行'.format(func.__name__))
func(*args, **kwargs)
return wrapper
@log_func_name
def say_hello(cnt):
'''打印hello'''
for _ in range(cnt):
print('hello')
if __name__ == '__main__':
say_hello(3)
其实@log_func_name
的语法糖,相当于在函数声明后加上say_hello = log_func_name(say_hello)
语句。
所以上面的代码可以等价为
def log_func_name(func):
'''打印函数名'''
def wrapper(*args, **kwargs):
print('函数{}正在运行'.format(func.__name__))
func(*args, **kwargs)
return wrapper
def say_hello(cnt):
'''打印hello'''
for _ in range(cnt):
print('hello')
say_hello = log_func_name(say_hello)
if __name__ == '__main__':
say_hello(3)
如果上述代码在ipython等交互环境上运行,我们还可以运行
say_hello.__name__
# 输出 'wrapper'
由此可以看书say_hello函数并不是我们直接声明的say_hello函数,实际上say_hello指向log_lineno函数返回的wrapper函数。即上文所说,@log_func_name
相当于执行了say_hello = log_lineno(say_hello)这句命令。
问题来了,如果say_hello函数带有参数那该怎么办?因为say_hello其实是指向wrapper的,所以say_hello能接受的参数,wrapper也必须能接受。那么对应say_hello函数的装饰器函数代码要如下所示
def log_func_name(func):
'''打印函数名'''
def wrapper(cnt):
print('函数{}正在运行'.format(func.__name__))
func(cnt)
return wrapper
@log_func_name
def say_hello(cnt):
'''打印hello'''
for _ in range(cnt):
print('hello')
上述方法是最简单粗暴的方法,即保持wrapper函数和say_hello函数的参数一致。
这样问题就来了,一个装饰器不可能只用于一个函数啊,不然直接在那个函数上修改就完事了。
即装饰器的使用范围要有广泛性,比如log_func_name这个装饰器是用于打印函数名,这个装饰器对所有的函数都生效,不管函数多复杂。
这个时候就要用到*args
和**kwargs
,要想知道这两个参数有什么,收到运行下面代码就知道啦
def print_args(*args, **kwargs):
print(args)
print(kwargs)
print_args(1,2,3)
# (1, 2, 3)
# {}
print_args(1,2,3,a=1,b=2)
# (1, 2, 3)
# {'a': 1, 'b': 2, 'c': 3}
可以看书,args就是一个参数的数组,kwargs是一个以参数名作为key的字典(关键字参数的字典)
而*args
前面的*
用于将args
数组解包,即传入的不是args
数组,而是将args
数组里面的元素都当成参数传用。同理**
用于将字典解包。
这样我们就解决了装饰器的适用性问题了,开心!!