python装饰器理解

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

python装饰器理解

  • [1 装饰器](#1 装饰器)
  • [2 使用装饰器的动机](#2 使用装饰器的动机)
  • [3 简单的装饰器](#3 简单的装饰器)
  • [4 装饰器的语法糖@的理解](#4 装饰器的语法糖@的理解)
  • [5 被装饰的函数有参数](#5 被装饰的函数有参数)
  • [6 装饰器有参数](#6 装饰器有参数)
  • [7 类装饰器](#7 类装饰器)
  • [8 类装饰器有参数](#8 类装饰器有参数)
  • [9 装饰器顺序](#9 装饰器顺序)

1 装饰器

装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。

装饰器的使用符合了面向对象编程的开放封闭原则。

开放封闭原则主要体现在两个方面:
	1. 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
	2. 对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。

2 使用装饰器的动机

使用装饰器之前,我们要知道,其实python里是万物皆对象,也就是万物都可传参。

函数也可以作为函数的参数进行传递的。

例子1:

python 复制代码
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:

python 复制代码
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:

python 复制代码
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 ''')

此时,也要测量它的执行时间,则也需要修改为:

python 复制代码
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:

python 复制代码
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装饰器(附源码例子)后,我对@装饰器的理解是:

python 复制代码
@A
B
 
#等价于
B
B=A(B)

现在用@实现代码2中的baiyu。代码4:

python 复制代码
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:

python 复制代码
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,如何实现呢?

根据前面提到的我对@的理解:

python 复制代码
@A
B
 
#等价于
B
B=A(B)

可以得到代码6:

python 复制代码
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 类装饰器

前面的装饰器都是函数,装饰器也可以是类,它同样符合:

python 复制代码
@A
B
 
#等价于
B
B=A(B)

此时,A是类。当我们初始化类A时,调用的是其__init__(),当我们调用类A时,则是调用其__call__

所以得到代码7:

python 复制代码
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 类装饰器有参数

仍然根据这个来理解:

python 复制代码
@A
B
 
#等价于
B
B=A(B)

看代码8,已经加了注释:

python 复制代码
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 装饰器顺序

一个函数可以被多个装饰器进行装饰,此时要理解执行顺序,可以再次根据:

python 复制代码
@A
B
 
#等价于
B
B=A(B)

即:

python 复制代码
@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:

python 复制代码
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()
相关推荐
亚图跨际20 分钟前
Python和R荧光分光光度法
开发语言·python·r语言·荧光分光光度法
谢眠37 分钟前
深度学习day3-自动微分
python·深度学习·机器学习
z千鑫1 小时前
【人工智能】深入理解PyTorch:从0开始完整教程!全文注解
人工智能·pytorch·python·gpt·深度学习·ai编程
MessiGo1 小时前
Python 爬虫 (1)基础 | 基础操作
开发语言·python
肥猪猪爸2 小时前
使用卡尔曼滤波器估计pybullet中的机器人位置
数据结构·人工智能·python·算法·机器人·卡尔曼滤波·pybullet
LZXCyrus2 小时前
【杂记】vLLM如何指定GPU单卡/多卡离线推理
人工智能·经验分享·python·深度学习·语言模型·llm·vllm
Enougme2 小时前
Appium常用的使用方法(一)
python·appium
懷淰メ2 小时前
PyQt飞机大战游戏(附下载地址)
开发语言·python·qt·游戏·pyqt·游戏开发·pyqt5
hummhumm2 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j