TL;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')
...
>>> [[PrintsOnDel(), print(x)][-1] for x in [1, 2, 3]]
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
False
>>> a.__code__ is b.__code__
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
请注意每个循环中的
12 MAKE_FUNCTION
instruction。它确实每次都会创建一个新的lambda实例。CPython的虚拟机是一个堆栈机器。参数由其他指令推入堆栈,然后由需要它们的后续指令消耗。请注意上面的
MAKE_FUNCTION
指令还推入了一个参数。
LOAD_CONST 0 (<code object <lambda>...
它重新使用了代码对象。