【问题标题】:Why wrapper and wrapped function are the same for some python codes.为什么某些python代码的包装器和包装函数是相同的。
【发布时间】:2018-10-14 05:28:48
【问题描述】:

我在 Github 上阅读了 Ian Goodfellow 的 GAN 源代码(链接 https://github.com/goodfeli/adversarial/blob/master/deconv.py)。特别是在第 40/41 行,代码是:

@functools.wraps(Model.get_lr_scalers)
def get_lr_scalers(self):

这是使用wraps 的一种相当陌生的方式,似乎目标是用用户定义的函数替换get_lr_scalers。但在那种情况下,我们真的不需要包装器,对吧?在这种情况下,我真的不知道wraps 的用途。

【问题讨论】:

    标签: python wrapper python-decorators


    【解决方案1】:

    wraps 将许多属性从另一个函数复制到此函数上 - 默认情况下,__module____name____qualname____annotations____doc__

    最明显有用的复制是__doc__。考虑这个更简单的例子:1

    class Base:
        def spam(self, breakfast):
            """spam(self, breakfast) -> breakfast with added spam
    
            <29 lines of detailed information here>
            """
    
    class Child:
        @functools.wraps(Base.spam)
        def spam(self, breakfast):
            newbreakfast = breakfast.copy()
            newbreakfast.meats['spam'] + 30
            return newbreakfast
    

    现在如果有人想使用help(mychild.spam),他们将获得 29 行有用的信息。 (或者,如果他们在 PyCharm 中自动完成 mychild.spam,它会弹出带有文档等的叠加层。)所有这些都无需我手动复制和粘贴。而且,更好的是,如果Base 来自某个我没有编写的框架,并且我的用户从该框架的 1.2.3 升级到 1.2.4,并且有更好的文档字符串,他们会看到更好的文档字符串。


    在最常见的情况下,Child 将是 Base 的子类,spam 将是覆盖。2 但实际上这不是必需的 - wraps 不需要'不在乎您是通过继承进行子类型化,还是通过实现隐式协议来进行子类型化;它对这两种情况同样有用。只要Child 旨在实现Base 中的spam 协议,Child.spam 就可以具有相同的文档字符串(可能还有其他元数据属性)。


    其他属性可能不如文档字符串有用。例如,如果您使用类型注释,它们在阅读代码方面的好处可能至少与它们能够运行 Mypy 进行静态类型检查的好处一样高,因此通常不会从另一个方法动态复制它们所有这些有用的。 __module____qualname__ 主要用于反射/检查,在这种情况下更容易产生误导而不是帮助(尽管您可能会想出一个框架示例,希望人们阅读Base 中的代码,而不是 Child 中的代码,这对于默认的明显示例不正确)。但是,除非它们是有害的,否则使用 @functools.wraps(Base.spam, assigned=('__doc__',)) 而不仅仅是默认值的可读性成本可能不值得。


    1。如果您使用的是 Python 2,请将这些类更改为从 object 继承;否则它们将是老式的类,只会以不相关的方式使事情复杂化。如果是 Python 3,没有老式的类,所以这个问题甚至不会出现。

    2。或者可能是 ABC 的“虚拟子类”,通过 register 调用或子类挂钩声明。

    【讨论】:

    • 感谢您的解释!真的解开了我的疑惑!
    • 一个问题,当您命名base 类和child 类时,您是否暗示这两个类之间存在某种关系/继承?或者两者可以是完全不相关的类,结论仍然成立?
    • @fnosdy 在我的示例中,我使用了一个子类和一个方法覆盖——但同样的事情适用于两个完全不相关的函数(或者,更确切地说,它们仅以某种方式相关不能直接用 Python 表达),只是想出一个明显的例子并不容易。但是考虑一个将类型作为垃圾邮件的类,并希望使用 Spam.eggs 中的文档字符串作为它的鸡蛋方法 - 包装会做到这一点(可能比写 eggs.__doc__ = Spam.eggs.__doc__ 更清楚)。
    • @fnosdy 你认为答案需要更清楚,还是添加更多示例?
    • 我认为基本和子定义对我来说有点混乱。我认为这两个类是相关的,也许 base(object) 和 child(object) 会消除混乱?
    【解决方案2】:

    @wraps 的目的是将一个函数的元信息复制到另一个函数。这通常在通过包装替换原始函数时完成,这通常由装饰器完成。

    但在一般情况下,以下是它在示例中的作用:

    def f1():
        """Function named f1. Prints 'f1'."""
        print('f1')
    
    @functools.wraps(f1)
    def f2():
        print('f2')
    

    现在,您可以测试发生了什么:

    >>> f1
    <function f1 at 0x006AD8E8>
    >>> f2
    <function f1 at 0x006AD978>
    >>> f1()
    f1
    >>> f2()
    f2
    >>> f1.__doc__
    "Function named f1. Prints 'f1'."
    >>> f2.__doc__
    "Function named f1. Prints 'f1'."
    

    当您调用f2 时,很明显它实际上是f2,但是当您检查它时,它的行为就像f1 - 它具有相同的文档字符串和相同的名称。

    这有什么用?为此:

    f1 = f2
    

    现在原来的 f1 被新功能取代了,但从外面看还是f1

    通常在装饰器中完成:

    def replace(func):
        @functools.wraps(func)
        def replacement():
            print('replacement')
        return replacement
    
    @replace
    def f1():
        """Function named f1. Prints 'f1'."""
        print('f1')
    

    它的行为是这样的:

    >>> f1()
    replacement
    >>> f1
    <function f1 at 0x006AD930>
    >>> f1.__name__
    'f1'
    >>> f1.__doc__
    "Function named f1. Prints 'f1'."
    

    【讨论】:

      猜你喜欢
      • 2018-10-12
      • 1970-01-01
      • 2021-10-05
      • 1970-01-01
      • 1970-01-01
      • 2018-11-20
      • 1970-01-01
      • 2018-03-07
      • 1970-01-01
      相关资源
      最近更新 更多