在对另一个问题的回答的评论中,有人说他们不确定functools是什么。Wraps在做什么。所以,我问这个问题是为了在StackOverflow上有一个记录,以供将来参考:functools。包裹真的有用吗?
当前回答
I very often use classes, rather than functions, for my decorators. I was having some trouble with this because an object won't have all the same attributes that are expected of a function. For example, an object won't have the attribute __name__. I had a specific issue with this that was pretty hard to trace where Django was reporting the error "object has no attribute '__name__'". Unfortunately, for class-style decorators, I don't believe that @wrap will do the job. I have instead created a base decorator class like so:
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)
其他回答
假设我们有这个:Simple Decorator,它接受一个函数的输出并将其放入一个字符串中,后面跟着三个!!!!。
def mydeco(func):
def wrapper(*args, **kwargs):
return f'{func(*args, **kwargs)}!!!'
return wrapper
现在让我们用“mydeco”装饰两个不同的函数:
@mydeco
def add(a, b):
'''Add two objects together, the long way'''
return a + b
@mydeco
def mysum(*args):
'''Sum any numbers together, the long way'''
total = 0
for one_item in args:
total += one_item
return total
当运行add(10,20), mysum(1,2,3,4),它工作!
>>> add(10,20)
'30!!!'
>>> mysum(1,2,3,4)
'10!!!!'
然而,name属性在定义函数时给出了它的名称,
>>>add.__name__
'wrapper`
>>>mysum.__name__
'wrapper'
更糟糕的是
>>> help(add)
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
>>> help(mysum)
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
我们可以通过以下方法进行部分修复:
def mydeco(func):
def wrapper(*args, **kwargs):
return f'{func(*args, **kwargs)}!!!'
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
现在我们再次运行第5步(第二次):
>>> help(add)
Help on function add in module __main__:
add(*args, **kwargs)
Add two objects together, the long way
>>> help(mysum)
Help on function mysum in module __main__:
mysum(*args, **kwargs)
Sum any numbers together, the long way
但是我们可以使用functools。包装(蒸煮工具)
from functools import wraps
def mydeco(func):
@wraps(func)
def wrapper(*args, **kwargs):
return f'{func(*args, **kwargs)}!!!'
return wrapper
现在再次运行第5步(第三次)
>>> help(add)
Help on function add in module main:
add(a, b)
Add two objects together, the long way
>>> help(mysum)
Help on function mysum in module main:
mysum(*args)
Sum any numbers together, the long way
参考
这是关于包装的源代码:
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)
前提条件:你必须知道如何使用装饰,特别是包装。这个评论解释得很清楚,或者这个链接也解释得很好。 当我们使用For eg: @ wrapper函数时。根据这个链接中给出的细节,它说
functools。Wraps是一个方便的函数,用于在定义包装器函数时调用update_wrapper()作为函数装饰器。 它等价于partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)。
@wraps decorator实际上给了functools一个调用。部分(func [* args][, * *关键字])。
functools.partial()定义是这样说的
partial()用于局部函数应用程序,它“冻结”函数的部分参数和/或关键字,从而生成具有简化签名的新对象。例如,partial()可以用来创建一个行为类似int()函数的可调用对象,其中base参数默认为2:
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
这使我得出结论,@wraps调用partial(),并将包装器函数作为参数传递给它。partial()最后返回简化版本,即包装器函数内部的对象,而不是包装器函数本身。
I very often use classes, rather than functions, for my decorators. I was having some trouble with this because an object won't have all the same attributes that are expected of a function. For example, an object won't have the attribute __name__. I had a specific issue with this that was pretty hard to trace where Django was reporting the error "object has no attribute '__name__'". Unfortunately, for class-style decorators, I don't believe that @wrap will do the job. I have instead created a base decorator class like so:
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)
当您使用装饰器时,您是在用另一个函数替换一个函数。换句话说,如果你有一个装饰器
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被替换为with_logging函数。不幸的是,这意味着如果你接着说
print(f.__name__)
它将打印with_logging,因为这是新函数的名称。事实上,如果您查看f的文档字符串,它将是空白的,因为with_logging没有文档字符串,因此您所编写的文档字符串将不再存在。另外,如果你看一下该函数的pydoc结果,它不会被列为一个参数x;相反,它将被列为接受*args和**kwargs,因为这是with_logging所接受的。
如果使用装饰器总是意味着丢失关于函数的信息,这将是一个严重的问题。这就是为什么我们有functools。wraps。它接受装饰器中使用的函数,并添加了复制函数名、文档字符串、参数列表等功能。由于wraps本身是一个装饰器,下面的代码做了正确的事情:
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'
推荐文章
- 如何在Python中进行热编码?
- 如何嵌入HTML到IPython输出?
- 在Python生成器上使用“send”函数的目的是什么?
- 是否可以将已编译的.pyc文件反编译为.py文件?
- Django模型表单对象的自动创建日期
- 在Python中包装长行
- 如何计算两个时间串之间的时间间隔
- 我如何才能找到一个Python函数的参数的数量?
- 您可以使用生成器函数来做什么?
- 将Python诗歌与Docker集成
- 提取和保存视频帧
- 使用请求包时出现SSL InsecurePlatform错误
- 如何检索Pandas数据帧中的列数?
- except:和except的区别:
- 错误:“字典更新序列元素#0的长度为1;2是必需的”