装饰器

1 装饰器的定义

器指的是工具,函数就是一种工具
装饰指的是为其他事物添加额外的东西点缀

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

2 装饰器的作用

首先我们要明白一个原则

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

我们对代码进行更新时应该尽量满足开放封闭原则,

即尽量在不修改函数中的源代码,不改变函数的调用方式的状态下增加代码的功能

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

3 装饰器的组成

装饰器是参数,函数,函数的嵌套定义,函数对象,名称空间和作用域,闭包函数等概念组合起来形成的一种产物

3.1必备知识

3.1.1 参数*args, **kwargs

如何将传入一个函数的任意个任意形态的实参原封不动地传给另一个函数

便是通过*args与**kwargs在形参位置和实参位置的不同作用完成的

def index(x,y):
    print(x,y)
def wrapper(*args,**kwargs):
    index(*args,**kwargs) # 最终传参为index(y=222,x=111)
wrapper(y=222,x=111)

3.1.2 名称空间与作用域

闭包中特殊的变量与名称空间的关系:

名称空间的的"嵌套"关系是在函数定义阶段,即检测语法的时候确定的

即需要调用的变量在定义阶段就确定了取变量的名称空间的顺序(不会确定值)

3.1.3 函数对象

函数可以被当做参数传入

函数可以当做返回值返回

def index():
    return 123

def foo(func):
    return func

print(foo(index))

3.1.4 函数的嵌套定义

def outter(func):
    def wrapper():
        pass
    return wrapper

3.1.5 传参的方式

传参的方式一:通过参数的形式为函数体传值

def wrapper(x):
    print(1)
    print(2)
    print(3)
    print(x)

wrapper(1)
wrapper(2)
wrapper(3)

传参的方式二:通过闭包的方式为函数体传值

def outter(x):
    def wrapper():
        print(1)
        print(2)
        print(3)
        print(x)
    return wrapper # return outter内的wrapper那个函数的内地址

f1=outter(1)
f1()
f2=outter(2)
f2()
f3=outter(3)
f3()

3.2 装饰器的主体

装饰器的主体是由闭包函数组成的

def outter():
    x=111
    def wrapper():
        print(x)
    return wrapper

f=outter()  # 此处f可以为任何名字,包括wrapper=outter()
f()			# wrapper(),这个与原函数同名,但是互不影响,这是名称空间的特性
# f为全局空间中的名字,而wrapper为局部空间中的名字

4 装饰器的使用

需求:在不修改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)

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

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):
        start=time.time()
        func(*args,**kwargs) # index的内存地址()
        stop=time.time()
        print(stop - start)
    return wrapper

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

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)

res=home('egon') # res=wrapper('egon')
print('返回值--》',res)

5 语法糖

在函数定义上一行写@+装饰器名字,便能省去func=wrapper(func)这一步

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(ome)
def home(name):
    time.sleep(2)
    print('welcome %s to home page' %name)


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

6 无参装饰器模板

无参装饰器可以直接在无参装饰器模板中添加要增加的功能,完成装饰器

def outter(func):
    def wrapper(*args,**kwargs):
        # 1、调用原函数
        # 2、为其增加新功能
        res=func(*args,**kwargs)
        return res
    return wrapper
def auth(func):
    def wrapper(*args,**kwargs):
        # 1、调用原函数
        # 2、为其增加新功能
        name=input('your name>>: ').strip()
        pwd=input('your password>>: ').strip()
        if name == 'egon' and pwd == '123':
            res=func(*args,**kwargs)
            return res
        else:
            print('账号密码错误')
    return wrapper

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

index()

7 装饰器优化

装饰器本质是偷梁换柱,即将原函数名指向的内存地址偷梁换柱成wrapper函数,所以应该将wrapper做的跟原函数尽可能一样才行

def outter(func):
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs) # res=index(1,2)
        return res
    return wrapper

def index():
    """这个是主页功能"""
    print("index----")
    
print(index.__name__)
# index
print(index.__doc__)
# 这个是主页功能

@outter
def index():
    print("index----")
    
print(index.__name__)
# wrapper
print(index.__doc__)
# None

可以看到,名字和注释都不和原来相同,我们可以通过

wrapper.__name__ = index.__name__

wrapper.__doc__ = index.__doc__

暂时解决,但是内置的__func__方法有很多,都与原函数不同,我们不应全部更换一遍

此时可以使用python提供的装饰器wraps

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__)	# 可以看到已经和原函数index相同了
print(index.__doc__) 	#help(index)

8 有参装饰器

有参装饰器就是@wrapper(f1,f2,f3)形式的装饰器,需要参数

8.1 有参装饰器的作用

需求: 为wrapper传入一个额外的参数

我们在无参装饰器中能发现,wrapper的形参为*args,**kwargs,如果对wrapper增加形参会使得需要额外的实参,在装饰之后导致原函数的调用方式改变: 需要输入额外的实参

所以,我们不能在def wrapper()内增加额外的形参

而使用语法糖时会出现一个问题,@outter等价于index=outter(index),语法糖只能接收一个参数,即函数的内存地址,
而我们无法在使用语法糖的同时传入另一个参数,即由于语法糖@的限制,outter函数只能有一个参数,并且该才是只用来接收 被装饰对象的内存地址

所以我们也不能在def outter(func)处额外增加形参

这时,我们就需要使用有参装饰器为内部传参

def outter(func):
    # func = 函数的内存地址
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        return res
    return wrapper


@outter 		# @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')
home=auth(home,'mysql')
transfer=auth(transfer,'ldap')

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

8.2 有参装饰器的使用

有参装饰器其实是在无参装饰器的基础上又在外部加了一层函数

8.2.1 演示一

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()

8.2.2 演示二

有参装饰器

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')  # @deco # index=deco(index) # index=wrapper
def index(x, y):
    print('index->>%s:%s' % (x, y))

@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)
home('egon')
transfer()

8.3 有参装饰器模板

def 有参装饰器(x,y,z):
    def outter(func):
        def wrapper(*args, **kwargs):
            res = func(*args, **kwargs)
            return res
        return wrapper
    return outter

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

9 叠加多个装饰器的加载、运行分析

加载顺序自下而上

执行顺序自上而下

def deco1(func1): # func1 = wrapper2的内存地址
    def wrapper1(*args,**kwargs):
        print('正在运行===>deco1.wrapper1')
        res1=func1(*args,**kwargs)
        return res1
    return wrapper1

def deco2(func2): # func2 = wrapper3的内存地址
    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')
            res3=func3(*args,**kwargs)
            return res3
        return wrapper3
    return outter3


# 加载顺序自下而上
@deco1      # index=deco1(wrapper2的内存地址)        ===> index=wrapper1的内存地址
@deco2      # index=deco2(wrapper3的内存地址)        ===> index=wrapper2的内存地址
@deco3(111) # ===>@outter3===> index=outter3(index) ===> index=wrapper3的内存地址
def index(x,y):
    print('from index %s:%s' %(x,y))
# 调用index-->其实是调用了wrapper1
# 调用wrapper1-->其实是调用了wrapper2
# 调用wrapper2-->其实是调用了wrapper3


# 执行顺序自上而下的,即wraper1->wrapper2->wrapper3

index(1,2) # wrapper1(1,2)
# 正在运行===>deco1.wrapper1
# 正在运行===>deco2.wrapper2
# 正在运行===>deco3.outter3.wrapper3
# from index 1:2

1

 posted on 2020-03-23 19:23  wwwpy  阅读(118)  评论(0编辑  收藏  举报