为什么包装器和包装函数对于某些 python 代码是相同的。
Why wrapper and wrapped function are the same for some python codes.
我正在 Github (link https://github.com/goodfeli/adversarial/blob/master/deconv.py) 阅读 Ian Goodfellow 的 GAN 源代码。特别是,在第 40/41 行,代码是:
@functools.wraps(Model.get_lr_scalers)
def get_lr_scalers(self):
这是一种相当陌生的使用 wraps
的方式,目标似乎是用用户定义的函数替换 get_lr_scalers
。但在那种情况下,我们真的不需要包装器,对吧?在这种情况下,我真的不知道 wraps
的目的。
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__
主要用于 reflection/inspection,在这种情况下更有可能产生误导而不是帮助(尽管您可能会想出一个框架示例,其中您d 希望人们阅读 Base
中的代码而不是 Child
中的代码,这对于默认的明显示例来说是不正确的)。但是,除非它们是有害的,否则使用 @functools.wraps(Base.spam, assigned=('__doc__',))
而不是默认值的可读性成本可能不值得。
1.如果您使用的是 Python 2,请将这些 类 更改为继承自 object
;否则它们将是旧式 类,这只会以无关紧要的方式使事情复杂化。如果Python3,没有老式的类,就不会出现这个问题
2。或者可能是 ABC 的 "virtual subclass",通过 register
调用或通过子类挂钩声明。
@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'."
我正在 Github (link https://github.com/goodfeli/adversarial/blob/master/deconv.py) 阅读 Ian Goodfellow 的 GAN 源代码。特别是,在第 40/41 行,代码是:
@functools.wraps(Model.get_lr_scalers)
def get_lr_scalers(self):
这是一种相当陌生的使用 wraps
的方式,目标似乎是用用户定义的函数替换 get_lr_scalers
。但在那种情况下,我们真的不需要包装器,对吧?在这种情况下,我真的不知道 wraps
的目的。
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__
主要用于 reflection/inspection,在这种情况下更有可能产生误导而不是帮助(尽管您可能会想出一个框架示例,其中您d 希望人们阅读 Base
中的代码而不是 Child
中的代码,这对于默认的明显示例来说是不正确的)。但是,除非它们是有害的,否则使用 @functools.wraps(Base.spam, assigned=('__doc__',))
而不是默认值的可读性成本可能不值得。
1.如果您使用的是 Python 2,请将这些 类 更改为继承自 object
;否则它们将是旧式 类,这只会以无关紧要的方式使事情复杂化。如果Python3,没有老式的类,就不会出现这个问题
2。或者可能是 ABC 的 "virtual subclass",通过 register
调用或通过子类挂钩声明。
@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'."