装饰器

一 装饰器

1、什么是装饰器

​ 器指的是工具,可以定义成函数

​ 装饰指的是为其他事物添加额外的功能

​ 总结:

​ 装饰器指的是定义一个函数/类,该函数是用来为其他函数增加功能的

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

2、为何要用装饰器

开放封闭原则

​ 开放:指的是对拓展功能是开放的

​ 封闭:指的是对修改源代码是封闭的

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

给被装饰对象加了装饰器后,使得被装饰对象在使用层面上没变

3、如何用

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

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

index(111,222)
index(x=111, y=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)
  

解决方式二:失败

问题:没有修改被装饰对象的调用方式,也没有修改其源代码,并且也附加了新功能,但是代码冗余

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 现在是调用 wapper

方案三的优化一:将 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)
    		stop = time.time()
   		  print(stop - start)
    return wrapper

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

home('tank')
#home(name='tank')
 

大方向:如何在方案三的基础上不改变函数的调用方式

方案三的优化三:将 wrapper 做的跟被装饰对象一模一样,以假乱真

import time

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

def home(name):
  	time.sleep(2)
    print('welcome to home page'%name)
		return 123
  
def outter(func):
  	def wrapper(*args **kwargs):
      	start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print(stop - start)
        return res   #如果不写这步, wrapper 函数默认返回的是None并不是 func 的返回值
    return wrapper

#偷梁换柱:home 这个名字指向的是 wrapper函数的内存地址
home = outter(home) #home = wrapper 的内存地址
res = home('egon') #res = wrapper('egon')
print('返回值-->',res)

二 语法糖

语法糖:让你开心的语法

@名字 ---》home = 名字(home)
def home(a,b,c):
		pass

使用语法糖时,需要注意将装饰器写在被装饰对象上方

import time

def timer(func):
  	def wrapper(*args, **kwargs):
      	start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print(stop - start)
        return res
    return wrapper
  
#在被装饰对象正上上的单独一行写@装饰器名字
@timer #index= timer(index) #index = wrapper的内存地址 
def index(x,y,z):
  	time.sleep(3)
    print('index %s %s %s'%(x,y,z))
 
@timer # home = timer(home) = warpper 
def home(name):
  	time.sleep(2)
    print('welcome %s to home page'%name)

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

无参装饰器模板

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

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


进阶版:

将被装饰对象的属性赋值给 warpper后,这样用户在调用时看不出任何变化。

from functools import wraps

def outter(func):  #这一步将被装饰对象写活了
		def wrapper(*args, **kwargs): #不修改被装饰对象的调用方式及为其增加新功能
      ''' 这个是主页功能 '''
				#1、调用原函数
				#2、为其增加新功能
				res = func(*args, **kwargs) #不修改被装饰对象的调用方式,且拿到返回值
        
    # 手动将原函数的属性赋值给wrapper函数(太累)
    # 1、函数wrapper.__name__ = 原函数.__name__
    # 2、函数wrapper.__doc__ = 原函数.__doc__
    # wrapper.__name__ = func.__name__
    # wrapper.__doc__ = func.__doc__
    		return res
     return wrapper #拿到 warpper 的内存地址
    
@outter # index=outter(index)
def index(x,y):
    """这个是主页功能"""
    print(x,y)

print(index.__name__)
print(index.__doc__) #help(index)

究极版:

from functools import wraps

def outter(func):  #这一步将被装饰对象写活了
  	@wraps(func) #将 func 函数的属性赋值给wrapper
		def wrapper(*args, **kwargs): #不修改被装饰对象的调用方式及为其增加新功能
      ''' 这个是主页功能 '''
				#1、调用原函数
				#2、为其增加新功能
				res = func(*args, **kwargs) #不修改被装饰对象的调用方式,且拿到返回值
    		return res
     return wrapper #拿到 warpper 的内存地址

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


print(index.__name__)
print(index.__doc__) #help(index)

示例:

登录功能装饰器

def auth(func):
  	def wrapper(*args, **kwargs):
      	#1、添加新功能
        #2、调用原函数
        name = input('请输入用户名:').strip()
        password = input('请输入密码:').strip()
        if name == 'egon' and password:
          res = func(*args, **kwargs)
          return res
        else:
          	print('账号密码错误')
    return wrapper
  
  
@auth
def index():
  	print('from index')

index()

三 有参装饰器

由于语法糖@的限制,outter 函数只能有一个参数,并且该参数是用来接收被装饰对象的内存地址。

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

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


偷梁换柱之后:

index 的参数什么样子,wrapper 的参数就应该什么样子

index 的返回值什么样子,wrapper 的返回值就应该是什么样子

index 的属性什么样子,wrapper 的属性就应该什么样子(from functools import wraps)

需求:根据用户的数据不同来源,选择不同的验证渠道

山炮玩法:

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')  # @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()


有参装饰器的模板

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

叠加多个装饰器,加载顺序与运行顺序

@deco1 # index=deco1(deco2.wrapper的内存地址)
@deco2 # deco2.wrapper的内存地址=deco2(deco3.wrapper的内存地址)
@deco3 # deco3.wrapper的内存地址=deco3(index)
def index():
    pass
  
index = deco1(deco2(deco3(index)))
加载顺序:deco3 deco2 deco1
运行顺序:deco1 deco2 deco3

加载与运行分析

def deco1(func1):
    def wrapper1(*args, **kwargs):
        print('正在运行 deco1.wrapper1')  #1
        res = func1(*args, **kwargs)
        print('func1 运行结束') #7
        return res
    return wrapper1


def deco2(func2):
    def wrapper2(*args, **kwargs):
        print('正在运行 deco2.wrapper2') #2
        res = func2(*args, **kwargs)
        print('func2 运行结束') #6
        return res
    return wrapper2


def deco3(num):
    def outter(func3):
        def wrapper3(*args, **kwargs):
            print('正在运行deco3.outter.wrapper3') #3
            res = func3(*args, **kwargs)
            print('func3 运行结束') #5
            return res
        return wrapper3
    return outter

#加载顺序:自下而上
@deco1  #index = deco1(func1 ==> wrapper2的内存地址) ======>wrapper1的内存地址
@deco2  # index = deco2(func2 ==> wrapper3的内存地址) =====>wrapper2内存地址
@deco3(111) #==》@outter==> index = outter(func3 ==>index) ===>wrapper3内存地址
def index(x,y):
    print('from index %s %s'%(x,y)) #4

#加载顺序:自下而上 即:wrapper1 --->wrapper2----->wrapper3
res = index(1,2) #warpper(1,2)

posted @ 2020-03-23 18:08  蛋蛋的丶夜  阅读(127)  评论(0编辑  收藏  举报