各种装饰器demo及优化
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time : 2018/5/28 14:06
# @File : lianxi.py
#
# import time
# def decorator(func):
# def wrapper(*args, **kwargs):
# start_time = time.time()
# func()
# end_time = time.time()
# print(end_time - start_time)
#
# return wrapper
#
# @decorator
# def func():
# time.sleep(0.8)
#
# func() # 函数调用
# 示例一
# #既不需要侵入,也不需要函数重复执行
# import time
#
# def deco(func):
# def wrapper():
# startTime = time.time()
# func()
# endTime = time.time()
# msecs = (endTime - startTime)*1000
# print("time is %d ms" %msecs)
# return wrapper
#
#
# @deco
# def func():
# print("hello")
# time.sleep(1)
# print("world")
#
# if __name__ == '__main__':
# f = func #这里f被赋值为func,执行f()就是执行func()
# f()
# #带有参数的装饰器
# import time
#
# def deco(func):
# def wrapper(a,b):
# startTime = time.time()
# func(a,b)
# endTime = time.time()
# msecs = (endTime - startTime)*1000
# print("time is %d ms" %msecs)
# return wrapper
#
#
# @deco
# def func(a,b):
# print("hello,here is a func for add :")
# time.sleep(1)
# print("result is %d" %(a+b))
#
# if __name__ == '__main__':
# f = func
# f(3,4)
# #func()
# #带有不定参数的装饰器
# import time
#
# def deco(func):
# def wrapper(*args, **kwargs):
# startTime = time.time()
# func(*args, **kwargs)
# endTime = time.time()
# msecs = (endTime - startTime)*1000
# print("time is %d ms" %msecs)
# return wrapper
#
#
# @deco
# def func(a,b):
# print("hello,here is a func for add :")
# time.sleep(1)
# print("result is %d" %(a+b))
#
# @deco
# def func2(a,b,c):
# print("hello,here is a2 func for add :")
# time.sleep(1)
# print("result2 is %d" %(a+b+c))
#
#
# if __name__ == '__main__':
# f = func
# func2(3,4,5)
# f(3,4)
# #func()
# 运行结果
# hello,here is a2
# func
# for add:
# result2 is 12
# time is 1000
# ms
# hello,here is a
# func
# for add:
# result is 7
# time is 1000
# ms
#
# #多个装饰器
#
# import time
#
# def deco01(func):
# def wrapper(*args, **kwargs):
# print("this is deco01")
# startTime = time.time()
# func(*args, **kwargs)
# endTime = time.time()
# msecs = (endTime - startTime)*1000
# print("time is %d ms" %msecs)
# print("deco01 end here")
# return wrapper
#
# def deco02(func):
# def wrapper(*args, **kwargs):
# print("this is deco02")
# func(*args, **kwargs)
#
# print("deco02 end here")
# return wrapper
#
# @deco01
# @deco02
# def func(a,b):
# print("hello,here is a func for add :")
# time.sleep(1)
# print("result is %d" %(a+b))
#
#
#
# if __name__ == '__main__':
# f = func
# f(3,4)
# #func()
#
# # 注意执行顺序
# '''
# this is deco01
# this is deco02
# hello,here is a func for add :
# result is 7
# deco02 end here
# time is 1000 ms
# deco01 end here
# '''
# 示例二
# 版本一1.1
# def debug(func):
# def wrapper():
# print( "[DEBUG]: enter {}()".format(func.__name__))
# return func()
# return wrapper
#
# def say_hello():
# print( "hello!")
#
# say_hello = debug(say_hello) # 添加功能并保持原函数名不变
# say_hello()
# 1.2 版本
# def debug(func):
# def wrapper():
# print("[DEBUG]: enter {}()".format(func.__name__))
# return func()
# return wrapper
#
# @debug
# def say_hello():
# print("hello!")
# say_hello()
# 2.0 版本
# def debug(func):
# def wrapper(something): # 指定一毛一样的参数
# print( "[DEBUG]: enter {}()".format(func.__name__))
# return func(something)
# return wrapper # 返回包装过函数
#
# @debug
# def say(something):
# print( "hello {}!".format(something))
#
# say('decorator.......')
# # 版本 3
# def debug(func):
# def wrapper(*args, **kwargs): # 指定宇宙无敌参数
# print( "[DEBUG]: enter {}()".format(func.__name__))
# print( 'Prepare and say...',)
# return func(*args, **kwargs)
# return wrapper # 返回
#
# @debug
# def say(something):
# print( "hello {}!".format(something))
# @debug
# def say2(something, aaa):
# print( "hello {},------{}!".format(something, aaa))
#
# say2('aaa', 'vvvv')
# say('aaa')
# # 版本 4
# def logging(level):
# def wrapper(func):
# def inner_wrapper(*args, **kwargs):
# print( "[{level}]: enter function {func}()".format(level=level,func=func.__name__))
# return func(*args, **kwargs)
# return inner_wrapper
# return wrapper
#
# @logging(level='INFO')
# def say(something):
# print( "say {}!".format(something))
#
# # 如果没有使用@语法,等同于
# # say = logging(level='INFO')(say)
#
# @logging(level='DEBUG')
# def do(something):
# print ("do {}...".format(something))
#
# if __name__ == '__main__':
# say('hello')
# do("my work")
# # 版本 5
# class logging(object):
# def __init__(self, func):
# self.func = func
#
# def __call__(self, *args, **kwargs):
# print( "[DEBUG]: enter function {func}()".format(func=self.func.__name__))
# return self.func(*args, **kwargs)
# @logging
# def say(something):
# print( "say {}!".format(something))
#
# say('hello')
# 版本 6
class logging(object):
def __init__(self, level='INFO'):
self.level = level
def __call__(self, func): # 接受函数
def wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(level=self.level,func=func.__name__))
func(*args, **kwargs)
return wrapper # 返回函数
@logging(level='INFO')
def say(something):
print("say {}!".format(something))
say('什么鬼')
装饰器里的那些坑
装饰器可以让你代码更加优雅,减少重复,但也不全是优点,也会带来一些问题。
位置错误的代码
让我们直接看示例代码。
def html_tags(tag_name):
print 'begin outer function.'
def wrapper_(func):
print "begin of inner wrapper function."
def wrapper(*args, **kwargs):
content = func(*args, **kwargs)
print "<{tag}>{content}</{tag}>".format(tag=tag_name, content=content)
print 'end of inner wrapper function.'
return wrapper
print 'end of outer function'
return wrapper_
@html_tags('b')
def hello(name='Toby'):
return 'Hello {}!'.format(name)
hello()
hello()
在装饰器中我在各个可能的位置都加上了print语句,用于记录被调用的情况。你知道他们最后打印出来的顺序吗?如果你心里没底,那么最好不要在装饰器函数之外添加逻辑功能,否则这个装饰器就不受你控制了。以下是输出结果:
begin outer function.
end of outer function
begin of inner wrapper function.
end of inner wrapper function.
<b>Hello Toby!</b>
<b>Hello Toby!</b>
错误的函数签名和文档
装饰器装饰过的函数看上去名字没变,其实已经变了。
def logging(func):
def wrapper(*args, **kwargs):
"""print log before a function."""
print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
return func(*args, **kwargs)
return wrapper
@logging
def say(something):
"""say something"""
print "say {}!".format(something)
print say.__name__ # wrapper
为什么会这样呢?只要你想想装饰器的语法糖@代替的东西就明白了。@等同于这样的写法。
say = logging(say)
logging其实返回的函数名字刚好是wrapper,那么上面的这个语句刚好就是把这个结果赋值给say,say的__name__自然也就是wrapper了,不仅仅是name,其他属性也都是来自wrapper,比如doc,source等等。
使用标准库里的functools.wraps,可以基本解决这个问题。
from functools import wraps
def logging(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""print log before a function."""
print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
return func(*args, **kwargs)
return wrapper
@logging
def say(something):
"""say something"""
print "say {}!".format(something)
print say.__name__ # say
print say.__doc__ # say something
看上去不错!主要问题解决了,但其实还不太完美。因为函数的签名和源码还是拿不到的。
import inspect
print inspect.getargspec(say) # failed
print inspect.getsource(say) # failed
如果要彻底解决这个问题可以借用第三方包,比如wrapt。后文有介绍。
不能装饰@staticmethod 或者 @classmethod
当你想把装饰器用在一个静态方法或者类方法时,不好意思,报错了。
class Car(object):
def __init__(self, model):
self.model = model
@logging # 装饰实例方法,OK
def run(self):
print "{} is running!".format(self.model)
@logging # 装饰静态方法,Failed
@staticmethod
def check_model_for(obj):
if isinstance(obj, Car):
print "The model of your car is {}".format(obj.model)
else:
print "{} is not a car!".format(obj)
"""
Traceback (most recent call last):
...
File "example_4.py", line 10, in logging
@wraps(func)
File "C:\Python27\lib\functools.py", line 33, in update_wrapper
setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'staticmethod' object has no attribute '__module__'
"""
前面已经解释了@staticmethod这个装饰器,其实它返回的并不是一个callable对象,而是一个staticmethod对象,那么它是不符合装饰器要求的(比如传入一个callable对象),你自然不能在它之上再加别的装饰器。要解决这个问题很简单,只要把你的装饰器放在@staticmethod之前就好了,因为你的装饰器返回的还是一个正常的函数,然后再加上一个@staticmethod是不会出问题的。
class Car(object):
def __init__(self, model):
self.model = model
@staticmethod
@logging # 在@staticmethod之前装饰,OK
def check_model_for(obj):
pass
如何优化你的装饰器
嵌套的装饰函数不太直观,我们可以使用第三方包类改进这样的情况,让装饰器函数可读性更好。
decorator.py
decorator.py 是一个非常简单的装饰器加强包。你可以很直观的先定义包装函数wrapper(),再使用decorate(func, wrapper)方法就可以完成一个装饰器。
from decorator import decorate
def wrapper(func, *args, **kwargs):
"""print log before a function."""
print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
return func(*args, **kwargs)
def logging(func):
return decorate(func, wrapper) # 用wrapper装饰func
你也可以使用它自带的@decorator装饰器来完成你的装饰器。
from decorator import decorator
@decorator
def logging(func, *args, **kwargs):
print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
return func(*args, **kwargs)
decorator.py实现的装饰器能完整保留原函数的name,doc和args,唯一有问题的就是inspect.getsource(func)返回的还是装饰器的源代码,你需要改成inspect.getsource(func.__wrapped__)。
wrapt
wrapt是一个功能非常完善的包,用于实现各种你想到或者你没想到的装饰器。使用wrapt实现的装饰器你不需要担心之前inspect中遇到的所有问题,因为它都帮你处理了,甚至inspect.getsource(func)也准确无误。
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
使用wrapt你只需要定义一个装饰器函数,但是函数签名是固定的,必须是(wrapped, instance, args, kwargs),注意第二个参数instance是必须的,就算你不用它。当装饰器装饰在不同位置时它将得到不同的值,比如装饰在类实例方法时你可以拿到这个类实例。根据instance的值你能够更加灵活的调整你的装饰器。另外,args和kwargs也是固定的,注意前面没有星号。在装饰器内部调用原函数时才带星号。
如果你需要使用wrapt写一个带参数的装饰器,可以这样写。
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
关于wrapt的使用,建议查阅官方文档,在此不在赘述。
http://wrapt.readthedocs.io/en/latest/quick-start.html
小结
Python的装饰器和Java的注解(Annotation)并不是同一回事,和C#中的特性(Attribute)也不一样,完全是两个概念。
装饰器的理念是对原函数、对象的加强,相当于重新封装,所以一般装饰器函数都被命名为wrapper(),意义在于包装。函数只有在被调用时才会发挥其作用。比如@logging装饰器可以在函数执行时额外输出日志,@cache装饰过的函数可以缓存计算结果等等。
而注解和特性则是对目标函数或对象添加一些属性,相当于将其分类。这些属性可以通过反射拿到,在程序运行时对不同的特性函数或对象加以干预。比如带有Setup的函数就当成准备步骤执行,或者找到所有带有TestMethod的函数依次执行等等。
至此我所了解的装饰器已经讲完,但是还有一些内容没有提到,比如装饰类的装饰器。有机会再补充。谢谢观看。