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

为什么有些python代码的包装器和包装函数是相同的。,python,wrapper,python-decorators,Python,Wrapper,Python Decorators,我正在Github(链接)上阅读Ian Goodfello的GAN源代码。具体而言,在第40/41行,代码为: @functools.wraps(Model.get_lr_scalers) def get_lr_scalers(self): 使用wrapps是一种相当陌生的方式,其目标似乎是用用户定义的函数替换get\u lr\u scaler。但是那样的话,我们真的不需要包装,对吗?我真的不知道在这种情况下,包装的目的是什么。默认情况下,将另一个函数中的许多属性复制到这个函数上,\uuuuu

我正在Github(链接)上阅读Ian Goodfello的GAN源代码。具体而言,在第40/41行,代码为:

@functools.wraps(Model.get_lr_scalers)
def get_lr_scalers(self):
使用
wrapps
是一种相当陌生的方式,其目标似乎是用用户定义的函数替换
get\u lr\u scaler
。但是那样的话,我们真的不需要包装,对吗?我真的不知道在这种情况下,
包装的目的是什么。

默认情况下,将另一个函数中的许多属性复制到这个函数上,
\uuuuuuu模块\uuuuuuu
\uu名称\uuuuuu
\uu注释\uuuuuuuuu
\uuuuu文档

最明显最有用的复制是
\uuuuuu doc\uuuuu
。考虑这个简单的例子:1

类库:
def垃圾邮件(自助、早餐):
“”“垃圾邮件(自助,早餐)->添加垃圾邮件的早餐
"""
班级儿童:
@functools.wrapps(Base.spam)
def垃圾邮件(自助、早餐):
newbreakfast=早餐。复制()
新鲜肉食['spam']+30
返回newbreakfast
现在,如果有人想使用
帮助(mychild.spam)
,他们将获得29行有用信息。(或者,如果他们在PyCharm中自动完成
mychild.spam
,它将弹出文档覆盖等等),而无需我手动复制和粘贴。而且,更好的是,如果
Base
来自我没有编写的某个框架,并且我的用户从该框架的1.2.3升级到1.2.4,并且有更好的docstring,他们会看到更好的docstring


在最常见的情况下,
Child
将是
Base
的一个子类,
spam
将是一个覆盖。2但这实际上不是必需的-
wrapps
不关心您是通过继承进行子类型化,还是通过实现隐式协议进行duck类型化;这对两种情况都同样有用。只要
Child
打算从
Base
实现
spam
协议,那么
Child.spam
具有相同的文档字符串(可能还有其他元数据属性)是有意义的


其他属性可能没有docstring那么有用。例如,如果您使用的是类型注释,那么它们在读取代码方面的好处可能至少与能够运行Mypy进行静态类型检查方面的好处一样高,因此仅仅从另一个方法动态复制它们通常并不那么有用。和
\uuuu模块\uuuu
\uuuuu质量名称\uuuuu
主要用于反射/检查,在这种情况下更容易产生误导而不是帮助(虽然您可能会提出一个框架示例,希望人们阅读
Base
中的代码,而不是
Child
中的代码,但对于默认的明显示例,情况并非如此)。但是,除非它们是积极有害的,否则使用
@functools.wrapps(Base.spam,assigned=(“\uu doc\uuuuuu”)的可读性成本会降低
而不仅仅是默认值可能不值得


1.如果您使用的是Python2,请将这些类更改为继承自
对象
;否则它们将是旧式类,这只会以一种不相关的方式使事情复杂化。如果使用Python3,则没有旧式类,因此此问题甚至不会出现


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

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

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

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'."
现在,原来的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'."
>>f1()
替换
>>>f1
>>>f1.\u名称__
“f1”
>>>f1.\u文件__
“名为f1的函数。打印'f1'。”

谢谢你的解释!它真的澄清了我的困惑!一个问题,当你命名
base
类和
child
类时,你是在暗示这两个类之间存在某种关系/继承?还是这两个类可以是完全不相关的类,结论仍然成立?@fnosdy在我的示例中,我使用的是一个子类ss和一个方法重写,但同样的事情适用于两个完全不相关的函数(或者,更确切地说,这两个函数仅以某种方式相关,不能用Python直接表示)但是,考虑一个明显的例子并不是很容易。但是考虑一个类,它是一个垃圾邮件,并希望使用SPOM的DOCSTORD。鸡蛋用于它的鸡蛋方法包会做到这一点(并且可能比编写代码>鸡蛋更清楚。@fnosdy您认为答案需要更清楚,还是需要添加更多的示例?我认为基和子定义对我来说有点混淆。我认为这两个类是相关的,也许基(对象)和子(对象)可以消除混淆?
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'."