Python知识——装饰器、有参装饰器

装饰器

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

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

为何要用装饰器:
开放封闭原则:

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

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

如何使用装饰器:
需求:在不修改index函数的源代码以及调用方式的前提下为其添加统计运行时间的功能

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

解决方案一

import time


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


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

'''
index 111 222
3.0009870529174805
'''

这个解决方法不但没修改被装饰器对象源代码以及调用方式,也实现了新功能。但是如果多次调用函数,就无法多次使用,如果再每次调用函数的时候,都在调用函数的位置加上上述方法,就形成了同样的代码,多次出现。
解决这个方法的思路可以是将此过程写成一个函数

解决方案二:

import time


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

def wapper():
	statr = time.time()
	index(111, 222)
	stop = time.time()
	print(stop-statr)

wapper()

'''
index 111 222
3.0009870529174805
'''

这个方案解决了相同代码出现多次的问题,但是函数的调用方式发生了改变。

解决方案二的优化一:

import time


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

def wapper(*args,**kwargs): #将index的参数写活了
	statr = time.time()
	index(*args,**kwargs)
	stop = time.time()
	print(stop-statr)

wapper(111,222)

这个优化方案可以以index函数的方式接收参数,但是调用的函数名字不是index,而且如果不仅仅想修饰index函数,也想修饰其他函数,如何再此优化

解决方案二的优化二:

import time

def home(name):
	time.sleep(2)
	print('welcome %s come home' %name)
def index(x, y):
    time.sleep(3)
    print('index %s %s' % (x, y))

def outter(func):
	#func=index的内存地址
	def wapper(*args,**kwargs):
		statr = time.time()
		func(*args,**kwargs) #调用index函数
		stop = time.time()
		print(stop-statr)
	return wapper
index=outter(index)  #首先将index函数的内存地址给了outter,wapper就会调用index函数,最后将wapper的内存地址返回,用index变量接收wapper内存地址
index() #此时的index调用的其实是wapper函数,而不是直接调用index函数
home=outter(home)
home('zhangs')  #此时可以将装饰器给多个函数

优化二使用了闭包的概念,可以通过传入不同函数的内存地址,使得装饰器可以运用到多个函数。但其实这个方法还是有问题的,就是如果被装饰的函数需要返回值,这个优化无法接收返回值。

解决方案二的优化三:

import time


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


def index(x, y):
    time.sleep(3)
    print('index %s %s' % (x, y))
    return 123   #被修饰的函数拥有返回值


def outter(func):
    def wapper(*args, **kwargs):
        statr = time.time()
        res = func(*args, **kwargs)   #用变量res接收函数index的返回值
        stop = time.time()
        print(stop-statr)
        return res  #返回index函数的返回值
    return wapper


index = outter(index)
ret = index(1, 2)   #接收index函数的返回值
print(ret)

'''
index 1 2
3.0008342266082764
123
'''

语法糖

在进行装饰器操作的时候,每一次都需要执行两次重复的代码,就是定义一个与被修饰函数一样的变量,再将闭包函数的返回值赋值给变量,变量才可以进行函数调用
有没有一种方法可以省去这个复杂的过程?

import time


def outter(func):
    def wapper(*args, **kwargs):
        statr = time.time()
        res = func(*args, **kwargs)   #用变量res接收函数index的返回值
        stop = time.time()
        print(stop-statr)
        return res  #返回index函数的返回值
    return wapper

@outter
def home(name):
    time.sleep(2)
    print('welcome %s come home' % name)

@outter
def index(x, y):
    time.sleep(3)
    print('index %s %s' % (x, y))
    return 123   #被修饰的函数拥有返回值

index(1,2)

'''
index 1 2
3.0001492500305176
'''

需要将装饰器放在最上方,避免调用函数的时候,函数为定义。以及@符号一定要在被修饰的函数正上方,@紧跟装饰器名字

装饰器补充
装饰器虽然表面可以将函数调用达到“偷梁换柱”的效果,但是当在查看被装饰器装饰前函数的内存地址,以及被装饰器装饰器后的内存地址

import time


def outter(func):
    def wapper(*args, **kwargs):
        statr = time.time()
        res = func(*args, **kwargs)  # 用变量res接收函数index的返回值
        stop = time.time()
        print(stop-statr)
        return res  # 返回index函数的返回值
    return wapper


# @outter
def index(x, y):
    time.sleep(3)
    print('index %s %s' % (x, y))
    return 123  # 被修饰的函数拥有返回值


# index(1, 2)
print(index.__name__)

'''
index

wapper
'''

由上述了解到使用了装饰器虽然可以达到直接调用函数的作用,但是在一些方面还是无法做到真正的“偷梁换柱”,还有被装饰的函数中的注释是否相等,等等

将原函数的属性赋值给wapper

import time
from functools import wraps 

def outter(func):
    #@wraps(func)
    def wapper(*args, **kwargs):
        statr = time.time()
        res = func(*args, **kwargs)  
        stop = time.time()
        print(stop-statr)
        return res  
    return wapper


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


index(1, 2)
print(index.__name__)


'''
index

index
'''

有参装饰器

有这么一个登录验证的代码:

def auth(func):
    def wapper(*args, **kwargs):
        name = input('请输入你的用户名:').strip()
        pwd = input('请输入你的密码:').strip()
        #从文件中取帐号密码
        if name == 'aaa' and pwd == '123':
            print('登录成功')
            res = func(*args,**kwargs)
            return res
        else:
            print('登录失败')
    return wapper


@auth
def index(x,y):
    print('index——>%s,%s'%(x,y))

index(1,2)

'''
请输入你的用户名:aaa
请输入你的密码:123
登录成功
index——>1,2
'''

在现实运用过程中,帐号密码是从多个软件或者文件进行读取的,在上述代码中只考虑了一种读取方式(也许是文件读取),还有其他软件中取得帐号密码,比如:数据库、ldap等等。
所以如果数据来源于不同,需要的装饰器代码也许也不同。

在使用wapper函数前,进行一次判断

def auth(func):
    def wapper(*args, **kwargs):
        name = input('请输入你的用户名:').strip()
        pwd = input('请输入你的密码:').strip()
        if db_type =='file':  #判断数据如果来自文件,执行以下代码
            print('基于文件的数据')
            res= func(*args, **kwargs)
            return res
        elif db_type == 'mysql': #判断数据如果来自数据库,执行以下代码
            print('基于数据库的数据')
            res= func(*args, **kwargs)
            return res
        elif db_type =='ldap': #判断数据如果来自ldap,执行以下代码
            print('基于ldap的数据')
            res= func(*args, **kwargs)
            return res
    return wapper


@auth
def file(x,y):
    print('file——>%s,%s'%(x,y))

def mysql(x,y):
    print('mysql——>%s,%s'%(x,y))

def ldap(x,y):
    print('ldap——>%s,%s'%(x,y))

问题就来了,db_type是一个参数,如何给函数传参?

在wapper函数传参?

def wapper(*args,**kwargs,db_type)

wapper函数的参数是为了被装饰的函数服务,如果在wapper函数加入,被装饰的函数在函数调用阶段也需要更改,这就不符合装饰器原本的作用

在auth函数传参?

def auth(func,db_type):

'''
Traceback (most recent call last):
  File "d:\python\study_6_18.py", line 63, in <module>
    def file(x,y):
TypeError: auth() missing 1 required positional argument: 'db_type'
'''

此时会报语法错误,在传参的时候少一个参数。
也不行

可以再次运用闭包,将外层再闭包一层函数

def outer(db_type):
    def auth(func):
        def wapper(*args, **kwargs):
          ...
        return wapper
    return auth


@outer('file')  #先调用outer('file'),返回了auth的内存地址,之后就变成了@auth——> file=auth(file)
def file(x,y):
    print('file——>%s,%s'%(x,y))

@outer('mysql')
def mysql(x,y):
    print('mysql——>%s,%s'%(x,y))

@outer('ldap')
def ldap(x,y):
    print('ldap——>%s,%s'%(x,y))


mysql(1,2)

'''
请输入你的用户名:aaa
请输入你的密码:123
基于数据库的数据
mysql——>1,2
'''
  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值