functools.wraps 有什么作用?

2024-12-16 08:35:00
admin
原创
195
摘要:问题描述:在对另一个问题的这个答案的评论中,有人说他们不确定functools.wraps在做什么。所以,我问这个问题是为了在 StackOverflow 上留下记录以供将来参考:functools.wraps到底做了什么?解决方案 1:当你使用装饰器时,你正在用另一个函数替换一个函数。换句话说,如果你有一个...

问题描述:

在对另一个问题的这个答案的评论中,有人说他们不确定functools.wraps在做什么。所以,我问这个问题是为了在 StackOverflow 上留下记录以供将来参考:functools.wraps到底做了什么?


解决方案 1:

当你使用装饰器时,你正在用另一个函数替换一个函数。换句话说,如果你有一个装饰器

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'

解决方案 2:

从python 3.5+开始:

@functools.wraps(f)
def g():
    pass

是 的别名g = functools.update_wrapper(g, f)。它的作用有三:

  • 它将 的 、 、 、 和 属性复制到__module____name____qualname____doc__默认__annotations__列表fg中,您可以在functools 源WRAPPER_ASSIGNMENTS中看到它。

  • 它用来自的所有元素更新__dict__的。(参见源代码)g`f.__dict__`WRAPPER_UPDATES

  • 它设置__wrapped__=f了新的属性g

结果是g看起来具有与 相同的名称、文档字符串、模块名称和签名f。唯一的问题是,就签名而言,这实际上并不正确:默认情况下, 只是inspect.signature遵循包装器链。您可以使用 进行检查,如文档inspect.signature(g, follow_wrapped=False)中所述。这会带来令人讨厌的后果:

  • 即使提供的参数无效,包装器代码也会执行。

  • 包装器代码无法使用其名称从收到的 args、*kwargs 轻松访问参数。实际上,必须处理所有情况(位置、关键字、默认),因此必须使用类似 的东西Signature.bind()

现在和装饰器之间有点混淆functools.wraps,因为开发装饰器的一个非常常见的用例是包装函数。但两者都是完全独立的概念。如果您有兴趣了解两者的区别,我为两者都实现了辅助库:decopatch可轻松编写装饰器,makefun可提供签名保留的替代方案@wraps。请注意,makefun与著名的库相比,它依赖于相同的经过验证的技巧decorator

解决方案 3:

  1. 假设我们有这个:简单的装饰器,它接受函数的输出并将其放入字符串中,后跟三个!!!!。

def mydeco(func):
    def wrapper(*args, **kwargs):
        return f'{func(*args, **kwargs)}!!!'
    return wrapper
  1. 现在让我们用“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
  1. 当运行add(10,20)、mysum(1,2,3,4)时,它成功了!

>>> add(10,20)
'30!!!'

>>> mysum(1,2,3,4)
'10!!!!'
  1. 但是,name属性在定义函数时会给我们提供函数的名称,

>>>add.__name__
'wrapper`

>>>mysum.__name__
'wrapper'
  1. 更糟的是

>>> help(add)
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)

>>> help(mysum)
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
  1. 我们可以通过以下方式部分修复:

def mydeco(func):
    def wrapper(*args, **kwargs):
        return f'{func(*args, **kwargs)}!!!'
    wrapper.__name__ = func.__name__
    wrapper.__doc__ = func.__doc__
    return wrapper
  1. 现在我们再次运行步骤 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

  1. 但是我们可以使用 functools.wraps (decotator 工具)

from functools import wraps

def mydeco(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return f'{func(*args, **kwargs)}!!!'
    return wrapper
  1. 现在再次运行步骤 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

参考

解决方案 4:

我经常使用类而不是函数作为装饰器。我遇到了一些麻烦,因为对象不会具有函数所期望的所有相同属性。例如,对象不会具有属性__name__。我遇到了一个特定的问题,很难追踪 Django 报告错误“对象没有属性' __name__'”的位置。不幸的是,对于类样式的装饰器,我不相信 @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)

解决方案 5:

  1. 先决条件:您必须知道如何使用装饰器,特别是包装器。此评论对此解释得比较清楚,或者此链接对此解释得也相当清楚。

  2. 每当我们使用 For 时,例如:@wraps 后跟我们自己的包装函数。根据此链接中提供的详细信息,它表示

functools.wraps 是在定义包装函数时调用 update_wrapper() 作为函数装饰器的便捷函数。

它相当于partial(update_wrapper,wrapped=wrapped,assigned=assigned,updated=updated)。

所以@wraps装饰器实际上调用了functools.partial(func,*args)。

functools.partial() 定义表示

partial() 用于部分函数应用,它“冻结”函数参数和/或关键字的某些部分,从而产生具有简化签名的新对象。例如,partial() 可用于创建一个可调用函数,其行为类似于 int() 函数,其中基本参数默认为两个:

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

这让我得出这样的结论:@wraps 调用 partial() 并将包装函数作为参数传递给它。partial() 最终返回简化版本,即包装函数内部的对象,而不是包装函数本身。

解决方案 6:

这是有关包装的源代码:

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)

解决方案 7:

简单来说,functools.wraps就是一个普通的函数,我们先来看一下官方给出的例子,结合源码我们可以看到更详细的实现,以及运行步骤如下:

  1. wraps(f)返回一个对象,比如O1 。它是Partial 类的一个对象

  2. 下一步是@O1...,这是python中的装饰器符号。这意味着

包装器=O1.__call__(包装器)

检查__call__的实现,我们发现在此步骤之后,(左侧的)包装器将成为self.func(self.args, *args, newkeywords)产生的对象。检查newO1的创建,我们知道self.func是函数update_wrapper。它使用参数args(右侧包装器)作为其第一个参数。检查update_wrapper的最后一步,可以看到返回了右侧包装器,其中某些属性根据需要进行了修改。

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2560  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1552  
  IPD(Integrated Product Development)流程作为一种先进的产品开发管理模式,在众多企业中得到了广泛应用。其中,技术评审与决策评审是IPD流程中至关重要的环节,它们既有明显的区别,又存在紧密的协同关系。深入理解这两者的区别与协同,对于企业有效实施IPD流程,提升产品开发效率与质量具有重要意义...
IPD管理流程   1  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、ClickUp、Freshdesk、GanttPRO、Planview、Smartsheet、Asana、Nifty、HubPlanner、Teamwork。在当今快速变化的商业环境中,项目管理软件已成为企业提升效率、优化资源分配和确保项目按时交付的关键工具。然而...
项目管理系统   2  
  建设工程项目质量关乎社会公众的生命财产安全,也影响着企业的声誉和可持续发展。高质量的建设工程不仅能为使用者提供舒适、安全的环境,还能提升城市形象,推动经济的健康发展。在实际的项目操作中,诸多因素会对工程质量产生影响,从规划设计到施工建设,再到后期的验收维护,每一个环节都至关重要。因此,探寻并运用有效的方法来提升建设工程...
工程项目管理制度   3  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用