24.python装饰器

2022-02-17  本文已影响0人  LIKESUNE

python装饰器的本质是做装饰用的,比如你在手机外面贴了张膜,那这个膜作为装饰品,并不影响你原来的手机的使用方式,并且这个装饰品还给你增加了防摔的新特性。python装饰器亦是如此,它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。下面我们一步步来研究一下

def  hi():
     print("Hi")
hi() 
# output: hi

这是一个最简单的函数,我们通过hi()的方式,对函数进行了调用。其实在python中,万物皆可引用,如下:

def  hi():
     print("Hi")
hello = hi
hello()
# output:hi

这里我们只是把hi这个函数引用给到hello了,并没有立刻调用这个函数。引用就是这个函数的寻址方式。比如一个人的名字叫张三,你给重新起了一个名字叫张狗蛋,这两个名字我们都能找到这个人。那这个有什么用那?接着往下看。
有一天我需要用统计一下函数执行的日志信息,于是函数改成了:

def  hi():
    logging.info("%s is running" % hi.__name__)
    print("Hi")
hi()
#output: hi is running
#        Hi

问题得到了短暂的解决,但是随着代码越来越多,代码中又增加了hello函数和good函数。

def  hi():
    logging.info("%s is running" % func.__name__)
    print("Hi")

def  hello():
    print("Hello")

def  good():
    print("Good")
hi()

这几个函数都需要打印日志,于是你想到,定义一个日志打印函数,把这些函数当一个参数传入不就行了,于是:

def log(func):
    logging.warning("%s is running" % func.__name__)
    func()

def  hi():
    print("Hi")

def  hello():
    print("Hello")

def  good():
    print("Good")   

# 注意这里传入的是参数名,不是hi(),hi()的话函数就执行了
log(hi)
# output:Hi
# WARNING:root:hi is running
log(hello)
# output:Hello
# WARNING:root:hello is running
log(good)
# output:Good
# WARNING:root:good is running

这样似乎问题得到了解决,但是有一个问题就是函数的调用方式被我们改变了,如果这是一个大工程,那么把所有原来的函数调用方式都改一遍是我们不希望看到的。那么能不能在不改变原函数使用方式的情况下,完成我们的需求,当然可以,也就是使用我们的装饰器:

# 这是一个装饰器,返回的是一个函数wrapfunc的引用
def  decorator(func):
       def wrapfunc():
           logging.warning("%s is running" % func.__name__)
           func()
       return wrapfunc  # 注意这里返回的是引用,不要加括号

def  hi():
    print("Hi")

def  hello():
    print("Hello")

hi = decorator(hi)
hello = decorator(hello)
# 调用方式没有发生改变
hi()
hello()

# output:
#Hi
#Hello
#WARNING:root:hi is running
#WARNING:root:hello is running

为了偷懒,我们将hi=decorator(hi),hello =decorator(hello)简化为@函数名放在待修饰函数头上,如下:

# 这是一个装饰器,返回的是一个函数wrapfunc的引用
def  decorator(func):
       def wrapfunc():
           logging.warning("%s is running" % func.__name__)
           func()
       return wrapfunc  # 注意这里返回的是引用,不要加括号
@decorator
def  hi():
    print("Hi")

@ decorator
def  hello():
    print("Hello")

# 调用方式没有发生改变
hi()
hello()

也就是直接把装饰器的名字装饰到函数的头上去,这样一个简单的装饰器我们就完成了,啥?这个装饰器不够强大,不能传参,那简单:

# 这是一个装饰器,返回的是一个函数wrapfunc的引用
def out_func(leval):
    def  decorator(func):
           def wrapfunc():
               if leval == "warning":
                    logging.warning("%s is running" % func.__name__)
               func()
           return wrapfunc
    return decorator

@out_func(leval="warning")
def  hi():
    print("Hi")

@out_func(leval="info")
def  hello():
    print("Hello")

# 调用方式没有发生改变
hi()
hello()

# output:
#Hi
#Hello
#WARNING:root:hi is running

可以从输出的结果看到,我们通过对装饰器的参数传入,控制了我们是否要打印函数的日志信息。hi打印了,hello没有打印。

类装饰器

刚才我们的装饰器,是一个函数装饰器,那么当然也可以写一个类装饰器了。相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。

#我们写一个类用作装饰器
class decorator(object):
    def __init__(self,func):
        self._func = func

    def __call__(self, *args, **kwargs):
        logging.warning("%s is running" % self._func.__name__)
        self._func()
# 把这个类作为装饰器调用的时候还在方法的头上加上@类名这个其实就等价于  hi = decorator(hi)
@decorator
def  hi():
    print("Hi")

@decorator
def  hello():
    print("Hello")

# 调用方式没有发生改变
hi()
hello()

到这里我们讲了装饰器函数和装饰器类,但是使用装饰器有一个弊端就是会导致原信息的丢失,比如函数的docstring、name、参数列表,什么意思?

# 这是一个装饰器,返回的是一个函数wrapfunc的引用
def  decorator(func):
       def wrapfunc():
           logging.warning("%s is running" % func.__name__)
           func()
       return wrapfunc  # 注意这里返回的是引用,不要加括号
@decorator
def  hi():
    print("Hi")
print(hi)

# output:<function wrapfunc at 0x10d909398>

可以看到原本我们的函数名是hi,结果加了个装饰器后,这些信息变成了warpfunc的了,怎么办,我们可以在装饰器函数的头部使用functools.wraps装饰器来解决这个问题。

# coding=utf-8
import logging
import functools
# 这是一个装饰器,返回的是一个函数wrapfunc的引用
def  decorator(func):
        @functools.wraps(func)
        def wrapfunc():
           logging.warning("%s is running" % func.__name__)
           func()
        return wrapfunc  # 注意这里返回的是引用,不要加括号

def  hi():
    print("Hi")
hi = decorator(hi)
print hi
# output:<function hi at 0x10a4bf398>
上一篇 下一篇

猜你喜欢

热点阅读