Python中多个重复lambda函数的内存开销是多少?
我想知道Python解释器实际上是如何处理内存中的lambda函数的 如果我有以下资料:Python中多个重复lambda函数的内存开销是多少?,python,Python,我想知道Python解释器实际上是如何处理内存中的lambda函数的 如果我有以下资料: def squares(): return [(lambda x: x**2)(num) for num in range(1000)] 这是否会在内存中创建1000个lambda函数实例,或者Python是否足够聪明,知道这1000个lambda中的每一个都是相同的,从而将它们作为一个函数存储在内存中;DR:在您的示例中,lambda对象的内存开销是一个lambda的大小,但仅当squares(
def squares():
return [(lambda x: x**2)(num) for num in range(1000)]
这是否会在内存中创建1000个lambda函数实例,或者Python是否足够聪明,知道这1000个lambda中的每一个都是相同的,从而将它们作为一个函数存储在内存中;DR:在您的示例中,lambda对象的内存开销是一个lambda的大小,但仅当
squares()
函数运行时,即使您持有对其返回值的引用,因为返回的列表不包含lambda对象
但是,即使您保留了从同一lambda表达式(或def语句)创建的多个函数实例,它们也共享同一个代码对象,因此每个额外实例的内存成本都小于第一个实例的成本
以你为例,
[(lambda x: x**2)(num) for num in range(1000)]
您只在列表中存储lambda调用的结果,而不是lambda本身,因此lambda对象的内存将被释放
lambda对象何时被垃圾回收取决于您的Python实现。CPython应该能够立即执行此操作,因为每个循环的引用计数都会降至0:
>>> class PrintsOnDel:
... def __del__(self):
... print('del') # We can see when this gets collected.
...
>>> [[PrintsOnDel(), print(x)][-1] for x in [1, 2, 3]] # Freed each loop.
1
del
2
del
3
del
[None, None, None]
PyPy是另一个故事
>>>> from __future__ import print_function
>>>> class PrintsOnDel:
.... def __del__(self):
.... print('del')
....
>>>> [[PrintsOnDel(), print(x)][-1] for x in [1, 2, 3]]
1
2
3
[None, None, None]
>>>> import gc
>>>> gc.collect() # Not freed until the gc actually runs!
del
del
del
0
随着时间的推移,它将创建1000个不同的lambda实例,但它们不会同时都在内存中(在CPython中),而且它们都指向同一个代码对象,因此一个函数有多个实例并不像听起来那么糟糕:
>>> a, b = [lambda x: x**2 for x in [1, 2]]
>>> a is b # Different lambda objects...
False
>>> a.__code__ is b.__code__ # ...point to the same code object.
True
分解字节码可以帮助您准确理解解释器在做什么:
>>> from dis import dis
>>> dis("[(lambda x: x**2)(num) for num in range(1000)]")
1 0 LOAD_CONST 0 (<code object <listcomp> at 0x000001D11D066870, file "<dis>", line 1>)
2 LOAD_CONST 1 ('<listcomp>')
4 MAKE_FUNCTION 0
6 LOAD_NAME 0 (range)
8 LOAD_CONST 2 (1000)
10 CALL_FUNCTION 1
12 GET_ITER
14 CALL_FUNCTION 1
16 RETURN_VALUE
Disassembly of <code object <listcomp> at 0x000001D11D066870, file "<dis>", line 1>:
1 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 16 (to 22)
6 STORE_FAST 1 (num)
8 LOAD_CONST 0 (<code object <lambda> at 0x000001D11D0667C0, file "<dis>", line 1>)
10 LOAD_CONST 1 ('<listcomp>.<lambda>')
12 MAKE_FUNCTION 0
14 LOAD_FAST 1 (num)
16 CALL_FUNCTION 1
18 LIST_APPEND 2
20 JUMP_ABSOLUTE 4
>> 22 RETURN_VALUE
Disassembly of <code object <lambda> at 0x000001D11D0667C0, file "<dis>", line 1>:
1 0 LOAD_FAST 0 (x)
2 LOAD_CONST 1 (2)
4 BINARY_POWER
6 RETURN_VALUE
因此,它会重新使用代码对象。您的代码创建一个lambda函数并调用它1000次,而不会在每次迭代时创建一个新对象。因为在每次迭代中,存储的是lambda函数的结果,而不是函数本身。这相当于:
def square(x): return x*x # or def square x = lambda x: x*x
[square(x) for x in range(1000)]
通过这种方式,我们将为每次迭代创建一个lambda函数对象。请参见此虚拟示例:
[lambda x: x*x for _ in range(3)]
给出:
[<function <listcomp>.<lambda> at 0x294358950>,
<function <listcomp>.<lambda> at 0x294358c80>,
<function <listcomp>.<lambda> at 0x294358378>]
[,,
,
]
lambda的内存地址都不同。然后为每个lambda创建一个不同的对象。所有这些函数对象都会立即消失。示例函数返回一个列表,而不是生成器。这似乎是一个没有人会写的人为的例子。这就是你想问的吗?没有人会从函数返回一个列表?@AlexWallish:Noone会故意在一个列表中返回完全相同的结果1000次(这迫使内容完全扩展;不像我说的生成器)。(他们为什么要这么做?)告诉我们更多关于上下文的信息。我试图理解这是否是一个真正的问题。这显然是一个例子。正如@gilch所说,重点显然不是制作一个方块列表,而是询问python如何处理内存中的lambda函数。
[<function <listcomp>.<lambda> at 0x294358950>,
<function <listcomp>.<lambda> at 0x294358c80>,
<function <listcomp>.<lambda> at 0x294358378>]