装饰器

1. 装饰器介绍

1.1 为何要用装饰器

软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。对扩展开放,意味着有新的需求或变化时,可以对现有的代码进行扩展,以适应新的情况。对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不需要对其修改。

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

1.2 什么是装饰器

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

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

2. 装饰器的实现

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

2.1 无参装饰器的实现

# 需求:在不修改index函数的源代码以及调用方式的前提下为其添加统计运行时间的功能
import time


def index(x, y):
    time.sleep(3)
    print('index %s, %s' % (x, y))


index(111, 222)

# 解决方案一
# 问题:没有修改被装饰对象的调用方式,但是修改了其源代码
import time


def index(x, y):
    start = time.time()
    time.sleep(3)
    print('index %s %s' % (x, y))
    stop = time.time()
    print(stop - start)


index(111, 222)

# 解决方案二
# 问题:没有修改被装饰对象的调用方式,也没有修改其源代码,并且加上了新功能
# 但代码冗余
import time


def index(x, y):
    time.sleep(3)
    print('index %s %s' % (x, y))


start_time = time.time()
index(111, 222)
stop_time = time.time()
print(start_time - stop_time)

# 解决方案三
# 问题:解决了方案二的代码冗余问题,但带来一个新问题即函数的调用方式改变
import time


def index(x, y):
    time.sleep(3)
    print('index %s %s' % (x, y))


def wrapper():
    start_time = time.time()
    index(111, 222)
    stop_time = time.time()
    print(start_time - stop_time)


wrapper()

# 方案三优化
import time


def index(x, y):
    time.sleep(3)
    print('index %s %s' % (x, y))


def wrapper(*args, **kwargs):
    start_time = time.time()
    index(*args, **kwargs)
    stop_time = time.time()
    print(start_time - stop_time)


wrapper(111, 222)

# 解决方案四
# 问题:解决了方案三函数的调用方式改变问题,但带来一个新问题即被装饰对象有返回值时返回值是None
import time


def run_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        index(*args, **kwargs)
        stop_time = time.time()
        print(start_time - stop_time)

    return wrapper


def index(x, y):
    time.sleep(3)
    print('index %s,%s' % (x, y))
    return 123


res = index(111, 222)
print(res)

# 方案四优化(装饰器终极版):给func用res来接收返回值然后return res,来实现对被装饰函数返回值的接收
import time


def run_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = index(*args, **kwargs)
        stop_time = time.time()
        print(start_time - stop_time)
        return res

    return wrapper


def index(x, y):
    time.sleep(1)
    print('index %s,%s' % (x, y))
    return 123


index = run_time(index)
res = index(111, 222)
print(res)

2.2 语法糖

import time


def run_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print(stop_time - start_time)
        return res

    return wrapper


# 在被装饰对象正上方的单独一行写@装饰器名字
@run_time  # index = run_time(index)
def index(x, y):
    time.sleep(3)
    print('index %s,%s' % (x, y))
    return 123


res = index(111, 222)
print(res)

# 补充:wraps的使用
from functools import wraps


# 无参装饰器模板
def outter(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # 1.调用原函数
        # 2.为其增加新功能
        res = func(*args, **kwargs)
        return res

    # wrapper.__name__ = func.__name__
    # wrapper.__doc__ = func.__doc__
    return wrapper


def index():
    """这个是主页功能"""
    print('welcome index')


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

 2.3 有参装饰器

def auth(db_type='file'):
    def deco(func):
        def wrapper(*args, **kwargs):
            name = input('your name:').strip()
            pwd = input('your password:').strip()
            if db_type == 'file':
                print('基于文件校正')
                if name == 'egon' and pwd == '123'
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('user or password error')
            elif db_type == 'mysql':
                print('基于mysql的验证')
            elif db_type == 'ldap':
                print('基于ldap的验证')
            else:
                print('不支持该db_type')

        return wrapper

    return deco


@auth(db_type='file')
def index():
    print('welcome index')


@auth(db_type='mysql')
def home():
    print('welcome home')


@auth(db_type='ldap')
def filter():
    print('welcome filter')


index()
home()
filter()

2.4 总结

# 无参装饰器模板
def outter(func):
    def wrapper(*args, **kwargs):
        # 1.调用原函数
        # 2.为其增加新功能
        res = func(*args, **kwargs)
        return res

    return wrapper


@outter
def index():
    print('welcome index')


index()


# 偷梁换柱:即将原函数名指向的内存地址偷梁换柱成wrapper函数
# 所以应将wrapper做的和原函数一样

# 有参函数模板
def 有参函数装饰器(x, y, z):
    def outter(func):
        def wrapper(*args, **kwargs):
            # 1.调用原函数
            # 2.为其增加新功能
            res = func(*args, **kwargs)
            return res

        return wrapper

    return outter


@有参函数装饰器(1, 2, 3)
def index():
    print('welcome index')


index()

2.5 叠加多个装饰器的加载、运行分析(了解)

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


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


def deco3(x):
    def outter(func3):
        def wrapper3(*args, **kwargs):
            print('正在运行===>deco3.outter3.wrapper3')
            res3 = func(*args, **kwargs)
            return res3

        return wrapper3

    return outter


# 加载顺序,自下而上
@deco1
@deco2
@deco3(233)
def index(x, y):
    print('from index %s:%s' % (x, y))


# 执行顺序:自上而下的,即wrapper1-->wrapper2-->wrapper3
index(22, 33)

 

 

 
posted @ 2021-02-21 16:13  Avery_W  阅读(85)  评论(0)    收藏  举报