这篇文章记录了对python装饰器的理解,主要参考了文章【Python】一文弄懂python装饰器(附源码例子),大部分内容是直接转载的,然后根据自己的理解多加了一些解释说明。
python装饰器理解
- 1 装饰器
- 2 使用装饰器的动机
- 3 简单的装饰器
- 4 装饰器的语法糖@的理解
- 5 被装饰的函数有参数
- 6 装饰器有参数
- 7 类装饰器
- 8 类装饰器有参数
- 9 装饰器顺序
1 装饰器
装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。
装饰器的使用符合了面向对象编程的开放封闭原则。
开放封闭原则主要体现在两个方面:
1. 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
2. 对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。
2 使用装饰器的动机
使用装饰器之前,我们要知道,其实python里是万物皆对象,也就是万物都可传参。
函数也可以作为函数的参数进行传递的。
例子1:
def baiyu():
print("我是攻城狮白玉")
def blog(name):
print('进入blog函数')
name()
print('我的博客是 https://blog.csdn.net/zhh763984017')
if __name__ == '__main__':
func = baiyu # 这里是把baiyu这个函数名赋值给变量func
func() # 执行func函数
print('------------')
blog(baiyu) # 把baiyu这个函数作为参数传递给blog函数
结果:
改写代码获取函数的执行时间,代码2:
import time
def baiyu():
t1 = time.time()
print("我是攻城狮白玉")
time.sleep(2)
print("执行时间为:", time.time() - t1)
def blog(name):
t1 = time.time()
print('进入blog函数')
name()
print('我的博客是 https://blog.csdn.net/zhh763984017')
print("执行时间为:", time.time() - t1)
if __name__ == '__main__':
func = baiyu # 这里是把baiyu这个函数名赋值给变量func
func() # 执行func函数
print('------------')
blog(baiyu) # 把baiyu这个函数作为参数传递给blog函数
结果:
如果有一个新的函数python_blog_list:
def python_blog_list():
print('''【Python】爬虫实战,零基础初试爬虫下载图片(附源码和分析过程)
https://blog.csdn.net/zhh763984017/article/details/119063252 ''')
print('''【Python】除了多线程和多进程,你还要会协程
https://blog.csdn.net/zhh763984017/article/details/118958668 ''')
print('''【Python】爬虫提速小技巧,多线程与多进程(附源码示例)
https://blog.csdn.net/zhh763984017/article/details/118773313 ''')
print('''【Python】爬虫解析利器Xpath,由浅入深快速掌握(附源码例子)
https://blog.csdn.net/zhh763984017/article/details/118634945 ''')
此时,也要测量它的执行时间,则也需要修改为:
def python_blog_list():
t1 = time.time()
print('''【Python】爬虫实战,零基础初试爬虫下载图片(附源码和分析过程)
https://blog.csdn.net/zhh763984017/article/details/119063252 ''')
print('''【Python】除了多线程和多进程,你还要会协程
https://blog.csdn.net/zhh763984017/article/details/118958668 ''')
print('''【Python】爬虫提速小技巧,多线程与多进程(附源码示例)
https://blog.csdn.net/zhh763984017/article/details/118773313 ''')
print('''【Python】爬虫解析利器Xpath,由浅入深快速掌握(附源码例子)
https://blog.csdn.net/zhh763984017/article/details/118634945 ''')
print("执行时间为:", time.time() - t1)
如果每个新函数都要这么改,显然不太方便,所以可采用装饰器来拓展一些原函数没有的功能。
3 简单的装饰器
定义一个测量执行时间的函数count_time,代码3:
import time
def baiyu():
print("我是攻城狮白玉")
time.sleep(2)
def count_time(func):
def wrapper():
t1 = time.time()
func()
print("执行时间为:", time.time() - t1)
return wrapper
if __name__ == '__main__':
baiyu = count_time(baiyu) # 因为装饰器 count_time(baiyu) 返回的是函数对象 wrapper,这条语句相当于 baiyu = wrapper
baiyu() # 执行baiyu()就相当于执行wrapper()
结果:
这里的count_time,返回了一个函数wrapper。把被装饰函数作为参数传递给它,它就返回wrapper函数。不过为了更方便地实现装饰器(即避免baiyu = count_time(baiyu)的使用),可以通过装饰器的语法糖@来实现。
4 装饰器的语法糖@的理解
看了【Python】一文弄懂python装饰器(附源码例子)后,我对@装饰器的理解是:
@A
B
#等价于
B
B=A(B)
现在用@实现代码2中的baiyu。代码4:
import time
def count_time(func):
def wrapper():
t1 = time.time()
func()
print("执行时间为:", time.time() - t1)
return wrapper
@count_time
def baiyu():
print("我是攻城狮白玉")
time.sleep(2)
'''
等价于
def baiyu():
print("我是攻城狮白玉")
time.sleep(2)
baiyu = count_time(baiyu)
'''
if __name__ == '__main__':
baiyu() # 用语法糖之后,就可以直接调用该函数了
结果:
5 被装饰的函数有参数
用@实现代码2中的blog。blog带参数,需修改wrapper。代码5:
import time
def count_time(func):
def wrapper(*args,**kwargs):
t1 = time.time()
func(*args,**kwargs)
print("执行时间为:", time.time() - t1)
return wrapper
def baiyu():
print("我是攻城狮白玉")
time.sleep(2)
@count_time
def blog(name):
print('进入blog函数')
name()
print('我的博客是 https://blog.csdn.net/zhh763984017')
'''
等价于
def blog(name):
print('进入blog函数')
name()
print('我的博客是 https://blog.csdn.net/zhh763984017')
blog = count_time(blog) 即 blog = wrapper
'''
if __name__ == '__main__':
blog(baiyu) # wraper(baiyu)
结果:
6 装饰器有参数
当装饰器也有参数时,比如需要给装饰器函数传入一些备注的信息msg,如何实现呢?
根据前面提到的我对@的理解:
@A
B
#等价于
B
B=A(B)
可以得到代码6:
import time
def count_time_args(msg=None):
def count_time(func):
def wrapper(*args, **kwargs):
t1 = time.time()
func(*args, **kwargs)
print(f"[{msg}]执行时间为:", time.time() - t1)
return wrapper
return count_time
@count_time_args(msg="baiyu")
def fun_one():
time.sleep(1)
'''
等价于
def fun_one():
time.sleep(1)
fun_one = count_time_args(msg="baiyu")(fun_one)
'''
@count_time_args(msg="zhh")
def fun_two():
time.sleep(1)
'''
等价于
def fun_two():
time.sleep(1)
fun_two = count_time_args(msg="zhh")(fun_two)
'''
@count_time_args(msg="mylove")
def fun_three():
time.sleep(1)
'''
等价于
def fun_three():
time.sleep(1)
fun_three = count_time_args(msg="mylove")(fun_three)
'''
if __name__ == '__main__':
fun_one()
fun_two()
fun_three()
结果:
7 类装饰器
前面的装饰器都是函数,装饰器也可以是类,它同样符合:
@A
B
#等价于
B
B=A(B)
此时,A是类。当我们初始化类A时,调用的是其__init__()
,当我们调用类A时,则是调用其__call__
。
所以得到代码7:
import time
class BaiyuDecorator:
def __init__(self, func):
self.func = func
print("执行类的__init__方法")
def __call__(self, *args, **kwargs):
print('进入__call__函数')
t1 = time.time()
self.func(*args, **kwargs)
print("执行时间为:", time.time() - t1)
@BaiyuDecorator
def baiyu():
print("我是攻城狮白玉")
time.sleep(2)
'''
等价于
def baiyu():
print("我是攻城狮白玉")
time.sleep(2)
baiyu=BaiyuDecorator(baiyu) #用baiyu初始化一个BaiyuDecorator
'''
def python_blog_list():
time.sleep(5)
print('''【Python】爬虫实战,零基础初试爬虫下载图片(附源码和分析过程)
https://blog.csdn.net/zhh763984017/article/details/119063252 ''')
print('''【Python】除了多线程和多进程,你还要会协程
https://blog.csdn.net/zhh763984017/article/details/118958668 ''')
print('''【Python】爬虫提速小技巧,多线程与多进程(附源码示例)
https://blog.csdn.net/zhh763984017/article/details/118773313 ''')
print('''【Python】爬虫解析利器Xpath,由浅入深快速掌握(附源码例子)
https://blog.csdn.net/zhh763984017/article/details/118634945 ''')
@BaiyuDecorator
def blog(name):
print('进入blog函数')
name()
print('我的博客是 https://blog.csdn.net/zhh763984017')
'''
等价于
def blog(name):
print('进入blog函数')
name()
print('我的博客是 https://blog.csdn.net/zhh763984017')
blog = BaiyuDecorator(blog) #用blog初始化一个BaiyuDecorator
'''
if __name__ == '__main__':
baiyu() #调用类BaiyuDecorator的call函数
print('--------------')
blog(python_blog_list) #调用类BaiyuDecorator的call函数,传参数python_blog_list
结果:
8 类装饰器有参数
仍然根据这个来理解:
@A
B
#等价于
B
B=A(B)
看代码8,已经加了注释:
class BaiyuDecorator:
def __init__(self, arg1, arg2): # init()方法里面的参数都是装饰器的参数
print('执行类Decorator的__init__()方法')
self.arg1 = arg1
self.arg2 = arg2
def __call__(self, func): # 因为装饰器带了参数,所以接收传入函数变量的位置是这里
print('执行类Decorator的__call__()方法')
def baiyu_warp(*args): # 这里装饰器的函数名字可以随便命名,只要跟return的函数名相同即可
print('执行wrap()')
print('装饰器参数:', self.arg1, self.arg2)
print('执行' + func.__name__ + '()')
func(*args)
print(func.__name__ + '()执行完毕')
return baiyu_warp
@BaiyuDecorator('Hello', 'Baiyu')
def example(a1, a2, a3):
print('传入example()的参数:', a1, a2, a3)
'''
等价于
def example(a1, a2, a3):
print('传入example()的参数:', a1, a2, a3)
example = BaiyuDecorator('Hello', 'Baiyu')(example) #装饰器通过BaiyuDecorator('Hello', 'Baiyu')进行初始化,再通过(example)调用call函数,得到返回值baiyu_warp。
'''
if __name__ == '__main__':
print('准备调用example()')
example('Baiyu', 'Happy', 'Coder') # baiyu_warp('Baiyu', 'Happy', 'Coder')
print('测试代码执行完毕')
结果:
9 装饰器顺序
一个函数可以被多个装饰器进行装饰,此时要理解执行顺序,可以再次根据:
@A
B
#等价于
B
B=A(B)
即:
@A
@B
C
# 1把@B C看成一个整体
C=(
@B
C
return C
)#这里不符合python语法,只是为了便于理解
@A
C
# 2再把这个整体进行等价处理
C=(
C
C = B(C)
return C
)#这里不符合python语法,只是为了便于理解
@A
C
# 3即
C
C=B(C)
@A
C
# 4得
C
C=A(B(C))
所以得到代码9:
def BaiyuDecorator_1(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('我是装饰器1')
return wrapper
def BaiyuDecorator_2(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('我是装饰器2')
return wrapper
def BaiyuDecorator_3(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('我是装饰器3')
return wrapper
@BaiyuDecorator_1
@BaiyuDecorator_2
@BaiyuDecorator_3
def baiyu():
print("我是攻城狮白玉")
'''
等价于
def baiyu():
print("我是攻城狮白玉")
baiyu = BaiyuDecorator_1(BaiyuDecorator_2(BaiyuDecorator_3(baiyu)))
'''
if __name__ == '__main__':
baiyu()