当 `func = func.__call__` 被执行时,Python 会发生啥?

Posted

技术标签:

【中文标题】当 `func = func.__call__` 被执行时,Python 会发生啥?【英文标题】:What is happens in Python when `func = func.__call__` is executed?当 `func = func.__call__` 被执行时,Python 会发生什么? 【发布时间】:2012-11-22 15:43:55 【问题描述】:

我最近在 Python 中想到了一些事情:x = y(z) 等同于 x = y.__call__(z)。但是,测试似乎使该假设无效,并且还会导致 Python 的解释器崩溃。

Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:55:48) [MSC v.1600 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> def ret(*args):
...     return args
...
>>> ret(1, 2, 3)
(1, 2, 3)
>>> for _ in range(1000000):
...     ret = ret.__call__
...
>>> ret(1, 2, 3)

运行第二个 ret(1, 2, 3) 会导致 Python 崩溃并返回命令提示符 (image)。

    ret = ret.__call__ 行执行时后台发生了什么? 为什么 Python 在最后一行停止工作,是否应该将其报告为错误?

无用参考: Python functions and their __call__ attribute

【问题讨论】:

我无法在 2.7 或 3.2.3 中重现它。 你打错了吗?可能你的意思是ret = ret.__call__(_) @akaRem:那根本行不通。 请定义“崩溃”。我可以使用 python2.7/3.2 和 3.3 在 Windows 上重现(我认为)。无论如何,每次你做ret = ret.__call__ 你实际上是在创建一个新的方法包装实例,当调用最终执行时,解释器必须通过你刚刚创建的所有包装器,所以函数调用甚至需要永远启动(甚至虽然我认为它不应该花几分钟......至少,通常你可以在一分钟内完成数百万或更多的函数调用) 某种确认:segmentation fault,尽管使用不同版本的 python。 3.2 本地相同。 【参考方案1】:

您正在创建方法包装器的深度嵌套结构。每个方法包装器仍然有对self的引用,其中self是对父方法包装器的引用,一直回到原来的函数:

>>> ret, ret.__call__.__self__
(<function ret at 0x10f17a050>, <function ret at 0x10f17a050>)
>>> ret.__call__, ret.__call__.__call__.__self__
(<method-wrapper '__call__' of function object at 0x10f17a050>, <method-wrapper '__call__' of function object at 0x10f17a050>)

注意方法包装器__self__属性的内存地址如何指向父对象。

如果您创建足够多的这些包装器,您可能会耗尽内存。

Python 为绑定到实例的所有函数创建这样的包装器。带有方法的自定义类也是如此:

>>> class Foo: 
...     def bar(self): return
... 
>>> Foo().bar
<bound method Foo.bar of <__main__.Foo object at 0x10f1798d0>>
>>> Foo().bar, Foo().bar.__self__
(<bound method Foo.bar of <__main__.Foo object at 0x10f179710>>, <__main__.Foo object at 0x10f179850>)

在通过属性访问访问方法时,根据需要从函数创建方法。因为它们持有对self 的引用,所以只要您持有对它们的引用,它们就会保留在内存中。因此,您的引用链拥有 100000 个内存包装器。

【讨论】:

谢谢!我在问题中添加了一张图片,以显示计算机告诉我的内容。我很困惑的是为什么 Python 不生成 RuntimeError 而只是退出。

以上是关于当 `func = func.__call__` 被执行时,Python 会发生啥?的主要内容,如果未能解决你的问题,请参考以下文章

__call PHP伪重载方法

python用类装饰函数的一个有趣实现

python-装饰器,类与对象,私有字段,析构,__call__,继承,多继承,接口

用类作为装饰器装饰函数!

使用__call__(self,[..)方法将类变成装饰器

如何在 8051 中使用 __func__