zoukankan      html  css  js  c++  java
  • Python 关于functools模块的wraps装饰器用途

    测试环境:Python3.6.2 + win10 +  Pycharm2017.3


     装饰器之functools模块的wraps的用途:

    首先我们先写一个装饰器

    # 探索functools模块wraps装饰器的用途
    from functools import wraps
    
    
    def trace(func):
        """ 装饰器 """
    
        # @wraps(func)
        def callf(*args, **kwargs):
            """ A wrapper function """
            print("Calling function:{}".format(func.__name__))  # Calling function:foo
            res = func(*args, **kwargs)
            print("Return value:{}".format(res))  # Return value:9
            return res
    
        return callf
    
    
    @trace
    def foo(x):
        """ 返回给定数字的平方 """
        return x * x
    
    
    if __name__ == '__main__':
        print(foo(3))  # 9
        print(foo.__doc__)
        help(foo)
        print(foo.__name__)
        # print(foo.__globals__)
        t = trace(foo)
        print(t)
    打印结果:
    Calling function:foo
    Return value:9
    9
     A wrapper function 
    Help on function callf in module __main__:
    
    callf(*args, **kwargs)
        A wrapper function
    
    callf
    <function trace.<locals>.callf at 0x0000022F744D8730>
    不带wraps的装饰器示例

    上面的装饰器例子等价于:trace(foo(3)),只是在使用装饰器时,我们不用再手动调用装饰器函数;

    如果把这段代码提供给其他人调用, 他可能会想看下foo函数的帮助信息时:

    >>>from xxx import foo
    >>>help(foo)    # print(foo__doc__)
    Help on function callf in module __main__:
    
    callf(*args, **kwargs)
        A wrapper function

    这里,他可能会感到迷惑,继续敲:

    >>> print(foo.__name__)
    callf

    最后, 他可能会看源码,找问题原因,我们知道Python中的对象都是"第一类"的,所以,trace函数会返回一个callf闭包函数,连带callf的上下文环境一并返回,所以,可以解释我们执行help(foo)的到结果了

    那么,我们如果才能得到我们想要的foo的帮助信息呢,这里就要用到了functools的wraps了。

    # 探索functools模块wraps装饰器的用途
    from functools import wraps
    
    
    def trace(func):
        """ 装饰器 """
    
        @wraps(func)
        def callf(*args, **kwargs):
            """ A wrapper function """
            print("Calling function:{}".format(func.__name__))  # Calling function:foo
            res = func(*args, **kwargs)
            print("Return value:{}".format(res))  # Return value:9
            return res
    
        return callf
    
    
    @trace
    def foo(x):
        """ 返回给定数字的平方 """
        return x * x
    
    
    if __name__ == '__main__':
        print(foo(3))  # 9
        print(foo.__doc__)
        help(foo)
        print(foo.__name__)
        # print(foo.__globals__)
        t = trace(foo)
        print(t)
    增加wraps的装饰器

    至于wraps的原理,通过下面部分源码,可以自行研究,在此就不予展开扩展了

    # 有关wraps的源码,有兴趣的可以自行研究下
    
    
    WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                           '__annotations__')
    WRAPPER_UPDATES = ('__dict__',)
    def update_wrapper(wrapper,
                       wrapped,
                       assigned = WRAPPER_ASSIGNMENTS,
                       updated = WRAPPER_UPDATES):
        """Update a wrapper function to look like the wrapped function
    
           wrapper is the function to be updated
           wrapped is the original function
           assigned is a tuple naming the attributes assigned directly
           from the wrapped function to the wrapper function (defaults to
           functools.WRAPPER_ASSIGNMENTS)
           updated is a tuple naming the attributes of the wrapper that
           are updated with the corresponding attribute from the wrapped
           function (defaults to functools.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
    
    def wraps(wrapped,
              assigned = WRAPPER_ASSIGNMENTS,
              updated = WRAPPER_UPDATES):
        """Decorator factory to apply update_wrapper() to a wrapper function
    
           Returns a decorator that invokes update_wrapper() with the decorated
           function as the wrapper argument and the arguments to wraps() as the
           remaining arguments. Default arguments are as for update_wrapper().
           This is a convenience function to simplify applying partial() to
           update_wrapper().
        """
        return partial(update_wrapper, wrapped=wrapped,
                       assigned=assigned, updated=updated)
    
    
    
    
    
    
    
    
    class partial:
        """New function with partial application of the given arguments
        and keywords.
        """
    
        __slots__ = "func", "args", "keywords", "__dict__", "__weakref__"
    
        def __new__(*args, **keywords):
            if not args:
                raise TypeError("descriptor '__new__' of partial needs an argument")
            if len(args) < 2:
                raise TypeError("type 'partial' takes at least one argument")
            cls, func, *args = args
            if not callable(func):
                raise TypeError("the first argument must be callable")
            args = tuple(args)
    
            if hasattr(func, "func"):
                args = func.args + args
                tmpkw = func.keywords.copy()
                tmpkw.update(keywords)
                keywords = tmpkw
                del tmpkw
                func = func.func
    
            self = super(partial, cls).__new__(cls)
    
            self.func = func
            self.args = args
            self.keywords = keywords
            return self
    
        def __call__(*args, **keywords):
            if not args:
                raise TypeError("descriptor '__call__' of partial needs an argument")
            self, *args = args
            newkeywords = self.keywords.copy()
            newkeywords.update(keywords)
            return self.func(*self.args, *args, **newkeywords)
    
        @recursive_repr()
        def __repr__(self):
            qualname = type(self).__qualname__
            args = [repr(self.func)]
            args.extend(repr(x) for x in self.args)
            args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items())
            if type(self).__module__ == "functools":
                return f"functools.{qualname}({', '.join(args)})"
            return f"{qualname}({', '.join(args)})"
    
        def __reduce__(self):
            return type(self), (self.func,), (self.func, self.args,
                   self.keywords or None, self.__dict__ or None)
    
        def __setstate__(self, state):
            if not isinstance(state, tuple):
                raise TypeError("argument to __setstate__ must be a tuple")
            if len(state) != 4:
                raise TypeError(f"expected 4 items in state, got {len(state)}")
            func, args, kwds, namespace = state
            if (not callable(func) or not isinstance(args, tuple) or
               (kwds is not None and not isinstance(kwds, dict)) or
               (namespace is not None and not isinstance(namespace, dict))):
                raise TypeError("invalid partial state")
    
            args = tuple(args) # just in case it's a subclass
            if kwds is None:
                kwds = {}
            elif type(kwds) is not dict: # XXX does it need to be *exactly* dict?
                kwds = dict(kwds)
            if namespace is None:
                namespace = {}
    
            self.__dict__ = namespace
            self.func = func
            self.args = args
            self.keywords = kwds
    wraps源码示例

    只要是知道,wraps是通过partial和update_wrapper来帮我们实现想要的结果的


    摘自:

      https://www.cnblogs.com/myd7349/p/how_to_use_wraps_of_functools.html    # 关于functools模块的wraps装饰器


    End

  • 相关阅读:
    Fish
    Brackets
    StoneWall【★★★★★】
    Nesting
    ajax补充FormData
    初始Ajax
    extra过滤
    Django 之缓存
    django中的信号
    Form组件归类
  • 原文地址:https://www.cnblogs.com/Neeo/p/8371826.html
Copyright © 2011-2022 走看看