zoukankan      html  css  js  c++  java
  • python装饰器

    装饰器入门

    以显示时间为例子:定义一个显示当前时间的函数:

    from datetime import datetime
    def now():
         print(datetime.utcnow())
    

    当调用now()的时候,会显示出当前时间

    我们想在显示时间之前(或之后),添加一点功能,但是我们又不想更改函数和整体结构。此时,我们就需要装饰器来实现这个功能
    装饰器的作用在于 在代码运行期间动态增加功能(在执行某个函数之前或之后动态的增加功能)
    现在,我们想在显示时间之前显示某些东西(这里我显示调用的函数名)

    import functools
    def log(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('call %s()' % fun.__name__)
            return fun(*args, **kw)
        return wrapper
    
    @log
    def now():
        print(datetime.utcnow())
    

    此时我们调用now()函数,将返回:

    call now()
    当前时间
    

    把@log 放到now()的定义处,相当于执行了 now = log(now)

    如果装饰器本身需要传入参数,我们需要增加一个函数:

    import functools
    def log(text):
         def decorator(func):
              @functools.wraps(func)
              def wrapper(*args, **kw):
                   print('The function is %s, %s' % (func.__name__, text), end=' ')
                   return func(*args, **kw)
              return wrapper
         return decorator
    
    @log(‘The time is ’)
    def now():
        print(datetime.utcnow())
    

    此时我们调用now()函数,将返回:

    The function is now, the time is 当前时间
    

    三层嵌套相当于执行了now = log(text)(now)
    首先执行log(text),返回一个decorator函数,然后执行decorator(now),返回一个wrapper函数
    我们可以尝试输入now.__name__来查看现在now的函数,结果是‘wrapper’
    在带参数的装饰器中,如果未加入@functools.wraps(func) 则用now.__name__查看,结果是‘wrapper’, 本例中加入了wraps,所以使用now.__name__查看, 结果是‘now’

    functools.wraps(func)源码分析

    在运行以上程序的时候,对装饰器设置断点,逐步查询(以没有参数的装饰器为例)
    当运行到functools.wraps(func)时,会进入functools.py的wraps函数:

    def wraps(wrapped,
              assigned = WRAPPER_ASSIGNMENTS,
              updated = WRAPPER_UPDATES):
    
        return partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)
    

    参数wrapped是传入的func参数,其他两个参数如下:

    WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')
    WRAPPER_UPDATES = ('__dict__',)
    

    wraps函数会返回一个partial函数。
    partial函数的作用是把wrapped、assigned、updated这三个参数传给update_wrapper函数,然后返回一个新函数
    partial是偏函数,当传入的参数为kw时,其作用是把一个函数的某些参数给固定住(即设置默认值),返回一个新函数;当传入的参数为*args时,会把该参数自动加入到原函数中,返回一个新函数
    在wraps源码中,可以发现,使用的是传入
    kw参数
    在partial函数中,我们可以看到一个update_wrapper函数和三个参数

    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
    
    try:
        from _functools import partial
    except ImportError:
        pass
    

    根据partical函数的源码来看,里面提供的newfunc函数的作用就是生成一个新函数
    接下来看函数update_wrapper

    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, {}))
        # Issue #17482: set __wrapped__ last so we don't inadvertently copy it
        # from the wrapped function when updating __dict__
        wrapper.__wrapped__ = wrapped
        # Return the wrapper so this can be used as a decorator via partial()
        return wrapper
    

    在函数update_wrapper中,wrapper表示@functools.wraps(func)后面紧跟着定义的函数,wrapped表示函数wraps传入的参数func(也就是要修饰的目标函数)
    该update_wrapper函数会把wrapped中的assigned复制给wrapper,并更新wrapper中的__dict__,返回wrapper函数
    update_wrapper函数是functools.wraps主要功能提供者,负责拷贝原函数的属性(assigned和updated)

    总结一下:

    functools.wraps(func)是装饰器的装饰器,主要作用是用来包装函数wrapper,通过拷贝原函数func的属性给wrapper的方式,使被包装的函数wrapper更像原函数func
    @functools.wraps(func) = 把update_wrapper函数与func以及其他参数绑定,生成一个新函数newfunc;然后执行newfunc函数(实际运行进行了参数固定的update_wrapper函数),把func更新到wrapper中,使得外部可以直接调用wrapper(wrapper在语句@functools.wraps(func)定义),从而达到动态地增加函数的功能的作用

    functools.wraps(func)是装饰器的装饰器:

    原函数 @functools.wraps(func)
    def wrapper():
    ...
    func()
    ...

    等价形式 wrapper = functools.wraps(func)(wrapper) #类似于前面讲的带参数的装饰器

    等价形式 wrapper = partial(update_wrapper, wrapped=func, assigned=assigned, updated=updated)(wrapper)

    进一步等价 wrapper = update_wrapper(wrapper=wrapper, wrapped=func, assigned=assigned, updated=updated)

  • 相关阅读:
    托管代码和非托管代码效率的对比
    托管程序与非托管程序的区别
    第15章 C# ADO.NET数据库操作
    第14章 C#进程与线程
    第13章 C#异常与调试
    第12章 C# WinForm
    第11章 C#委托和事件
    第10章 C#文件操作
    第9章 C#泛型
    第8章 C#集合
  • 原文地址:https://www.cnblogs.com/eric-nirnava/p/decorator.html
Copyright © 2011-2022 走看看