gpt4 book ai didi

python - Python 的迭代器解包(star unpacking)是如何实现的(或者说,解包自定义迭代器涉及哪些神奇的方法?)

转载 作者:行者123 更新时间:2023-12-04 04:30:25 25 4
gpt4 key购买 nike

我正在编写一个定义 __iter____len__ 的类,其中 __len__ 的值取决于 __iter__ 返回的迭代器。我得到一个有趣的 RecursionError
语言版本:Python 3.8.6、3.7.6。 示例仅用于说明错误。
在以下示例中, Iter.__len__() 尝试解包 self ,将结果存储在 list 中,然后尝试调用该列表中的内置 list.__len__() 以获取长度。

>>> class Iter:
... def __iter__(self):
... return range(5).__iter__()
... def __len__(self):
... return list.__len__([*self])
...
>>> len(Iter())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in __len__
File "<stdin>", line 5, in __len__
File "<stdin>", line 5, in __len__
[Previous line repeated 993 more times]
File "<stdin>", line 3, in __iter__
RecursionError: maximum recursion depth exceeded in comparison
但是,如果我将类 Iter 定义如下,其中 Iter.__len__() 显式解包由 Iter.__iter__() 返回的迭代器:
>>> class Iter:
... def __iter__(self):
... return range(5).__iter__()
... def __len__(self):
... return list.__len__([*self.__iter__()])
...
>>> len(Iter())
5
那么就没有错误了。
从回溯来看,似乎 list.__len__() 正在尝试调用 Iter.__len__() ,即使提供的参数据说已经是 native list 对象。 RecursionError 的原因是什么?

根据 schwobaseggl ,使用 set 而不是 list 不会导致 RecursionError :
>>> class Iter:
... def __iter__(self):
... return range(5).__iter__()
... def __len__(self):
... return set.__len__({*self})
...
>>> len(Iter())
5

最佳答案

它与解包本身没什么关系,但与不同集合类型的实现有关,特别是它们的构造函数。

[*iterable]  # list
(*iterable,) # tuple
{*iterable} # set
所有触发对其类各自构造函数的调用。
来自 current C implementation for list(iterable) :
list___init___impl(PyListObject *self, PyObject *iterable) {
/* ... */
if (iterable != NULL) {
if (_PyObject_HasLen(iterable)) {
Py_ssize_t iter_len = PyObject_Size(iterable);
if (iter_len == -1) {
if (!PyErr_ExceptionMatches(PyExc_TypeError)) {
return -1;
}
PyErr_Clear();
}
if (iter_len > 0 && self->ob_item == NULL
&& list_preallocate_exact(self, iter_len)) {
return -1;
}
}
PyObject *rv = list_extend(self, iterable);
/* ... */
}
可以看出(即使像我这样有限的 C 知识),测试迭代器的大小以便分配正确的内存量,这就是触发对 __len__ 的调用的原因。通过的迭代。
不出所料,可以验证 set没有这样的事情。毕竟,传递的迭代的大小和结果集的大小之间的关系远没有列表或元组那么直接。例如,想想 set([1] * 10**5) .使用传递列表的大小信息为集合分配内存是愚蠢的。
在旁注中,正如本网站上的评论和许多其他问题/答案所指出的那样(例如 here ):
如果要确定 iterable 的长度,除了将所有项目收集到 Sized 中之外,还有更多(主要是节省空间的)有效方法。集合,例如:
def __len__(self):
return sum(1 for _ in self)

关于python - Python 的迭代器解包(star unpacking)是如何实现的(或者说,解包自定义迭代器涉及哪些神奇的方法?),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65428255/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com