待手绾青丝

待手绾青丝

待手绾青丝

庭中三千梨花树,再无一朵入我心。 心中只你一朵,似在心中,不在心中,可望可念可想不可及。

109 文章数
2 评论数
来首音乐
光阴似箭
今日已经过去小时
这周已经过去
本月已经过去
今年已经过去个月

05_装饰器

待手绾青丝
2024-10-23 / 0 评论 / 43 阅读 / 0 点赞

装饰器

一、什么是装饰器?

装饰:指的是为被装饰对象添加新的功能

器:指的是器具/工具,装饰器与被装饰对象均可以是任意可调用对象。

概括的讲,装饰器的作用就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加额外的功能。

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

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

二、为什么要用装饰器?

软件的设计应该遵循开放封闭的原则。

那么什么是开放封闭原则呢?所谓的开放封闭,直白的讲就是对扩展是开放的,然而对修改是封闭的。

对扩展开放,就意味着有新的需求或变化的时候,可以对现有代码进行扩展,以适应新的情况。

对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。

​ 软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃,而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器

三、装饰器的实现

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

1、无参装饰器

如果想要为下述函数添加统计其运行时间的功能:

import time


def index():
    time.sleep(2)
    print(f'Welcome to the index page!')
    return 200


index()  # 函数调用执

方案一:

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

import time


def index():
    time.sleep(2)
    print(f'Welcome to the index page!')
    return 200


start_time = time.time()
index()  # 函数调用执行
stop_time = time.time()
print(f'Run time is {stop_time - start_time}')

# 结果
Welcome to the index page! 
Run time is 2.0089287757873535

index函数可能不止在一个地方调用

方案二:解决代码冗余

考虑代码冗余问题,我们将新增的代码写成一个函数。

import time


def index():
    time.sleep(2)
    print(f'Welcome to the index page!')
    return 200


def wrapper():
    start_time = time.time()
    index()  # 函数调用执行
    stop_time = time.time()
    print(f'Run time is {stop_time - start_time}')

wrapper()

# 结果
Welcome to the index page!
Run time is 2.00653338432312

方案三:适用于其他函数

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

import time


def index():
    time.sleep(2)
    print(f'Welcome to the index page!')
    return 200


def wrapper(func):
    start_time = time.time()
    res = func()  # 函数调用执行
    stop_time = time.time()
    print(f'Run time is {stop_time - start_time}')
    return res

wrapper(index)

# 结果
Welcome to the index page!
Run time is 2.0006017684936523

但是,如果一旦这么更改,被装饰对象的源代码虽然没有更改,但是更改了被装饰对象的调用方式,这样,显然是不符合****开放封闭****原则的,就好似如下这种情况:

wrapper(index)
wrapper(其他函数的内存地址)

方案四:不改变使用方式

既然这种方式违反了开放封闭原则,那么我们就换一种方式为函数体传值,这种方式就是:将值包给函数,如下所示:

import time


def index():
    time.sleep(2)
    print(f'Welcome to the index page!')
    return 200

def calc_time(func):
    def wrapper():
        start_time = time.time()
        res = func()  # 函数调用执行
        stop_time = time.time()
        print(f'Run time is {stop_time - start_time}')
        return res
    return wrapper

​ 这样我们便可以在不修改被装饰对象源代码以及调用方式的情况下为其加上统计时间的功能,只不过需要事先执行一次calc_time将被装饰对象传入,返回一个闭包函数wrapper,并重新赋值给函数名index,如下所示:

index = calc_time(index)  # 得到index = wrapper,wrapper携带对外部作用域的引用,func = 原始的index
index()  # 执行的是wrapper(),在wrapper的函数体内再执行最原始的index

​ 至此,我们就实现了一个无参装饰器calc_time,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能,但是我们忽略了一个重要的点,万一我们的被装饰对象是一个有参函数,那么就会抛出异常!!!

import time


def index():
    time.sleep(2)
    print(f'Welcome to the index page!')
    return 200


def calc_time(func):
    def wrapper():
        start_time = time.time()
        res = func()  # 函数调用执行
        stop_time = time.time()
        print(f'Run time is {stop_time - start_time}')
        return res
    return wrapper


home = calc_time(index)
home('Tim Cook')

# 结果
Traceback (most recent call last):
  File "C:\Users\xiaowu\Desktop\pythnon-blog\python\python.py", line 21, in <module>
    home('Tim Cook')
TypeError: calc_time.<locals>.wrapper() takes 0 positional arguments but 1 was given

​ 之所以会抛出异常,是因为home('Tim Cook')调用的其实是wrapper('Tim Cook'),而函数wrapper没有参数,wrapper函数接收的参数其实是最原始的func的,为了能满足被装饰对象的所有情况,我们要使用*args, **kwargs来接收任意类型,任意多个参数,因此,修改过的装饰器如下所示:

import time


def index(name):
    time.sleep(2)
    print(f'Welcome to the index page! %s' % name)
    return 200


def calc_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)  # 函数调用执行
        stop_time = time.time()
        print(f'Run time is {stop_time - start_time}')
        return res
    return wrapper


index = calc_time(index)
index('Tim Cook')

1.语法糖:@装饰器名称

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

import time


def calc_time(func):
    def wrapper(*args, **kwargs):  # 调用原来的功能,在原来的功能基础上加上其他功能
        start_time = time.time()
        res = func(*args, **kwargs)  # 函数调用执行
        stop_time = time.time()
        print(f'Run time is {stop_time - start_time}')
        return res
    return wrapper


# 在被装饰对象上方单独写一行@装饰器名字
@calc_time  # index = calc_time(index)
def index(name):
    time.sleep(2)
    print(f'Welcome to the index page! %s' % name)
    return 200


index('Tim Cook')

如果我们有多个装饰器,那么可以叠加多个

@deco3
@deco2
@deco1
def index():
    pass

# 叠加多个装饰器也没有特殊之处,上述代码语义如下:
index = deco3(deco2(deco1(index)))

2.装饰器模板

def outter(func):
    def wrapper(*args, **kwargs):  # 调用原来的功能,在原来的功能基础上加上其他功能
        res = func(*args, **kwargs)  # 函数调用执行
        return res
    return wrapper

用模板做一个认证功能

def auth(func):
    def wrapper(*args, **kwargs):  # 调用原来的功能,在原来的功能基础上加上其他功能
        name = input('your name>>:').strip()
        pwd = input('your passwd>>:').strip()
        if name == 'trevor' and pwd == '123':
            res = func(*args, **kwargs)  # 函数调用执行
            return res
        else:
            print('账号密码错误')
    return wrapper

@auth
def index():
    print('index 666')

index()

# 结果
your name>>:trevor
your passwd>>:123
index 666

3.伪装原来函数的属性

from functools import wraps  # 装饰函数工具,伪装原函数属性

def auth(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        name = input('your name>>:').strip()
        pwd = input('your passwd>>:').strip()
        if name == 'trevor' and pwd == '123':
            res = func(*args, **kwargs)
            return res
        else:
            print('账号密码错误')

    return wrapper

@auth
def index():
    """这是函数使用帮助"""
    print('index 666')

print(index.__doc__)
print(index.__name__)

4.最终模板

from functools import wraps  # 装饰函数工具

def outter(func):
    @wraps(func)
    def wrapper(*args, **kwargs):  # 调用原来的功能,在原来的功能基础上加上其他功能
        res = func(*args, **kwargs)  # 函数调用执行
        return res
    return wrapper

@outter
def index():
    """这是函数使用帮助"""
    print('index 666')

2、有参装饰器

1.给予无参装饰器写一个认证功能

def auth(func):
    def wrapper(*args, **kwargs):
        name = input('your name>>:').strip()
        pwd = input('your passwd>>:').strip()
        if name == 'trevor' and pwd == '123':
            res = func(*args, **kwargs)
            return res
        else:
            print('账号密码错误')
    return wrapper

@auth
def index():
    print('index 666')

index()

# 结果
your name>>:trevor
your passwd>>:123
index 666

2.认证功能支持多个认证方式

def auth(func):
    def wrapper(*args, **kwargs):
        if db_type == 'file':
            print('当前认证方式', db_type)
            name = input('your name>>:').strip()
            pwd = input('your passwd>>:').strip()
            if name == 'trevor' and pwd == '123':
                res = func(*args, **kwargs)
                return res
            else:
                print('账号密码错误')
        if db_type == 'mysql':
            print('当前认证方式', db_type)
            name = input('your name>>:').strip()
            pwd = input('your passwd>>:').strip()
            if name == 'trevor' and pwd == '123':
                res = func(*args, **kwargs)
                return res
            else:
                print('账号密码错误')
        if db_type == 'ldap':
            print('当前认证方式', db_type)
            name = input('your name>>:').strip()
            pwd = input('your passwd>>:').strip()
            if name == 'trevor' and pwd == '123':
                res = func(*args, **kwargs)
                return res
            else:
                print('账号密码错误')
        else:
            print('不支持该db_type')
    return wrapper

@auth
def index():
    print('index 666')

index()

如上述代码所示,wrapper函数需要一个db_type参数,而函数authwrapper*的参数都有其特定的功能,不能用来接收其他类别的参数,可以在deco的外部再包一层函数user_authorized,专门用来接收额外的参数,这样便保证了在user_authorized函数内无论多少层都可以引用到。

3.添加一个user_authorized函数用于传入db_type

def user_authorized(db_type):
    def auth(func):
        def wrapper(*args, **kwargs):
            if db_type == 'file':
                print('当前认证方式', db_type)
                name = input('your name>>:').strip()
                pwd = input('your passwd>>:').strip()
                if name == 'trevor' and pwd == '123':
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('账号密码错误')
            elif db_type == 'file':
                print('当前认证方式', db_type)
                name = input('your name>>:').strip()
                pwd = input('your passwd>>:').strip()
                if name == 'trevor' and pwd == '123':
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('账号密码错误')
            elif db_type == 'file':
                print('当前认证方式', db_type)
                name = input('your name>>:').strip()
                pwd = input('your passwd>>:').strip()
                if name == 'trevor' and pwd == '123':
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('账号密码错误')
            else:
                print('不支持该db_type')
        return wrapper

    return auth

@user_authorized(db_type='file')
def index():
    print('index 666')


index()

4.最终模板

from functools import wraps  # 装饰函数工具
def 装饰器名称(a, b, c):
    def outter(func):
        @wraps(func)
        def wrapper(*args, **kwargs):  # 调用原来的功能,在原来的功能基础上加上其他功能
            res = func(*args, **kwargs)  # 函数调用执行
            return res
        return wrapper
    return outter

@装饰器名称(a=1, b=2, c=3)
def index():
    """这是函数使用帮助"""
    print('index 666')

四、叠加多个装饰器分析

1、代码

def deco1(func1):
    def wrapper1(*args, **kwargs):
        print('正在运行==》deco1.wrapper1')
        res1=func1(*args, **kwargs)
        return res1
    return wrapper1


def deco2(func2):
    def wrapper2(*args, **kwargs):
        print('正在运行==》deco2.wrapper2')
        res2=func2(*args, **kwargs)
        return res2
    return wrapper2


def deco3(x):
    def outter3(func3):
        def wrapper3(*args, **kwargs):
            print('正在运行==》deco3.outter3.wrapper3.%s' %(x))
            res3=func3(*args, **kwargs)
            return res3
        return wrapper3
    return outter3


@deco1
@deco2
@deco3(333)
def index (x, y):
    print(f'from index %s %s' %(x, y))

2、加载顺序自下而上

def deco1(func1):
    def wrapper1(*args, **kwargs):
        print('正在运行==》deco1.wrapper1')
        res1=func1(*args, **kwargs)
        return res1
    return wrapper1


def deco2(func2):
    def wrapper2(*args, **kwargs):
        print('正在运行==》deco2.wrapper2')
        res2=func2(*args, **kwargs)
        return res2
    return wrapper2


def deco3(x):
    def outter3(func3): # func3=被装饰对象index函数的内存地址
        def wrapper3(*args, **kwargs):
            print('正在运行==》deco3.outter3.wrapper3.%s' %(x))
            res3=func3(*args, **kwargs)
            return res3
        return wrapper3
    return outter3


@deco1 # ==> @outter3 ==> index=outter3(index) ==> index=wrapper3的内存地址
@deco2 # ==> @outter3 ==> index=outter3(index) ==> index=wrapper3的内存地址
@deco3(333) # ==> @outter3 ==> index=outter3(index) ==> index=wrapper3的内存地址
def index (x, y):
    print(f'from index %s %s' %(x, y))


print(index)


# 结果
<function deco1.<locals>.wrapper1 at 0x00000264939C2200>

3、执行顺序自上而下

def deco1(func1):
    def wrapper1(*args, **kwargs):
        print('正在运行==》deco1.wrapper1')
        res1=func1(*args, **kwargs)
        return res1
    return wrapper1


def deco2(func2):
    def wrapper2(*args, **kwargs):
        print('正在运行==》deco2.wrapper2')
        res2=func2(*args, **kwargs)
        return res2
    return wrapper2


def deco3(x):
    def outter3(func3):
        def wrapper3(*args, **kwargs):
            print('正在运行==》deco3.outter3.wrapper3.%s' %(x))
            res3=func3(*args, **kwargs)
            return res3
        return wrapper3
    return outter3


@deco1
@deco2
@deco3(333)
def index (x, y):
    print(f'from index %s %s' %(x, y))


index(1, 2)


# 结果
正在运行==》deco1.wrapper1
正在运行==》deco2.wrapper2
正在运行==》deco3.outter3.wrapper3.333
from index 1 2
文章不错,扫码支持一下吧~
上一篇 下一篇
评论
最新回复
文章目录
每日一句