custom_decorator
自定义装饰器
1 写一个简单的装饰器
def foo():
print 'foo'
foo #表示是函数
foo() #表示执行 foo 函数
1.1 一个普通的装饰器
装饰器
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('call %s():' % func.__name__)
print('args = {}'.format(*args))
return func(*args, **kwargs)
return wrapper
这样就定义了一个打印出方法名及其参数的装饰器。
使用
@log
def test(p):
print(test.__name__ + " param: " + p)
test("I'm a param")
备注
值得注意的是 @functools.wraps(func),这是 python 提供的装饰器。它能把原函数的元信息拷贝到装饰器里面的 func 函数中。
函数的元信息包括 docstring、name、参数列表等等。
可以尝试去除 @functools.wraps(func),你会发现 test.__name__的输出变成了 wrapper。
1.2 带参数的装饰器
import functools
def log_with_param(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('call %s():' % func.__name__)
print('args = {}'.format(*args))
print('log_param = {}'.format(text))
return func(*args, **kwargs)
return wrapper
return decorator
@log_with_param("param")
def test_with_param(p):
print(test_with_param.__name__)
等同于
# 传入装饰器的参数,并接收返回的 decorator 函数
decorator = log_with_param("param")
# 传入 test_with_param 函数
wrapper = decorator(test_with_param)
# 调用装饰器函数
wrapper("I'm a param")
1.3 类装饰器
在 python 中我们可以利用一个函数作为装饰器来装饰另一个函数,但是装饰器只能是函数吗 ? 当然了我们还可以使用类来作为装饰器!
class A(object):
def __init__(self,func):
print('定义初始化函数')
print('func name is %s'%func.__name__)
self.__func = func
def __call__(self):
print('call 方法作为装饰器中的功能')
self.__func()
print('增加的功能 2')
@A
def B():
print('这个是 B 是原函数')
def C():
print('这个是 C 是原函数')
if __name__ == "__main__":
# 装饰器
print "---------------"
B()
# 直接在 A 中调用 C
print "---------------"
# 等同于 x=A(C);x()
# 允许一个类的实例像函数一样被调用。实质上说,这意味着 x() 与 x.__call__() 是相同的。
A(C)()
这个的输出结果是:
定义初始化函数
func name is B
---------------
call 方法作为装饰器中的功能
这个是 B 是原函数
增加的功能 2
---------------
定义初始化函数
func name is C
call 方法作为装饰器中的功能
这个是 C 是原函数
增加的功能 2
其中 butterfly 中的 handler 都是被 protocol_json.Protocol 装饰
2 装饰器的本质
2.1 本质
装饰器本质就是返回了个新的函数
即
# 将原函数作为参数传入
def decorator(func):
def newfunc():
# 调用之前 do something
pass
return func() # 调用原函数
# 将新函数返回
return newfunc
def func(): pass
func = decorator(func) # 手动的把新函数赋值给旧函数
func() # newfunc()
变为了
@decorator # 相当于 func = decorator(func)
def func(): pass
2.2 装饰器加强库
decorator.py 和 wrapt 两个库是装饰器加强库,可以很方便的定义和使用装饰器函数,wrapt 比 decorator.py 功能更强些
使用 wrapt 库可以相当于对返回新函数的这块进行了封装,用户只需要实现 newfunc 的功能即可,即
原方式
def decorator(func):
def newfunc():
# 调用之前 do something
pass
return func() # 调用原函数
# 将新函数返回
return newfunc
现方式
import wrapt
# without argument in decorator
@wrapt.decorator
def newfunc(wrapped, instance, args, kwargs): # instance is must
# 调用之前 do something
pass
return wrapped(*args, **kwargs)
3 wrapt
使用 wrapt 只需要定义一个装饰器函数,但是函数签名是固定的,必须是 (wrapped, instance, args, kwargs)
注意第二个参数 instance 是必须的,就算你不用它。当装饰器装饰在不同位置时它将得到不同的值,比如装饰在类实例方法时你可以拿到这个类实例。根据 instance 的值能够更加灵活的调整装饰器
另外,args 和 kwargs 也是固定的,注意前面没有星号。在装饰器内部调用原函数时才带星号。
3.1 不带参数的装饰器
import wrapt
# without argument in decorator
@wrapt.decorator
def logging(wrapped, instance, args, kwargs): # instance is must
print "[DEBUG]: enter {}()".format(wrapped.__name__)
return wrapped(*args, **kwargs)
@logging
def say(something): pass
上面的功能相当于如下功能:
def logging(func): # 将原函数作为参数传入
def newfunc(*args, **kwargs):
print "[DEBUG]: enter"
return func(*args, **kwargs) # 调用原函数
# 将新函数返回
return newfunc
@logging # 相当于 say = logging(say)
def say(something): pass
3.2 带参数的装饰器
def logging(level):
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
print "[{}]: enter {}()".format(level, wrapped.__name__)
return wrapped(*args, **kwargs)
return wrapper
@logging(level="INFO")
def do(work): pass
4 handler 装饰器例子
# coding=utf8
"""
# Description:
api demo
"""
import logging
from xlib import retstat
from xlib.util import wrapt
from xlib.util import funcsigs
from xlib.middleware import funcattr
from xlib.httpgateway import Request
__info = "demo"
__version = "1.0.1"
logger = logging.getLogger("service")
def requested(method):
"""
装饰器:限制请求方法
"""
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
"""
Args:
- wrapped:被装饰的函数或类方法
- instance:
- 如果被装饰者为普通类方法,该值为类实例
- 如果被装饰者为 classmethod 类方法,该值为类
- 如果被装饰者为类/函数/静态方法,该值为 None
- args:调用时的位置参数(注意没有 * 符号)
- kwargs:调用时的关键字参数(注意没有 ** 符号)
"""
# (1) 提取原 func 中的 req, 不论使用位置参数还是关键字参数
sig = funcsigs.signature(wrapped)
# 将位置参数 args 转为 dict kargs
kargs = dict(zip([k for k in sig.parameters if k not in kwargs], args))
req = kargs.get("req", kwargs.get("req"))
if req.wsgienv.get("REQUEST_METHOD") != method:
req.error_str = "method not support"
return retstat.ERR, {}, [(__info, __version)]
return wrapped(*args, **kwargs)
return wrapper
@funcattr.api
@requested("GET")
def hello(req, str_info):
"""
demo
"""
isinstance(req, Request)
logger.info("str_info={str_info}".format(str_info=str_info))
return retstat.OK, {"str_info": str_info}, [(__info, __version)]
5 传送门
Python 中使用装饰器时需要注意的一些问题 ------------ 从这个里面发现了 wrapt 这个神器
Last updated