ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

11--装饰器函数

2022-06-14 09:03:57  阅读:142  来源: 互联网

标签:11 index -- wrapper time print home 装饰 def


1 什么是装饰器

器指的是工具,可以定义成成函数
装饰指的是为其他事物添加额外的东西点缀

合到一起的解释:
装饰器指的定义一个函数,该函数是用来为其他函数添加额外的功能

2 为何要用装饰器

开放封闭原则
开放:指的是对拓展功能是开放的
封闭:指的是对修改源代码是封闭的

装饰器就是在不修改被装饰对象的源代码以及调用方式的前提下,为被装饰对象添加新功能

装饰器的用途,比如:插入日志、性能测试、事务处理、缓存、权限校验等应用场景

装饰器是解决这类问题的绝佳设计,有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用

3 如何实现函数装饰器

函数装饰器分为:无参装饰器和有参装饰器两种

二者的实现原理一样,都是 "函数嵌套+闭包+函数对象" 的组合使用的产物。

3.1 无参装饰器的实现

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

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

index(111,222)
# index(y=111,x=222)
# index(111,y=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()
index(111,222)
stop=time.time()
print(stop - start)

start=time.time()
index(111,222)
stop=time.time()
print(stop - start)

start=time.time()
index(111,222)
stop=time.time()
print(stop - start)


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

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

def wrapper():
    start=time.time()
    index(111,222)
    stop=time.time()
    print(stop - start)

wrapper()


# 方案三的优化一:将index的参数写活了
import time

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

def wrapper(*args,**kwargs):
    start=time.time()
    index(*args,**kwargs) # index(3333,z=5555,y=44444)
    stop=time.time()
    print(stop - start)

wrapper(3333,4444,5555)
# wrapper(3333,z=5555,y=44444)


# 方案三的优化二:在优化一的基础上把被装饰对象写活了,原来只能装饰index
import time

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

def home(name):
    time.sleep(2)
    print('welcome %s to home page' %name)


def outter(func):
    # func = index的内存地址
    def wrapper(*args,**kwargs):  # 引用外部作用域的变量func
        start=time.time()
        func(*args,**kwargs)  # index的内存地址()
        stop=time.time()
        print(stop - start)
    return wrapper

index=outter(index)  # index=wrapper的内存地址
home=outter(home)  # home=wrapper的内存地址

home('egon')
# home(name='egon')

# 方案三的优化三:将wrapper做的跟被装饰对象一模一样,以假乱真,但调用方式被修改了
import time

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

def home(name):
    time.sleep(2)
    print('welcome %s to home page' %name)

def outter(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print(stop - start)
        return res
    return wrapper

# 偷梁换柱:home这个名字指向的wrapper函数的内存地址
home=outter(home)
home('egon')  # wrapper('egon')

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



# 优化四(最终版):如何在方案三的基础上不改变函数的调用方式
  直接调用index(参数),不用多执行 index=timer(index)

# 语法糖:让你开心的语法
  来取代index=timer(index)的形式
    
  # 在被装饰对象正上方的单独一行写: @装饰器名字
  就会调用装饰器函数,且把它正下方的函数名当做实参传入,然后将返回的结果重新赋值给原函数名

    
# 装饰器的完整案例
import time

def timmer(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print(stop - start)
        return res
    return wrapper

@timmer  # index=timmer(index) 
def index(x,y,z):
    time.sleep(3)
    print('index %s %s %s' %(x,y,z))

@timmer # home=timmer(home)
def home(name):
    time.sleep(2)
    print('welcome %s to home page' %name)

index(x=1,y=2,z=3)
home('egon')

3.1.1 无参装饰器模板

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

3.1.2 多个装饰器

# 装饰器可以叠加多个
  结论:越靠近函数 的装饰器,越先执行。由下往上 

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

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

3.2 有参装饰器的实现

# 一:知识储备
# 由于语法糖@的限制,outter函数只能有一个参数,并且只用来接收被装饰对象的内存地址
def outter(func):
    # func = 函数的内存地址
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        return res
    return wrapper

@outter # index=outter(index) # index=>wrapper
def index(x,y):
    print(x,y)


# 推论过程:
# 一、山炮玩法 (不用语法糖,自己直接写参数)
def auth(func,db_type):
    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

@auth  # 账号密码的来源是文件
def index(x,y):
    print('index->>%s:%s' %(x,y))

@auth # 账号密码的来源是数据库
def home(name):
    print('home->>%s' %name)

@auth # 账号密码的来源是ldap
def transfer():
    print('transfer')

# 若不用语法糖@,自己调用写:
index=auth(index,'file')
index(1,2)

home=auth(home,'mysql')
home('egon')

transfer=auth(transfer,'ldap')
transfer()


# 二、山炮玩法(将参数用闭包来传递,不用语法糖,自己直接写参数调用)
def auth(db_type):
    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


deco=auth(db_type='file')
@deco # 账号密码的来源是文件
def index(x,y):
    print('index->>%s:%s' %(x,y))

deco=auth(db_type='mysql')
@deco # 账号密码的来源是数据库
def home(name):
    print('home->>%s' %name)

deco=auth(db_type='ldap')
@deco # 账号密码的来源是ldap
def transfer():
    print('transfer')

index(1,2)
home('egon')
transfer()


# 三、语法糖方式:(将参数用闭包来传递,并用语法糖来参数调用)
def auth(db_type):
    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)  # index(1,2)
                    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')  # 1.@deco  # 2.index=deco(index)  # 3.index=wrapper
def index(x, y):
    print('index->>%s:%s' % (x, y))

# 解析 @auth过程 ===》 index = wrapper 将被装饰对象转化为wrapper对象
  1.第一步解析 函数调用 auth(db_type='file') :@auth(db_type='file') ===》 @deco 
  2.第二步解析 语法糖@  @deco ====》 index = deco(index) 
  3.第三部解析 函数调用 deco(index) :index = deco(index)  ====》index = wrapper
    
@auth(db_type='mysql')  # @deco # home=deco(home) # home=wrapper
def home(name):
    print('home->>%s' % name)


@auth(db_type='ldap')  # 账号密码的来源是ldap
def transfer():
    print('transfer')

index(1, 2)  # 相当于 wrapper(1,2)
home('egon')
transfer()

3.2.2 有参装饰器模板

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

@有参装饰器(1,y=2,z=3)
def 被装饰对象():
    pass

4 装饰器补充--完全伪装

# 核心:偷梁换柱,即将原函数名指向的内存地址偷梁换柱成wrapper函数
#        所以应该将wrapper做的跟原函数一样才行

# 即下面:将wrapper的函数信息,伪装成跟原函数(index)一模一样
from functools import wraps

def outter(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """这个是主页功能"""
        res = func(*args, **kwargs)  # res=index(1,2)
        return res
    
    # 手动将原函数的属性赋值给wrapper函数
    # 1.函数wrapper.__name__ = 原函数.__name__
    # 2.函数wrapper.__doc__ = 原函数.__doc__  
    # wrapper.__name__ = func.__name__
    # wrapper.__doc__ = func.__doc__

    return wrapper

@outter # index=outter(index)
def index(x,y):
    """这个是主页功能"""
    print(x,y)

print(index.__name__) # 实际是wrapper的name
print(index.__doc__)  # help(index)

index(1,2) # wrapper(1,2)

# 偷梁换柱之后
# index的参数什么样子,wrapper的参数就应该什么样子
# index的返回值什么样子,wrapper的返回值就应该什么样子
# index的属性什么样子,wrapper的属性就应该什么样子==》from functools import wraps

标签:11,index,--,wrapper,time,print,home,装饰,def
来源: https://www.cnblogs.com/Edmondhui/p/16373065.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有