Python functools.wrapps做什么?
在对此的评论中,有人说他们不确定Python functools.wrapps做什么?,python,decorator,functools,Python,Decorator,Functools,在对此的评论中,有人说他们不确定functools.wrapps在做什么。所以,我问这个问题是为了在StackOverflow上有一个关于它的记录以供将来参考:functools.wrapps到底做了什么?当你使用装饰器时,你是在用一个函数替换另一个函数。换句话说,如果你有一个装饰师 def logged(func): def with_logging(*args, **kwargs): print(func.__name__ + " was called")
functools.wrapps
在做什么。所以,我问这个问题是为了在StackOverflow上有一个关于它的记录以供将来参考:functools.wrapps
到底做了什么?当你使用装饰器时,你是在用一个函数替换另一个函数。换句话说,如果你有一个装饰师
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
那么当你说
@logged
def f(x):
"""does some math"""
return x + x * x
这和说
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
您的函数f
将替换为带有日志记录的函数。不幸的是,这意味着如果你说
print(f.__name__)
它将打印带有日志记录的,因为这是新函数的名称。事实上,如果您查看f
的docstring,它将是空的,因为带有日志记录的没有docstring,因此您编写的docstring将不再存在。另外,如果您查看该函数的pydoc结果,它不会被列为只接受一个参数x
;相反,它将被列为使用*args
和**kwargs
,因为这是使用_日志记录所需要的
如果使用decorator总是意味着丢失有关函数的信息,那么这将是一个严重的问题。这就是为什么我们有functools.wrapps
。这将采用装饰器中使用的函数,并添加在函数名、docstring、参数列表等上进行复制的功能。由于wrapps
本身就是装饰器,因此以下代码执行正确的操作:
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
print(f.__name__) # prints 'f'
print(f.__doc__) # prints 'does some math'
我经常使用类,而不是函数作为装饰器。我在这方面遇到了一些问题,因为一个对象不会具有函数所期望的所有相同属性。例如,对象将不具有属性\uuuu name\uuuu
。我对此有一个特别的问题,很难追踪Django报告错误“对象没有属性”\uuuuuu name\uuuuuuu
”的位置。不幸的是,对于类风格的装饰者来说,我不相信@wrap可以完成这项工作。我创建了一个基本装饰器类,如下所示:
class DecBase(object):
func = None
def __init__(self, func):
self.__func = func
def __getattribute__(self, name):
if name == "func":
return super(DecBase, self).__getattribute__(name)
return self.func.__getattribute__(name)
def __setattr__(self, name, value):
if name == "func":
return super(DecBase, self).__setattr__(name, value)
return self.func.__setattr__(name, value)
此类将所有属性调用代理给正在修饰的函数。因此,您现在可以创建一个简单的装饰器来检查是否指定了2个参数,如下所示:
class process_login(DecBase):
def __call__(self, *args):
if len(args) != 2:
raise Exception("You can only specify two arguments")
return self.func(*args)
先决条件:您必须知道如何使用装饰器,特别是包装。这解释得有点清楚,或者这也解释得很好
无论何时我们使用例如:@wrapps,后跟我们自己的包装函数。根据本文给出的细节,它说
functools.wrapps是一个方便的函数,用于在定义包装函数时调用update_wrapper()作为函数装饰器
它相当于部分(update_wrapper,wrapped=wrapped,assigned=assigned,updated=updated)
所以@wraps decorator实际上调用了functools.partial(func[,*args][,**关键字])
functtools.partial()的定义是
partial()用于partial函数应用程序,它“冻结”函数参数和/或关键字的某些部分,从而生成具有简化签名的新对象。例如,partial()可用于创建一个可调用函数,其行为类似于int()函数,其中基参数默认为两个:
这让我得出结论,@wrapps调用partial(),并将包装函数作为参数传递给它。最后的partial()返回简化版本,即包装函数内部的对象,而不是包装函数本身。简而言之,functools.wrapps只是一个常规函数。让我们考虑一下。在的帮助下,我们可以看到有关实现和运行步骤的更多详细信息,如下所示:
包装(f)返回一个对象,例如O1。这是本书的一个主题
下一步是@O1…,这是python中的装饰符号。这意味着
wrapper=O1.\uuuu调用(wrapper)
检查的实现,我们看到在这一步之后,(左侧)包装器成为self.func(*self.args,*args,**newkeywords)检查\uu new\uuu>中O1的创建所导致的对象,我们知道self.func是函数update\u wrapper。它使用参数*args,即包装的右侧作为其第一个参数。检查update_wrapper的最后一步,可以看到右侧的wrapper被返回,并根据需要修改一些属性 这是关于包装的源代码:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
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:
setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# 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)
从python 3.5+开始:
@functools.wraps(f)
def g():
pass
是g=functools.update\u wrapper(g,f)
的别名。它有三个功能:
- 它复制了
g
上f
的\uuuu模块
、\uuuu名称
、\uuuu文档
和\uu注释
属性。此默认列表位于WRAPPER\u ASSIGNMENTS
中,您可以在中看到它
- 它使用
f.\uuuu dict\uu
中的所有元素更新的\uuu dict\uuu
。(请参阅源代码中的WRAPPER\u UPDATES
)
- 它在
g
结果是g
显示为与f
具有相同的名称、docstring、模块名称和签名。唯一的问题是,关于签名,这实际上不是真的:只是默认情况下,inspect.signature
遵循包装器链。您可以使用inspect.signature(g,follow\u wrapped=False)
进行检查,如中所述。这会产生恼人的后果:
- 即使提供的参数无效,包装器代码也将执行
- 包装器代码无法使用其na轻松访问参数
@functools.wraps(f)
def g():
pass