风-fmgao

导航

各种装饰器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的函数依次执行等等。

至此我所了解的装饰器已经讲完,但是还有一些内容没有提到,比如装饰类的装饰器。有机会再补充。谢谢观看。

posted on 2018-05-28 15:01  风-fmgao  阅读(186)  评论(0编辑  收藏  举报