python——装饰器

装饰器用于给已有函数在不改变已有函数的情况下额外增加功能
使用场景有增加统一日志、埋点、性能调优

  简单装饰器的写法

def printlog(func):
    def wrapper():
        print("[DEBUG LOG]:  enter {}".format(func.__name__))
        return func()
    return wrapper

@printlog
def saygoodbye():
    print("good Bye")


if __name__ == '__main__':
    saygoodbye()
    print(saygoodbye.__name__)
--------------
打印结果
[DEBUG LOG]:  enter saygoodbye
good Bye
wrapper

调用过程:
1.main函数调用了saygoodbye函数
2.saygoodbye 打上了printlog装饰器的标签,=>转去执行printlog
3.printlog接收一个函数,即将saygoodbye函数传入printlog内
4.printlog 直接return了wrapper,wrapper是一个内嵌函数=>转去执行wrapper
5.wrapper函数首先打印[DEBUG LOG]:  enter (func.__name__),将saygoodbye函数名打印出来
6.wrapper函数 return func(),=>转去执行原函数saygoodbye

整个过程就是在执行saygoodbye函数前,首先去执行装饰器中的功能,最后return到原调用函数,实现了给原有函数增加功能

装饰器有两个必须条件装饰器接收一个函数,并返回一个函数需要使用装饰器的函数在函数上方使用@装饰器名去关联装饰器

        装饰器接收的函数就是关联装饰器的函数,返回的函数是装饰器中内部定义的内嵌函数,这个内嵌函数最后会返回原调用函数。
    
        拿上面例子代码解释一下:装饰器printlog接收的函数是saygoodbye,返回的函数是装饰器中定义的wrapper函数,wrapper函数返回的是原调用函数saygoodbye
    
        但是最后一行print(saygoodbye.__name__),却返回的不是saygoodbye,而是装饰器中的内嵌函数wrapper,原来saygoodbye已经被printlog装饰器装饰成了wrapper函数,我们却希望得到的仍是saygoodbye这个原函数
所以装饰器中的内嵌函数使用@functools.wraps(func)来消除这个问题

def printlog(func):
    @functools.wraps(func)
    def wrapper():
        print("[DEBUG LOG]:  enter {}".format(func.__name__))
        return func()
    return wrapper

@printlog
def saygoodbye():
    print("good Bye")


if __name__ == '__main__':
    saygoodbye()
    print(saygoodbye.__name__)
-----------------
返回结果
[DEBUG LOG]:  enter saygoodbye
good Bye
saygoodbye

可见,被装饰的函数因为有了@functools.wraps(func)没有被改变名称

进阶装饰器——函数传参

上一个举例中saygoodbye函数没有参数,那么如果是有参数的函数想要用装饰器怎么办?

我们在内嵌函数定义时将参数原封不动传入,然后return原函数时再传递给原函数

这里使用到可变传参、关键字传参,即使参数个数不同、或者无参数都可以正常调用

def printlog(func):
    @functools.wraps(func)
    def wrapper(*args,**kws):
        print("[DEBUG LOG]:  enter {}".format(func.__name__))
        return func(*args,**kws)
    return wrapper

        装饰器内嵌函数使用(*args,**kws)将函数参数收入,然后又传入原调用函数func(*args,**kws),下面是调用举例

@printlog
def say(content='hello'):
    print("say {}".format(content))
@printlog
def do(sb,sth):
    print('{} is  {}'.format(sb,sth))
@printlog
def doSthInWhere(sb,sth,where):
    print('{} is  {} in {}'.format(sb,sth,where))
@printlog
def introduce(**map):
    print(map)
    for key in map:
        print('{} is {} years old'.format(key,map[key]))
if __name__ == '__main__':
    say('hello')
    do('Lily','sleep')
    do(*['Lily', 'sleep'])
    doSthInWhere('Lily','sleep','home')
    stu = {'Tom':10,'Jack':11}
    introduce(**stu)

可以看到,不论是几个传参个数,都可以成功使用装饰器的功能

装饰器再进阶——装饰器传参:

如果装饰器中的新增功能,想要根据装饰器的参数做对应的操作,如日志打印想根据传参打印日志级别,那么就需要在用装饰器的时候给他传入参数

实现办法:在上面装饰器的外面再包一层函数定义,函数传入装饰器的参数,然后在用装饰器的时候给他传入参数

def outputlog(level):
    def printlog(func):
        @functools.wraps(func)
        def wrapper(*args,**kws):
            print("[{level} LOG]:  enter {funcname}".format(level=level,funcname=func.__name__))
            return func(*args,**kws)
        return wrapper
    return printlog
@outputlog('INFO')
def say(content='hello'):
    print("say {}".format(content))
if __name__ == '__main__':
    say('hello')
--------------
打印结果:
[INFO LOG]:  enter say
say hello

这样,就可以实现装饰器中传入参数

编写类装饰器

将装饰器函数改写为类装饰器,将装饰器的调用函数及装饰器的参数定义写在init中,内嵌函数写在___call__魔法方法里,这样看着代码可读性更好一些

定义没有装饰器参数的类装饰器

class outputlog(object):
#在init中赋值func给类装饰器
    def __init__(self,func):
        self.func= func
#在__call__中编写需要增加的功能代码,最后return 调用原函数
    def __call__(self, *args, **kwargs):
            print("[DEBUG LOG]:  enter {func}".format( func=self.func.__name__))
            return self.func(*args, **kwargs)

传入参数的类装饰器

class outputlog(object):
    def __init__(self,level="INFO"):
        self.level= level

    def __call__(self,func):
            @functools.wraps(func)
            def innerfun(*args, **kwargs):
                print("[{} LOG]:  enter {func}".format( self.level,func=func.__name__))
                # print("[{level} LOG]:  enter {funcname}".format(level=self.level, funcname=self.func.__name__))
                func(*args, **kwargs)
            return innerfun


@outputlog()
def say(content='hello'):
    print("say {}".format(content))

@outputlog('INFO')
def do(sb,sth):
    print('{} is  {}'.format(sb,sth))

if __name__ == '__main__':
    say()
    say('hello')
----------------
结果:
[INFO LOG]:  enter say
say hello
[INFO LOG]:  enter say
say hello

在init中只传入类装饰器的参数,在__call__传入调用函数func,这时__call__相当于外面包的那层函数,里面的内嵌函数使用@functools.wraps(func)修饰,并在函数中调用func,不用写return

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值