深入了解python装饰器

 更新时间:2022年03月02日 10:13:31   作者:xiaotanggao  
这篇文章主要介绍了解python装饰器,装饰器定义一个为其他函数添加功能的函数,装饰器就是在不修改装饰对象源代码以及调用方式的前提下,为装饰对象添加新功能,下面和小编一起进入文章内容了解更多知识,需要的朋友可以参考一下

一、装饰器

1.相关知识点

  • *args:负责将多余的位置实参汇总,赋值给args
  • **kwargs:负责将多余的关键字实参汇总,赋值给kwargs

命名空间与作用域

函数对象:

  • 可以把函数当成参数传入
  • 可以把函数当做返回值返回

函数的嵌套定义:在函数内定义函数

闭包函数:父函数的返回值为一个函数,被返回的函数调用了父函数的局部变量,且该函数可以在父函数外部执行

装饰器:

装饰器:定义一个为其他函数添加功能的函数

为什么要使用装饰器?

  • 开放封闭原则:开放扩展功能但封闭源代码的修改
  • 装饰器就是在不修改装饰对象源代码以及调用方式的前提下,为装饰对象添加新功能

装饰器实现:

需求:不修改源代码,计算代码执行时间

 源代码:

import time
def func0(x):
    time.sleep(1)
    print(x)
func0(0)

# 方案一:实现了计算代码执行时间的功能,但修改了源代码,不符合要求
def func1(x):
    start = time.time()
    time.sleep(1)
    print(x)
    end = time.time()
    print('方案一 运行时间:{}'.format(end - start))
func1(1)

# 方案二:满足要求,但代码不具备重用性
start = time.time()
func0(2)
end = time.time()
print('方案二 运行时间:{}'.format(end - start))

# 方案三:将方案二封装为函数,但修改了函数的调用方式,不符合要求
def wrapper():
    start = time.time()
    func0(3)
    end = time.time()
    print('方案三 运行时间:{}'.format(end - start))
wrapper()

# 方案四:不修改原函数的调用方式
def wrapper(x):
    start = time.time()
    func0(x)
    end = time.time()
    print('方案四 运行时间:{}'.format(end - start))
wrapper(4)

# 方案五:方案四参数被写死,优化方案四
def wrapper(*args, **kwargs):
    start = time.time()
    func0(*args, **kwargs)
    end = time.time()
    print('方案五 运行时间:{}'.format(end - start))
wrapper(5)

# 方案六:方案五函数被写死,优化方案五,但修改了源代码的调用方式
def outter(a):
    def wrapper(*args, **kwargs):
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('方案六 运行时间:{}'.format(end - start))
    return wrapper
f = outter(func0)
f(6)

# 方案七:优化方案六(outter即为装饰器,用于装饰func0)
def outter(a):
    def wrapper(*args, **kwargs):
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('方案七 运行时间:{}'.format(end - start))
    return wrapper
func0 = outter(func0)    # 偷梁换柱,调用者无感知
func0(7)

运行结果:

0
1
方案一 运行时间:1.001857042312622
2
方案二 运行时间:1.0040733814239502
3
方案三 运行时间:1.0017154216766357
4
方案四 运行时间:1.007995367050171
5
方案五 运行时间:1.0145602226257324
6
方案六 运行时间:1.0046615600585938
7
方案七 运行时间:1.0094060897827148

2.语法糖

  • 使用语法糖,需要将装饰器放到被装饰对象前
# 不使用语法糖
import time
def func0(x):
    time.sleep(1)
    print(x)
# func0(0)

def outter(a):
    def wrapper(*args, **kwargs):
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('运行时间:{}'.format(end - start))
    return wrapper
func0 = outter(func0)
func0('hello')

# 使用语法糖
import time

def outter(a):
    def wrapper(*args, **kwargs):
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('运行时间:{}'.format(end - start))
    return wrapper

@outter # 语法糖,等价于该行func0 = outter(func0)
def func0(x):
    time.sleep(1)
    print(x)

func0('hello')
print(func0.__name__) 

运行结果:

hello
运行时间:1.0050427913665771
wrapper

3.装饰器模板

 # 装饰器模板
 def decorator_name(x):
     def wrapper(*args, **kwargs):
         # ...新添加的功能...
         # 下为调用原函数的格式
         x(*args, **kwargs)
     return wrapper

@decorator_name
 def func_name():
     pass

扩展:真正实现偷梁换柱,调用者无感知

  • 不使用装饰器
 # 不使用装饰器
import time


def func0(x):
    time.sleep(1)
    print(x)

func0('hello')
print(func0.__name__) # 查看函数名 
print(help(func0)) # 查看帮助信息(主要为注释内容)

运行结果:

hello
func0
Help on function func0 in module main:

func0(x)
这是函数

None

使用装饰器:

import time

def outter(a):
    def wrapper(*args, **kwargs):
        '''这是装饰器'''
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('运行时间:{}'.format(end - start))
    return wrapper

@outter # func0 = outter(func0)
def func0(x):
    '''这是函数'''
    time.sleep(1)
    print(x)

func0('hello')
print(func0.__name__)
print(help(func0))

运行结果:

hello
运行时间:1.011878490447998
wrapper
Help on function wrapper in module main:

wrapper(*args, **kwargs)
这是装饰器

None

呕吼,露馅了

  • 解决方法,将原函数的属性和方法,赋值给装饰器内的wrapper
import time

def outter(a):
    def wrapper(*args, **kwargs):
        '''这是装饰器'''
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('运行时间:{}'.format(end - start))
    wrapper.__name__ = a.__name__
    wrapper.__doc__ = a.__doc__
    return wrapper

@outter # func0 = outter(func0)
def func0(x):
    '''这是函数'''
    time.sleep(1)
    print(x)

func0('hello')
print(func0.__name__)
print(help(func0))

运行结果:

hello
运行时间:1.0030155181884766
func0
Help on function func0 in module main:

func0(*args, **kwargs)
这是函数

None

但是,函数有很多属性和方法,一个一个手动修改过于麻烦,甚至可能会遗漏,但python也提供了解决方法

import time
from functools import wraps

def outter(a):
    @wraps(a)
    def wrapper(*args, **kwargs):
        '''这是装饰器'''
        start = time.time()
        a(*args, **kwargs)
        end = time.time()
        print('运行时间:{}'.format(end - start))
    # wrapper.__name__ = a.__name__
    # wrapper.__doc__ = a.__doc__
    return wrapper

@outter # func0 = outter(func0)
def func0(x):
    '''这是函数'''
    time.sleep(1)
    print(x)

func0('hello')
print(func0.__name__)
print(help(func0))

运行结果:

hello
运行时间:1.0114128589630127
func0
Help on function func0 in module main:

func0(x)
这是函数

None

4.有参装饰器

  • 装饰器内需要传入参数,但是由于语法糖的限制,装饰器只能有一个参数,并且该参数仅用来接收被装饰对象的内存地址,如何传入其他参数?
  • 解决思路:将装饰器做成闭包函数
def outter(db_type):
    # 装饰器auth
    def auth(x):
        def wrapper(*args, **kwargs):
            if db_type == 'file':
                name = input('请输入姓名:')
                passwd = input('请输入密码:')
                if name == 'zhangsan' and passwd == '123':
                    x(*args, **kwargs)
                    print('基于文件认证')
                else:
                    print('用户名或密码错误,认证失败')
            elif db_type == 'mysql':
                x(*args, **kwargs)
                print('基于数据库认证')
            else:
                print('未知认证方式,不支持')
        return wrapper
    return auth

# 函数
auth = outter(db_type='file')
@auth
def file():
    print('hello')


auth = outter(db_type='mysql')
@auth
def mysql():
    print('world')

msg = input('请选择认证方式(1=file|2=mysql):').strip()
if msg =='1':
    file()
elif msg == '2':
    mysql()
else:
    print('不支持')
  • 将传入的参数写入语法糖
def outter(db_type):
    # 装饰器auth
    def auth(x):
        def wrapper(*args, **kwargs):
            if db_type == 'file':
                name = input('请输入姓名:')
                passwd = input('请输入密码:')
                if name == 'zhangsan' and passwd == '123':
                    x(*args, **kwargs)
                    print('基于文件认证')
                else:
                    print('用户名或密码错误,认证失败')
            elif db_type == 'mysql':
                x(*args, **kwargs)
                print('基于数据库认证')
            else:
                print('未知认证方式,不支持')
        return wrapper
    return auth

# 函数
# auth = outter(db_type='file')
# @auth
@outter(db_type='file')
def file():
    print('hello')

# auth = outter(db_type='mysql')
# @auth
@outter(db_type='mysql')
def mysql():
    print('world')

msg = input('请选择认证方式(1=file|2=mysql):').strip()
if msg =='1':
    file()
elif msg == '2':
    mysql()
else:
    print('不支持')
  • 模板
# 有参装饰器模板
def out_decorator_name(x,y,z):
    def decorator_name(a):
        def wrapper(*args, **kwargs):
            # ...新添加的功能...
            # 下为调用原函数的格式
            a(*args, **kwargs)
        return wrapper
    return decorator_name

@out_decorator_name(x,y,z=1)
def func_name():
     pass

到此这篇关于深入了解python装饰器的文章就介绍到这了,更多相关python装饰器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解在OpenCV中实现的图像标注技术

    详解在OpenCV中实现的图像标注技术

    图像标注在计算机视觉中很重要,计算机视觉是一种技术,它允许计算机从数字图像或视频中获得高水平的理解力,并以人类的方式观察和解释视觉信息,本文将重点讨论在OpenCV的帮助下创建这些注释,感兴趣的朋友一起看看吧
    2022-06-06
  • 使用Matplotlib绘制不同颜色的带箭头的线实例

    使用Matplotlib绘制不同颜色的带箭头的线实例

    这篇文章主要介绍了使用Matplotlib绘制不同颜色的带箭头的线实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-04-04
  • python re.match函数的具体使用

    python re.match函数的具体使用

    本文主要介绍了python re.match函数的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • Python实现的调用C语言函数功能简单实例

    Python实现的调用C语言函数功能简单实例

    这篇文章主要介绍了Python实现的调用C语言函数功能,结合简单实例形式分析了Python使用ctypes模块调用C语言函数的具体步骤与相关操作技巧,需要的朋友可以参考下
    2019-03-03
  • 如何用Python一次性下载抖音上音乐

    如何用Python一次性下载抖音上音乐

    不知道什么时候开始,中国出现了南抖音、北快手的互文格局。喜欢抖音主要是两个初衷,学做菜听音乐。抖音捧红了很多人,也让很多本不怎么让大家熟知的歌曲、BGM,经过翻唱、混剪与视频搭配,从而传播大街小巷。有没有想过将这些好听的剪辑批量下载下来呢?
    2021-05-05
  • python tkinter中的锚点(anchor)问题及处理

    python tkinter中的锚点(anchor)问题及处理

    这篇文章主要介绍了python tkinter中的锚点(anchor)问题及处理方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • 在VScode中配置Python开发环境的超详细指南

    在VScode中配置Python开发环境的超详细指南

    在使用VSCode编写Python代码前,我们需要先配置Python环境,这篇文章主要给大家介绍了关于在VScode中配置Python开发环境的相关资料,需要的朋友可以参考下
    2023-12-12
  • python实现的config文件读写功能示例

    python实现的config文件读写功能示例

    这篇文章主要介绍了python实现的config文件读写功能,结合实例形式分析了Python文件读写相关操作技巧,需要的朋友可以参考下
    2019-09-09
  • python图片和二进制转换的三种实现方式

    python图片和二进制转换的三种实现方式

    本文介绍了将PIL格式、数组和图片转换为二进制的不同方法,包括使用PIL库、OpenCV和直接读取二进制,此外,还提到了数据传输中base64格式的应用,这些信息对需要进行图片数据处理和转换的开发者非常有用
    2024-09-09
  • tensorflow获取变量维度信息

    tensorflow获取变量维度信息

    这篇文章主要为大家详细介绍了tensorflow获取变量维度信息,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03

最新评论