首页 > 编程语言 >【python基础之装饰器】---装饰器

【python基础之装饰器】---装饰器

时间:2023-12-12 14:46:33浏览次数:35  
标签:函数 python wrapper --- func time home 装饰 def

title:  【python基础之装饰器】---装饰器
date:  2023-12-11  18:54:06 
updated: 2023-12-12 14:30:00
description: 
cover: 
       https://home.cnblogs.com/u/dream-ze/

image

【一】什么是装饰器

  • 装饰 代指为被装饰对象添加新的功能, 代指器具/工具,装饰器与被装饰的对象均可以是任意可调用对象。
  • 概括地讲,装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能。
  • 装饰器经常用于有切面需求的场景
    • 插入日志、性能测试、事务处理、缓存、权限校验等应用场景
    • 装饰器是解决这类问题的绝佳设计
    • 有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

提示:可调用对象有函数,方法或者类,此处我们单以本章主题函数为例,来介绍函数装饰器,并且被装饰的对象也是函数。

【二】装饰器的用途

  • 软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。
    • 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
    • 对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。
  • 软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃
    • 而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。

【三】装饰器的分类

  • 函数装饰器分为:无参装饰器和有参装饰两种
  • 二者的实现原理一样,都是’函数嵌套+闭包+函数对象’的组合使用的产物。

【四】无参装饰器

【1】引入

  • 如果想为下述函数添加统计其执行时间的功能
import time

def index():
    time.sleep(3)
    print('Welcome to the index page’)
    return 200

index() #函数执行

【2】直接计时

  • 遵循不修改被装饰对象源代码的原则,我们想到的解决方法可能是这样
import time


def index():
    time.sleep(3)
    print("Welcome to the index page")
    return 200


start_time = time.time()
index()  # 函数执行
stop_time = time.time()
print('run time is %s' % (stop_time - start_time))

【3】函数作为参数

  • 考虑到还有可能要统计其他函数的执行时间
    • 于是我们将其做成一个单独的工具,函数体需要外部传入被装饰的函数从而进行调用
    • 我们可以使用参数的形式传入
import time


def index():
    time.sleep(3)
    print("Welcome to the index page")
    return 200


def wrapper(func):  # 通过参数接收外部的值
    start_time = time.time()
    res = func()
    stop_time = time.time()
    print('run time is %s' % (stop_time - start_time))
    return res


wrapper(index)
  • 但之后函数的调用方式都需要统一改成
wrapper(index)
wrapper(其他函数)
  • 这便违反了不能修改被装饰对象调用方式的原则
    • 于是我们换一种为函数体传值的方式,即将值包给函数

【4】将值包给函数

import time


def index():
    time.sleep(3)
    print("Welcome to the index page")
    return 200


def timer(func):  # 通过参数接收外部的值
    def wrapper():
        start_time = time.time()
        res = func()
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))

    return inner


wrapper = timer(index)
wrapper()
  • 这样我们便可以在不修改被装饰函数源代码和调用方式的前提下为其加上统计时间的功能
  • 只不过需要事先执行一次timer将被装饰的函数传入,返回一个闭包函数wrapper重新赋值给变量名 /函数名index
index=timer(index)  #得到index=wrapper,wrapper携带对外作用域的引用:func=原始的index
index() # 执行的是wrapper(),在wrapper的函数体内再执行最原始的index
  • 至此我们便实现了一个无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。

【5】问题:带参数会报错

  • 但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常
import time


def home(name):
    time.sleep(5)
    print('Welcome to the home page', name)


def wrapper(func):  # 通过参数接收外部的值
    def inner():
        start_time = time.time()
        res = func()
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))

    return inner


home = wrapper(home)
home()
'''
Traceback (most recent call last):
  File "E:\PythonProjects\def_func.py", line 322, in <module>
    home()
  File "E:\PythonProjects\def_func.py", line 314, in inner
    res = func()
TypeError: home() missing 1 required positional argument: 'name'
'''

【6】无参装饰器模板

# 通过参数 func 接收外部的函数地址
def wrapper(func):
    def inner():
        # 第一部分:执行外部传入的函数之前执行的代码
        '''...'''
        # 第二部分:执行外部传入的函数地址(res接受外部函数的返回值,如果有返回值的情况下)
        result = func()
        # 第三部分:执行外部传入的函数之后执行的代码
        '''...'''
        return result
    return inner

【五】有参装饰器

【1】有参装饰器详解

  • 之所以会抛出异常,是因为home(egon)调用的其实是wrapper(egon),而函数wrapper没有参数。
    • wrapper函数接收的参数其实是给最原始的func用的
    • 为了能满足被装饰函数参数的所有情况,便用上*args+**kwargs组合
    • 于是修正装饰器timer如下
import time


def home(name):
    time.sleep(5)
    print('Welcome to the home page', name)


# 定义外层函数
def wrapper(func):
    # 定义内层函数:通过参数接收外部的值
    def inner(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))

    return inner


# 这里调用外层函数 wrapper 返回了内层函数的地址 inner
home = wrapper(home)
# 而内层函数其实是 inner(*args, **kwargs)
# 所以我们可以传参数进去
home("Dream")

【2】有参装饰器模版

# 通过参数 func 接收外部的函数地址
def wrapper(func):
    def inner(*args, **kwargs):
        # 第一部分:执行外部传入的函数之前执行的代码
        '''...'''
        # 第二部分:执行外部传入的函数地址(res接受外部函数的返回值,如果有返回值的情况下)
        result = func(*args, **kwargs)
        # 第三部分:执行外部传入的函数之后执行的代码
        '''...'''
        return result
    return inner

【六】无参语法糖

【1】装饰器内函数无参语法糖

  • 此时我们就可以用timer来装饰带参数或不带参数的函数了
    • 但是为了简洁而优雅地使用装饰器,Python提供了专门的装饰器语法来取代index=timer(index)的形式
    • 需要在被装饰对象的正上方单独一行添加 @timer
    • 当解释器解释到 @timer 时就会调用timer函数
    • 且把它正下方的函数名当做实参传入
    • 然后将返回的结果重新赋值给原函数名
import time


def home(name):
    time.sleep(5)
    print('Welcome to the home page', name)


# 定义外层函数
def wrapper(func):
    # 定义内层函数:通过参数接收外部的值
    def inner(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))

    return inner


# 第一种调用方式
# 这里调用外层函数 wrapper 返回了内层函数的地址 inner
home = wrapper(home)
# 而内层函数其实是 inner(*args, **kwargs)
# 所以我们可以传参数进去
home("Dream")


# 第二种调用方式:语法糖
@wrapper
def index():
    time.sleep(5)
    print('Welcome to the index page')


index()

【2】装饰器内函数有参语法糖

import time


def home(name):
    time.sleep(5)
    print('Welcome to the home page', name)


# 定义外层函数
def wrapper(func):
    # 定义内层函数:通过参数接收外部的值
    def inner(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))

    return inner


# 第一种调用方式
# 这里调用外层函数 wrapper 返回了内层函数的地址 inner
home = wrapper(home)
# 而内层函数其实是 inner(*args, **kwargs)
# 所以我们可以传参数进去
home("Dream")


# 第二种调用方式:语法糖
@wrapper  # index = wrapper(index) ----> index()
def index(name):
    time.sleep(5)
    print('Welcome to the index page', name)


index("Dream")

【3】有参语法糖

【七】有参语法糖

【1】有参语法糖推导

  • 了解无参装饰器的实现原理后
    • 我们可以再实现一个用来为被装饰对象添加认证功能的装饰器
    • 实现的基本形式如下
# 通过参数 func 接收外部的函数地址
def wrapper(func):
    def inner(*args, **kwargs):
        # 第一部分:执行外部传入的函数之前执行的代码
        '''...'''
        # 第二部分:执行外部传入的函数地址(res接受外部函数的返回值,如果有返回值的情况下)
        result = func(*args, **kwargs)
        # 第三部分:执行外部传入的函数之后执行的代码
        '''...'''
        return result
    return inner
  • 如果我们想提供多种不同的认证方式以供选择,单从wrapper函数的实现角度改写如下
def deco(func):
    def wrapper(*args,**kwargs):
        if driver == 'file':
            # 编写基于文件的认证,认证通过则执行
            res=func(*args,**kwargs)
            # 并返回res
            return res
        elif driver == 'mysql':
            # 编写基于mysql认证,认证通过则执行
            res=func(*args,**kwargs)
            # 并返回res
            return res
    return wrapper
  • 函数wrapper需要一个driver参数,而函数deco与wrapper的参数都有其特定的功能
    • 不能用来接受其他类别的参数,可以在deco的外部再包一层函数auth,用来专门接受额外的参数
    • 这样便保证了在auth函数内无论多少层都可以引用到
def auth(driver):
    def deco(func):
        def wrapper(*args, **kwargs):
            if driver == 'file':
                # 编写基于文件的认证, 认证通过则执行
                res = func(*args, **kwargs)
                # 并返回res
                return res
            elif driver == 'mysql':
                # 编写基于mysql认证, 认证通过则执行
                res = func(*args, **kwargs)
                # 并返回res
                return res
        return wrapper
    return deco

【2】有参语法糖使用

  • 此时我们就实现了一个有参装饰器,使用方式如下
def auth(driver):
    def deco(func):
        def wrapper(*args, **kwargs):
            if driver == 'file':
                # 编写基于文件的认证, 认证通过则执行
                res = func(*args, **kwargs)
                # 并返回res
                return res
            elif driver == 'mysql':
                # 编写基于mysql认证, 认证通过则执行
                res = func(*args, **kwargs)
                # 并返回res
                return res

        return wrapper

    return deco


# 先调用auth_type(driver='file'),得到@deco
# deco是一个闭包函数,包含了对外部作用域名字driver的引用
# @deco的语法意义与无参装饰器一样
@auth(driver='file')
def index():
    pass


@auth(driver='mysql')
def home():
    pass

【八】伪装装饰器

【1】查看装饰器(help)

  • 可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性
    • 但对于被装饰之后的函数,查看文档注释
import time


# 定义外层函数
def wrapper(func):
    # 定义内层函数:通过参数接收外部的值
    def inner(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))

    return inner


@wrapper
def home(name):
    '''
    home page function
    :param name: str
    :return: None
    '''
    time.sleep(5)
    print('Welcome to the home page', name)


print(help(home))
'''
打印结果:

Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

None
'''
  • 在被装饰之后home=wrapper
    • 查看home.name也可以发现home的函数名确实是wrapper
    • 想要保留原函数的文档和函数名属性,需要修正装饰器
import time


# 定义外层函数
def wrapper(func):
    # 定义内层函数:通过参数接收外部的值
    def inner(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))

    inner.__doc__ = func.__doc__
    inner.__name__ = func.__name__
    return inner


@wrapper
def home(name):
    '''
    home page function
    :param name: str
    :return: None
    '''
    time.sleep(5)
    print('Welcome to the home page', name)


print(help(home))
'''
打印结果:

Help on function home in module __main__:

home(*args, **kwargs)
    home page function
    :param name: str
    :return: None

None
'''

【2】伪装装饰器(functools)

  • 按照上述方式来实现保留原函数属性过于麻烦
    • functools模块下提供一个装饰器wraps专门用来帮我们实现这件事
import time
from functools import wraps


# 定义外层函数
def wrapper(func):
    # 伪装内层函数
    @wraps(func)
    # 定义内层函数:通过参数接收外部的值
    def inner(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))

    return inner


@wrapper
def home(name):
    '''
    home page function
    :param name: str
    :return: None
    '''
    time.sleep(5)
    print('Welcome to the home page', name)


print(help(home))
'''
打印结果:

Help on function home in module __main__:

home(name)
    home page function
    :param name: str
    :return: None

None
'''

标签:函数,python,wrapper,---,func,time,home,装饰,def
From: https://www.cnblogs.com/queryH/p/17896762.html

相关文章

  • MBR30200FCT-ASEMI大电流肖特基MBR30200FCT
    编辑:llMBR30200FCT-ASEMI大电流肖特基MBR30200FCT型号:MBR30200FCT品牌:ASEMI封装:TO-220F最大平均正向电流:30A最大重复峰值反向电压:200V产品引线数量:3产品内部芯片个数:2产品内部芯片尺寸:122MIL峰值正向漏电流:<10ua恢复时间:5ns浪涌电流:275A芯片材质:最大正向电压:0.85V~0......
  • 【python基础之函数对象和闭包】 --- 函数对象与闭包
    title:【python基础之函数对象和闭包】---函数对象与闭包date:2023-12-1119:20:00updated:2023-12-1119:20:00description:cover:https://home.cnblogs.com/u/dream-ze/【一】函数对象函数对象指的是函数可以被当做数据来处理具体可以分为四......
  • openGauss-预编译慢问题
    背景:使用opengauss3.0.3,JDBC使用占位符,PreparedStatement的方法测试,因为是压测和业务场景特殊,每次使用的参数都一致。现象:1、不传参的方法,将where条件变量写死,执行时间很快2、修改enable_pbe_optimization=off,发现执行5次之后,依然慢分析:查看where条件带值的SQL执行计划,和使......
  • 无涯教程-MFC - Date & Time Picker函数
    日期和时间选择控件(CDateTimeCtrl)实现了一种直观且可识别的输入或选择特定日期的方法,选择日期后,月份日历控件将自动消失。让无涯教程通过创建一个新的MFC应用程序来研究一个简单的示例。步骤1-删除标题并将其ID设置为IDC_STATIC_TXT。步骤2-为文本控件添加value变量。......
  • Python网络连接request报错:OSError: [Errno 113] No route to host
    报错:(pytorch)devil@Monster:~$huggingface-clilogin_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|_|......
  • 无涯教程-MFC - Timer函数
    MFC计时器既没有按钮来表示它,也没有类,要创建计时器,只需调用CWnd::SetTimer()方法,此函数调用为您的应用程序创建一个计时器,像其他控件一样,计时器使用标识符。让无涯教程创建一个新的基于MFC对话框的应用程序。步骤1-删除标题并将其ID设置为IDC_STATIC_TXT步骤2-为文本控件......
  • MBR30100FCT-ASEMI大功率肖特基MBR30100FCT
    编辑:llMBR30100FCT-ASEMI大功率肖特基MBR30100FCT型号:MBR30100FCT品牌:ASEMI封装:TO-220F特性:插件、肖特基二极管最大平均正向电流:30A最大重复峰值反向电压:100V恢复时间:5ns引脚数量:3芯片个数:2最大正向压降:0.90V芯片尺寸:122MIL浪涌电流:275A漏电流:10ua工作温度:-40℃~170℃包装方式:100/......
  • centos7 docker-ce
    言Docker使用越来越多,安装也很简单,本次记录一下基本的步骤。Docker目前支持CentOS7及以后的版本,内核要求至少为3.10。Docker官网有安装步骤,本文只是记录一下,您也可以参考DockerCEInstallonCentOS环境说明CentOS7(MinimalInstall)$cat/etc/redhat-releaseCentOSLinu......
  • python 统一图片尺寸
     ......
  • MBR30100FCT-ASEMI大功率肖特基MBR30100FCT
    编辑:llMBR30100FCT-ASEMI大功率肖特基MBR30100FCT型号:MBR30100FCT品牌:ASEMI封装:TO-220F特性:插件、肖特基二极管最大平均正向电流:30A最大重复峰值反向电压:100V恢复时间:5ns引脚数量:3芯片个数:2最大正向压降:0.90V芯片尺寸:122MIL浪涌电流:275A漏电流:10ua工作温度:-40℃~17......