測試環境:Python3.6.2 + win10 + Pycharm2017.3html
裝飾器之functools模塊的wraps的用途:python
首先咱們先寫一個裝飾器閉包
# 探索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>
上面的裝飾器例子等價於:trace(foo(3)),只是在使用裝飾器時,咱們不用再手動調用裝飾器函數;app
若是把這段代碼提供給其餘人調用, 他可能會想看下foo函數的幫助信息時:ide
>>>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了。this
# 探索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的原理,經過下面部分源碼,能夠自行研究,在此就不予展開擴展了spa
# 有關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是經過partial和update_wrapper來幫咱們實現想要的結果的code
摘自:
https://www.cnblogs.com/myd7349/p/how_to_use_wraps_of_functools.html # 關於functools模塊的wraps裝飾器
End