python装饰器理解

发布时间:2023年12月17日

这篇文章记录了对python装饰器的理解,主要参考了文章【Python】一文弄懂python装饰器(附源码例子),大部分内容是直接转载的,然后根据自己的理解多加了一些解释说明。

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()

在这里插入图片描述

文章来源:https://blog.csdn.net/weixin_45034895/article/details/135029346
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。