IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> Python装饰器 -> 正文阅读

[Python知识库]Python装饰器

Python装饰器:函数闭包的语法糖

预备知识

闭包:又称为闭包函数或者闭合函数。闭包是一种特殊的函数,这种函数由两个函数的嵌套组成,且称之为外函数和内函数,外函数返回值是内函数的引用,此时就构成了闭包。格式如下:

def 外层函数(参数):
    def 内层函数():
        print("内层函数执行", 参数)

    return 内层函数


内层函数的引用 = 外层函数("传入参数")
内层函数的引用()

1.什么是装饰器

??装饰器就是一个闭包,装饰器是闭包的一种应用。用于拓展原来函数功能的一种函数,可以在不更改原函数代码的前提下给函数增加新的功能。

2.为什么要使用装饰器

装饰器的使用符合了面向对象编程的开放封闭原则。
?
开放封闭原则主要体现在两个方面:

  • 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
  • 对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类尽任何修改。

业务用途:装饰器经常用于有切面需求的场景。比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。

3.引出装饰器

需求:为下列info函数添加统计运行时间的辅助功能。

def info():
	print('这是一个函数!')

info()

解决方案一:在函数源代码上添加运行时间功能。

import time
def info():
    start = time.time()
    print('这是一个函数!')
    stop = time.time()
    print(stop - start)

info()

缺点:函数逻辑和辅助功能耦合在一起了。不方便修改,容易引起bug。


解决方案二:将辅助功能从函数中抽离。定义辅助函数count_time,在辅助函数count_time调用函数info

import time
def count_time(func):
    start = time.time()
    func()
    stop = time.time()
    print(stop - start)
    
def info():
    print('这是一个函数!')

count_time(info)

优点:解耦,函数职责分离。
缺点:要通过辅助函数来调用主要功能函数,不方便。


解决方案三:在调用主要功能函数时自动完成对运行时间的统计。通过闭包增强主要功能函数count_time,给它增加一个运行时间统计功能。

import time

def count_time(func):
    def improved_func():
        start = time.time()
        func()
        stop = time.time()
        print(stop - start)
    return improved_func
    
def info():
    print('这是一个函数!')

info = count_time(info)     # 左边的info只是个变量,作为指针。也可改为info_1 = count_time(info),相应的,下面改为info_1()
info()

缺点:需要显式进行闭包增强。即info = count_time(info)

4.语法糖:让你开心的语法

??语法糖指计算机中添加的某种语法,这种语法对语言的功能没有影响,但是更方便使用。
??对上述解决方案三使用@操作,可以省去显式闭包增强的步骤,直接调用info() 即可得到想要的结果。@count_time等价于info = count_time(info)

import time
def count_time(func):
    def improved_func():
        start = time.time()
        func()
        stop = time.time()
        print(stop - start)
    return improved_func

@count_time    
def info():
    print('这是一个函数!')

info()

优点:利用装饰器及其语法糖操作,提高了程序的可重复利用性,并增加了程序的可读性。

5.装饰器进一步理解1

思考:为什么装饰器要像解决方案三一样,必须写内嵌函数,像下面这样不行吗?

import time
def count_time(func):
    start = time.time()
    func()
    stop = time.time()
    print(stop - start)

@count_time  
def info():
    print('这是一个函数!')
    
info()

执行如下代码,观察结果:
执行一

import time
def count_time(func):
    start = time.time()
    func()
    stop = time.time()
    print(stop - start)
    return func

@count_time  
def info():
    print('这是一个函数!')

输出结果如下:

这是一个函数!
0.0

观察结果:

  • @count_time等价于info=count_time(info)
  • 执行一中的装饰器没有嵌套内函数
  • 没有对info进行调用也输出了装饰效果

执行二

import time
def count_time(func):
    start = time.time()
    func()
    stop = time.time()
    print(stop - start)
    return func

@count_time  
def info():
    print('这是一个函数!')

info()
info()

输出结果如下:

这是一个函数!
0.0
这是一个函数!
这是一个函数!

观察结果:

  • 当两次调用info()的时候,装饰器函数只被调用了一次
  • 执行二中的装饰器也没有嵌套内函数

结果分析

  • 在执行一中,info利用@count_time进行装饰,运行即执行info=count_time(info)这句代码,在count_time中,info作为参数传入,先调用函数info(),返回info的指针,赋值给info。count_time(info)的返回值其实就是函数info()的地址。
  • 在执行二中,由于@count_time等价于info= count_time(info)这句代码,即@count_time可以改写成info= count_time(info),只执行了一次,因此装饰器函数只被调用了一次。后续两次调用info(),其实与info()是否被装饰无关。
  • 要确保每次调用功能函数时,装饰器都能被调用,必须使用内嵌包装函数,即装饰器必须为闭包函数。内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象。

6.多个装饰器的原理2

观察如下执行结果

def dec1(func):  
    print("1111")  
    def one():  
        print("2222")  
        func()  
        print("3333")  
    return one  
  
def dec2(func):  
    print("aaaa")  
    def two():  
        print("bbbb")  
        func()  
        print("cccc")  
    return two  
 
@dec1  
@dec2  
def test():  
    print("test test")  
  
test()  
aaaa  
1111  
2222  
bbbb  
test test  
cccc  
3333

Tips:装饰器的外函数和内函数之间的语句是没有装饰到目标函数上的,而是在装载装饰器时的附加操作。即上面的print("1111")print("aaaa")

执行顺序分析
??可以看到,多个装饰器的情况下,先是自底向上完成装饰器的定义,然后自顶向下执行装饰器的功能。17~20行是自底向上完成装饰器的过程,顺序是test=dect1(dect2(test)),即test被赋值为dect1内函数,作为指针指向内函数的内存地址;22行是自顶向下执行被装饰函数的过程,调用了test指针指向的函数,即test()。
自底向上完成装饰器
step1:先执行dect2(test),dect2对test进行装饰。输出aaaa,返回函数two(函数two中的func指向函数test)。
step2:然后执行dect1(two),dect1对two进行装饰。输出1111,返回函数one(函数one中的func指向函数two)。
step3:test被赋值,作为指针指向one函数。
自顶向下执行装饰器
step4:调用test(),即执行one函数,one()
step5:在函数dect1的内函数one中,输出2222,func指向函数two,跳到函数two,执行函数two
step6:在函数dect2的内函数two中,输出bbbb,func指向函数test,跳到函数test,执行函数test
step7:函数test中,结果是输出test test,函数test执行完毕
step8:回到函数dect2的内函数two中,输出cccc,函数two执行完毕
step9:回到dect1的内函数one中,输出3333,函数one执行完毕
step10:整个装饰执行过程完毕!

7.被装饰函数带参数和返回值

问题:上述被装饰函数均没有参数和返回值,对于带参数和返回值的函数,装饰器又该是什么样的呢?

探索一:带参数

例如,对如下函数添加计算运行时间的装饰器。

def add(x=1,y=2):
    return x+y

直接运用上述count_time装饰器进行探寻。

import time
def count_time(func):
    def improved_func():
        start = time.time()
        func()
        stop = time.time()
        print(stop - start)
    return improved_func

@count_time  
def add(x,y):
    print('x+y')

add(1,2)

运行结果如下:
add(1,2) TypeError: improved_func() takes 0 positional arguments but 2 were given

装饰器count_time无法传入函数add需要的参数值,因此需要接收函数add的参数。改写如下:

import time
def count_time(func):
    def improved_func(*args,**kwargs):
        start = time.time()
        func(*args,**kwargs)
        stop = time.time()
        print(stop - start)
    return improved_func

@count_time  
def add(x,y):
    print(x+y)

add(1,2)

运行结果如下:

3
0.0

探索二:带参数和返回值
例如,对如下函数添加计算运行时间的装饰器。

def add(x,y):
    return x+y

直接运用探索一接收参数的count_time装饰器进行探索。

import time
def count_time(func):
    def improved_func(*args,**kwargs):
        start = time.time()
        func(*args,**kwargs)
        stop = time.time()
        print(stop - start)
    return improved_func

@count_time  
def add(x,y):
    return x+y

print(add(3,4))

运行结果如下:

0.0
None

装饰器count_time无法返回函数add的返回值,因此需要返回函数add的返回值。改写如下:

import time
def count_time(func):
    def improved_func(*args,**kwargs):
        start = time.time()
        ret = func(*args,**kwargs)
        stop = time.time()
        print(stop - start)
        return ret
    return improved_func

@count_time  
def add(x,y):
    return x+y

print(add(3,4))

运行结果如下:

7
0.0

8.装饰器自带参数

需求:当多个函数需要统计运行时间时,为了输出显示是哪个函数的运行时间,装饰器自身需要带参,传入参数为被装饰函数的函数名称,因此可在上述装饰器的外层再嵌套一个函数,参数为装饰器自身参数。如下所示:

import time
def dec(func_name):
    def count_time(func):
        def improved_func(*args,**kwargs):
            start = time.time()
            ret = func(*args,**kwargs)
            stop = time.time()
            print('{}运行时间为'.format(func_name),stop - start)
            return ret
        return improved_func
    return count_time

@dec('add函数')
def add(x,y):
    return x+y

print(add(1,2))

运行结果为:

add函数运行时间为 0.0
3

  1. Python中装饰器的原理是什么 ??

  2. python装饰器详解 ??

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2022-03-15 22:28:05  更:2022-03-15 22:31:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/29 14:06:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计