【问题标题】:Understanding len function with iterators用迭代器理解 len 函数
【发布时间】:2014-11-01 21:34:37
【问题描述】:

阅读文档后,我注意到内置函数 len 不支持所有可迭代对象,仅支持序列和映射(和集合)。在阅读之前,我一直认为len 函数使用了迭代协议来评估对象的长度,所以读到这里我真的很惊讶。

我阅读了已经发布的问题(herehere),但我仍然感到困惑,我仍然不明白为什么不允许 len 处理所有可迭代对象的真正原因。

这是一个比实施更概念性/逻辑性的原因吗?我的意思是当我问一个对象的长度时,我问的是一个属性(它有多少个元素),一个作为生成器的对象没有的属性,因为它们内部没有元素,产生元素。

此外,生成器对象可以产生无限的元素,带来未定义的长度,这是其他对象(如列表、元组、字典等)无法发生的事情......

我是对的,还是我没有考虑更多见解/更多内容?

【问题讨论】:

  • 我不认为你会得到比你已经看到的更好的答案。
  • 这也让我很困惑。为什么 python 在生成器上支持sumall,但不支持len?这基本上是同一类东西。 PEP 或邮件列表中的某处必须有解释...
  • 你会在迭代器上使用len 做什么?找出它有多长的唯一方法是迭代它,所以一旦你发现它有多长(假设它不是无限长)你已经消耗了它并且不能将它用于任何事情否则。
  • @EMS - 您的概括可能是正确的。在 Python 中,我希望 len() 始终是 O(1) 并且是非破坏性的。因此,我不认为它对迭代器有效。
  • @EMS:如果没有成员持有容器的长度,唯一知道终点在哪里的方法就是哨兵值。哨兵值比长度成员更令人头疼。想象一下,如果附加到一个列表需要您遍历整个内容以找到它的结尾。此外,保持长度不是 O(n);每个可能改变大小的操作都需要 O(1),而且它节省的时间比它花费的时间要多。

标签: python python-3.x


【解决方案1】:

最大的原因是降低了类型安全

您在实际需要的地方编写了多少程序来使用一个可迭代对象,只是为了知道它有多少元素,而丢弃其他任何东西?

在我使用 Python 编码的几年中,从来没有需要它。在正常程序中这是一个无意义的操作。迭代器可能没有长度(例如,无限迭代器或生成器期望通过send() 输入),因此要求它没有多大意义。 len(an_iterator) 产生错误这一事实意味着您可以在代码中找到错误。您可以看到,在程序的某个部分中,您在错误的事情上调用了len,或者您的函数实际上可能需要一个序列而不是您预期的迭代器。

删除此类错误会产生一类新的错误,人们在调用 len 时会错误地使用迭代器,或者在没有意识到的情况下将迭代器当作序列使用。

如果你真的需要知道迭代器的长度,len(list(iterator)) 有什么问题?额外的6个字符?编写您自己的适用于迭代器的版本很简单,但是,正如我所说,99% 的情况下,这仅仅意味着 您的代码有问题,因为这样的操作并没有多大意义感觉。

第二个原因是,通过该更改,您违反了 len 的两个很好的属性,这些属性当前适用于所有(已知)容器:

  • 众所周知,在 Python 中实现的所有容器(所有内置插件、标准库、numpy & scipyall 其他大型第三方库在动态大小和静态大小的容器上执行此操作)。因此,当您看到len(something) 时,您就知道len 调用很便宜。让它与迭代器一起工作意味着突然间所有程序都可能由于计算长度而变得低效。

    还请注意,您可以轻松地在每个容器上实现 O(1) __len__。预先计算长度的成本通常可以忽略不计,通常值得支付。 唯一的例外是如果您实现 不可变 容器,这些容器的部分内部表示与其他实例共享(以节省内存)。但是,我不知道有什么实现可以做到这一点,而且大多数情况下,无论如何你都可以实现比 O(n) 更好的时间。

    总而言之:目前每个人都在 O(1) 中实现__len__,并且很容易继续这样做。所以期望调用len 是O(1)。即使它不是标准的一部分。 Python 开发人员有意在他们的文档中避免使用 C/C++ 的风格法律术语并信任用户。在这种情况下,如果您的 __len__ 不是 O(1),那么您应该记录下来。

  • 众所周知,它没有破坏性__len__ 的任何合理实现都不会改变它的论点。所以你可以确定len(x) == len(x),或者n = len(x);len(list(x)) == n

    即使这个属性在文档中也没有定义,但是每个人都期望它,目前没有人违反它。

这样的属性很好,因为您可以使用它们对代码进行推理和假设。 它们可以帮助您确保一段代码的正确性,或了解其渐近复杂性。您提出的更改会使查看某些代码并理解它是否正确或它的复杂性变得更加困难,因为您必须牢记特殊情况。

总而言之,您提出的更改有一个非常小的优点:在非常特殊的情况下节省很少的字符,但它有几个很大的缺点,会影响现有代码的很大一部分。


另一个次要原因。如果len 使用迭代器,我肯定有些人会因为它的副作用而开始滥用它(取代已经丑陋的map 或列表理解)。突然间人们可以编写如下代码:

len(print(something) for ... in ...)

打印文本,这真的很丑。不好读。有状态的代码应该与语句相关联,因为它们提供了副作用的视觉提示。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-05
    • 2012-07-12
    • 1970-01-01
    • 1970-01-01
    • 2013-08-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多