装饰器本质以及装饰器产生的问题

装饰器介绍

装饰器的本质:一个闭包函数

装饰器的功能:在不修改原函数及其调用方式的情况下对原函数功能进行扩展

一个简单的装饰器实例

def time(func):#定义一个装饰器,接收一个函数作为参数
    def inner(*args,**kwargs):#在装饰器内定义一个内部函数
        """执行函数之前要做的"""
        re = func(*args,**kwargs)#被装饰的函数并且要执行,并接受结果
        """执行函数之后要做的"""
        return re#返回原函数的运行结果
    return inner#返回内部函数名

def func():
    pass
func = time(func) # 此时func ==inner

python为我们提供了简单写法

def time(func):
    def inner(*args,**kwargs):
        """执行函数之前要做的"""
        re = func(*args,**kwargs)
        """执行函数之后要做的"""
        return re#返回原函数的运行结果
    return inner

@time
def func():
    pass
# @time就等于func = time(func),下面要紧接函数
#当再次调用函数func是实际调用的是inner
#接收到的参数会被inner(*args,**kwargs)接受,并存储到元祖或字典中
#而args或者kwargs在传递到原函数时又会被分解
#从而实现了不改变函数调用方式,添加了新功能

当我们明白装饰器的本质之后,就可以去搞搞更多的装饰器,比如带参数的装饰器,以及用类写一个装饰器

带参数的函数装饰器:@xxx()就相当于xxx()的返回结果还可以接受一个函数被调用,然后再返回一个函数

def deco(arg):  
    def _deco(func):  
        def __deco():  
            print("before %s called [%s]." % (func.__name__, arg))  
            func()  
            print("  after %s called [%s]." % (func.__name__, arg))  
        return __deco  
    return _deco  
 
@deco("mymodule")  
def myfunc():  
    print(" myfunc() called.")  

用类实现一个装饰器:func = Clsaa(func),func()就是执行了Class中的__call__

class ttl_func:
    def __init__(self,ttl_property):
        self.ttl_property=ttl_property
    def __call__(self, *args, **kwargs):
          # 函数执行前做的
          ret = self.ttl_property( *args, **kwargs)
          # 函数执行后做的
          return ret
@ttl_property
def cc():
    print(time.time())

装饰器产生的问题

def outer(func):
    def inner(*args,**kwargs):
        """我是装饰器里的函数"""
        func(*args,**kwargs)
    return inner
 
@outer
def function():
    """我是被装饰的函数"""
    print("哈哈哈")
 
print(function.__name__) # 函数名
print(function.__doc__)  # 函数注释
# 打印了装饰其中的内容,function其实就是outer(function) 就是inner函数
# inner
# 我是装饰器里的函数
 
# 修复方法
from functools import wraps
def outer(func):
    @wraps(func)
    def inner(*args,**kwargs):
        """我是装饰器里的函数"""
        func(*args,**kwargs)
    return inner
 
@outer
def function():
    """我是被装饰的函数"""
    print("哈哈哈")
 
print(function.__name__) # 函数名
print(function.__doc__)  # 函数注释
 
#function
#我是被装饰的函数

来了解一下wraps是如何做到的,首先我们要介绍一下partial和update_wrapper函数

functools.partial函数

该函数接收一个函数func和一些位置参数和关键字参数,内部会将这些参数绑定给func并返回一个新的函数,如果一个关键字参数被重复传入,后面的值会覆盖前面的值

from functools import partial

def add(x, y):
    return x+y

# 关键字传入
add2 = partial(add, y=2)
add3 = partial(add2, y=3)
q = add2(3)  # 这里将会输出5
p = add3(3)  # 这里将会输出6

# 位置传入
add4 = partial(add, 2)
add5 = partial(add4, 3)
b = add2(3)  # 这里将会输出5
d = add3()  # 这里将会输出5

  这个函数是使用C而不是Python实现的,但是官方文档中给出了Python实现的代码,如下所示,大家可以进行参考

def partial(func, *args, **keywords): # 设置的参数
    def newfunc(*fargs, **fkeywords): # 调用的参数传入
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords) # 关键字参数的更新
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

functools.update_wrapper

源码

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):
    for attr in assigned:
        try:
            value = getattr(wrapped, attr) # 从被修饰的函数中获得指定属性
        except AttributeError:
            pass
        else:
            setattr(wrapper, attr, value) # 设置给修饰他的函数
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {})) # 更新修饰字典的键值
    wrapper.__wrapped__ = wrapped
    return wrapper #返回修饰函数

使用update_wrapper来修复一个修饰器函数

from functools import update_wrapper

def wrapper(f):
    def wrapper_function(*args, **kwargs):
        """这个是修饰函数"""
        return f(*args, **kwargs)
    update_wrapper(wrapper_function, f)  # << 此处修复
    return wrapper_function
    
@wrapper
def wrapped():
    """这个是被修饰的函数"""
    print('wrapped')


print(wrapped.__doc__)  # 输出`这个是被修饰的函数`
print(wrapped.__name__)  # 输出`wrapped`

functools.wraps

源码:

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)
@wraps
def func():
    pass
# func = wraps(func) = partial(update_wrapper, wrapped=func,assigned=assigned, updated=updated) = 一个绑定好属性的函数

  

posted @ 2018-04-23 22:16  瓜田月夜  阅读(444)  评论(0编辑  收藏  举报