为什么在执行func(*iterable)时CPython调用len(iterable)?
最近我正在编写一个下载程序,它使用HTTP范围字段同时下载多个块。我编写了一个Python类来表示范围(HTTP头的范围是一个闭合区间):为什么在执行func(*iterable)时CPython调用len(iterable)?,python,cpython,Python,Cpython,最近我正在编写一个下载程序,它使用HTTP范围字段同时下载多个块。我编写了一个Python类来表示范围(HTTP头的范围是一个闭合区间): \uuuu iter\uuuu神奇的方法是支持元组解包: header = {'Range': 'bytes={}-{}'.format(*the_range)} 而len(u范围)是该范围内的字节数 现在我发现'bytes={}-{}'。format(*the_range)偶尔会导致内存错误。经过一些调试,我发现CPython解释器在执行func(*it
\uuuu iter\uuuu
神奇的方法是支持元组解包:
header = {'Range': 'bytes={}-{}'.format(*the_range)}
而len(u范围)
是该范围内的字节数
现在我发现'bytes={}-{}'。format(*the_range)
偶尔会导致内存错误。经过一些调试,我发现CPython解释器在执行func(*iterable)
时会尝试调用len(iterable)
,并且(可能)会根据长度分配内存。在我的机器上,当len(u范围)
大于1GB时,会出现MemoryError
这是一个简化的例子:
class C:
def __iter__(self):
yield 5
def __len__(self):
print('__len__ called')
return 1024**3
def f(*args):
return args
>>> c = C()
>>> f(*c)
__len__ called
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
MemoryError
>>> # BTW, `list(the_range)` have the same problem.
>>> list(c)
__len__ called
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
MemoryError
C类:
定义(自我):
收益率5
定义(自我):
打印(称为“列印”)
返回1024**3
def f(*args):
返回参数
>>>c=c()
>>>f(*c)
__莱恩打电话来了
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
记忆者
>>>#顺便说一句,`list(the_range)`也有同样的问题。
>>>名单(c)
__莱恩打电话来了
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
记忆者
因此,我的问题是:
为什么CPython调用len(iterable)
?从我看来,只有迭代并抛出迭代器,才能知道迭代器的长度。这是优化吗
\uuuu len\uuuu
方法能否返回对象的“假”长度(即不是内存中元素的真实数量)f(*c)
时,使用操作码调用函数\u EX
:
0 LOAD_GLOBAL 0 (f)
2 LOAD_GLOBAL 1 (c)
4 CALL_FUNCTION_EX 0
6 POP_TOP
由于c
是一个iterable,调用PySequence\u Tuple
将其转换为一个Tuple,然后调用PyObject\u LengthHint
来确定新的Tuple长度,因为\u len\u
方法是在c
上定义的,它被调用,其返回值用于为新的Tuple分配内存,由于malloc
失败,最终引发了MemoryError
错误
/* Guess result size and allocate space. */
n = PyObject_LengthHint(v, 10);
if (n == -1)
goto Fail;
result = PyTuple_New(n);
\uuuu len\uuuu
方法能否返回对象的“假”长度(即不是内存中元素的真实数量)
在这种情况下,是的
当
\uuuu len\uuuu
的返回值小于需要时,python将在填充元组时调整新元组对象的内存空间以适应需要。如果它大于需要,尽管python会分配额外的内存,但最终会调用\PyTuple\u Resize
来回收分配过多的空间。。无论如何,我建议不要这样做(使用一个不产生范围元素的\uuuuuuuuu iter\uuuuuuu
)。如果迭代器产生两个项,那么序列的长度是两个。@juanpa.arrivillaga@Ryan我知道list(it)
或f(*it)
都创建一个序列,并将调用操作符.length\u hint(it)
来预分配空间。和operator.length\u hint
查看it
具有\uu len\uu
方法,因此只返回len(it)
——因此序列分配太大。是这样吗?
/* Guess result size and allocate space. */
n = PyObject_LengthHint(v, 10);
if (n == -1)
goto Fail;
result = PyTuple_New(n);