首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python的迭代器解包(星型解压缩)是如何实现的(或者,在解压缩自定义迭代器时涉及哪些神奇的方法?)

Python的迭代器解包(星型解压缩)是如何实现的(或者,在解压缩自定义迭代器时涉及哪些神奇的方法?)
EN

Stack Overflow用户
提问于 2020-12-23 17:15:03
回答 1查看 453关注 0票数 7

我正在编写一个定义__iter____len__的类,其中__len__的值取决于__iter__返回的迭代器。我得到了一个有趣的RecursionError

语言版本:Python3.8.6,3.7.6.示例仅用于说明错误。

在下面的示例中,Iter.__len__()尝试解压缩self,将结果存储在list中,然后尝试调用列表中的内置list.__len__()以获取长度。

代码语言:javascript
复制
>>> 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__()返回的迭代器

代码语言:javascript
复制
>>> class Iter:
...     def __iter__(self):
...         return range(5).__iter__()
...     def __len__(self):
...         return list.__len__([*self.__iter__()])
...
>>> len(Iter())
5

那么就没有错误了。

从回溯来看,list.__len__()似乎试图调用Iter.__len__(),即使提供的参数据说已经是一个原生list对象。RecursionError的原因是什么?

根据schwobaseggl的说法,使用set而不是list不会导致RecursionError

代码语言:javascript
复制
>>> class Iter:
...     def __iter__(self):
...         return range(5).__iter__()
...     def __len__(self):
...         return set.__len__({*self})
...
>>> len(Iter())
5
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-12-23 17:57:03

它与解压缩本身无关,而是与不同集合类型的实现,特别是它们的构造函数的实现有关。

代码语言:javascript
复制
[*iterable]  # list
(*iterable,) # tuple
{*iterable}  # set

所有触发对其类各自构造函数的调用。

来自current C implementation for list(iterable)

代码语言:javascript
复制
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集合中,例如:

代码语言:javascript
复制
def __len__(self):
    return sum(1 for _ in self)
票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65428255

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档