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>
网友评论