为什么包装器和包装函数对于某些python代码是相同的。

时间:2018-05-03 19:48:53

标签: python wrapper python-decorators

我正在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的目的。

2 个答案:

答案 0 :(得分:1)

wraps将来自其他函数的多个属性复制到此函数上 - 默认情况下,__module____name____qualname____annotations__和{{1} }}

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

__doc__

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

在最常见的情况下,Base将是Child的子类,而Base将是覆盖。 2 但实际上并非如此required - spam并不关心你是通过继承进行子类型化,还是仅通过实现隐式协议来打字;它对两种情况同样有用。只要wraps旨在实现Child中的spam协议,Base就可以拥有相同的文档字符串(以及其他元数据属性)。

其他属性可能不如docstrings有用。例如,如果您正在使用类型注释,那么它们在读取代码时的好处可能至少与它们能够运行Mypy进行静态类型检查一样高,因此只需从其他方法动态复制它们通常不是一切都很有用。 Child.spam__module__主要用于反思/检查,在这种情况下更有可能产生误导而不是有用(尽管您可能想出一个您想要的框架示例人们要阅读__qualname__中的代码而不是Base中的代码,但对于默认的明显示例则不然。但是,除非它们是有害的,否则使用Child而不仅仅是默认值的可读性成本可能不值得。

<子> 1。如果您使用的是Python 2,请将这些类更改为继承自@functools.wraps(Base.spam, assigned=('__doc__',));否则他们将成为旧式的课程,这会使事情变得无关紧要。如果是Python 3,那么就没有旧式的类,所以这个问题甚至都不会出现。

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

答案 1 :(得分:0)

@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'."