在python中,iterable的接口是迭代器接口的一个子集。这样做的好处是,在许多情况下,它们都可以用同样的方法进行治疗。但是,这两者之间有一个重要的语义差异,因为对于一个不可迭代的__iter__返回一个新的迭代器对象,而不仅仅是self。如何测试iterable是否真的是iterable而不是迭代器?从概念上讲,我将iterables理解为集合,而迭代器只管理迭代(即跟踪位置),而不是集合本身。
例如,当一个人想要循环多次时,差异就很重要。如果给定了迭代器,则第二个循环将不工作,因为迭代器已经用完,并直接引发StopIteration。
测试next方法是很有诱惑力的,但这看起来很危险,而且有点错误。我应该检查一下第二个循环是否是空的吗?
有没有什么方法可以用更为Python式的方式来做这样的测试?我知道这听起来像一个经典的LBYL反对EAFP的案例,所以也许我应该放弃?还是我错过了什么?
编辑:洛特在下面的回答中说,这主要是一个想要在迭代器上进行多次传递的问题,一开始不应该这样做。但是,在我的例子中,数据非常大,根据具体情况,必须多次传递以进行数据处理(绝对没有办法解决这个问题)。
iterable也由用户提供,在单次传递足够的情况下,它将使用迭代器(例如,为了简单起见,由生成器创建)。但是,如果用户在需要多个过程时只提供迭代器,那么最好防止出现这种情况。
编辑2:实际上,这是一个非常好的抽象基类示例。迭代器和iterable中的__iter__方法具有相同的名称,但语义不同!因此,hasattr是无用的,但isinstance提供了一个干净的解决方案。
1
| 'iterator' if obj is iter(obj) else 'iterable' |
- 哇,这似乎是我一直在寻找的答案,谢谢!我会等一会儿再接受,以防有人指出这个问题。
- 好吧,问题是对obj.uuIter_uuuu()的一个"浪费"调用,但我看不到其他可靠的方法。
- 虽然我不知道一个反例,但这并不能保证有效。
- @_Ω__:好吧,你可以想象对象,它没有。next(),但是有_uuIter_uuuuuuuu(self)=lambda x:x
- @_Ω__:但同样,这种物体的意义是什么?
- 我从来没有说过没有东西的事。下一步。你的前提是iter(obj) is obj,而afaik是真的,但这并不能保证。
- @Tzot是根据pep 234来保证的——一个想要成为迭代器的类应该实现两个方法:一个next()方法,其行为如上所述,另一个__iter__()方法返回self。
- 答案假设迭代器集和iterables是不相交的,事实并非如此。
- -1文件对象是它自己的迭代器,例如iter(f)返回f(除非f关闭)。- 5.9。文件对象
- @piotrdobrogost:ypb?"如果它像鸭子一样咯咯叫……文件对象实现迭代器接口。
- 我的观点是,如果它像迭代器一样发出嘎嘎声,它像iterable一样发出嘎嘎声,那么两者都是。正如我在我之前的评论中所说,您的测试假设一些对象不能同时是迭代器和ITerable,这是错误的,文件对象就是这样一个对象的例子。
However, there is an important semantic difference between the two...
不是真正意义上的或重要的。它们都是不可更改的——它们都使用for语句。
The difference is for example important when one wants to loop multiple times.
这是什么时候发生的?你得更具体一点。在极少数情况下,当您需要通过一个不可重复的集合进行两次传递时,通常会有更好的算法。
例如,假设您正在处理一个列表。您可以根据需要迭代一个列表。为什么你被一个迭代器而不是iterable缠住了?好吧,那不管用。
好的,这是一个。你在两次读取一个文件,你需要知道如何重置iterable。在这种情况下,它是一个文件,需要seek;或者关闭并重新打开。感觉恶心。您可以使用ecx1〔1〕获得一个列表,允许两次通过而不复杂。所以这是不必要的。
等等,如果我们有一个文件这么大,我们不能把它全部读到内存中怎么办?而且,由于不明原因,我们也找不到。那么呢?
现在,我们只剩下两次传球了。第一次,我们积累了一些东西。索引、摘要或其他东西。索引包含文件的所有数据。摘要通常是对数据的重组。通过从"摘要"到"重新构造"的微小更改,我们将文件的数据保存在新的结构中。在这两种情况下,我们都不需要文件——我们可以使用索引或摘要。
所有"两通"算法都可以更改为原始迭代器或iterable的一通,以及不同数据结构的第二通。
这既不是lybl也不是eafp。这是算法设计。您不需要重置迭代器——yagni。
编辑
下面是一个迭代器/可迭代问题的例子。这只是一个设计糟糕的算法。
1 2 3
| it = iter(xrange(3))
for i in it: print i,; #prints 1,2,3
for i in it: print i,; #prints nothing |
这是微不足道的固定。
1 2 3
| it = range(3)
for i in it: print i
for i in it: print i |
"多次并联"是一成不变的。编写一个需要ITerable的API。当有人拒绝阅读API文档或者在阅读之后拒绝跟随它时,他们的东西就会断裂。应该如此。
"如果一个用户在需要多次传递时只提供一个迭代器,那么很好地防止出现这种情况",这两个例子都是疯狂的人编写的代码破坏了我们的简单API。
如果有人精神错乱,足以阅读大部分(但不是所有的API文档),并在需要iterable时提供迭代器,那么您需要找到此人并教他们(1)如何阅读所有的API文档,以及(2)遵循API文档。
"保障"问题不太现实。这些疯狂的程序员非常罕见。在少数情况下,当它确实出现时,你知道他们是谁,并且可以帮助他们。
编辑2
"我们必须多次读取相同的结构"算法是一个基本问题。
不要这样做。
1 2 3 4 5
| for element in someBigIterable:
function1( element )
for element in someBigIterable:
function2( element )
... |
改为这样做。
1 2 3 4
| for element in someBigIterable:
function1( element )
function2( element )
... |
或者,考虑这样的事情。
1 2 3
| for element in someBigIterable:
for f in ( function1, function2, function3, ... ):
f( element ) |
在大多数情况下,这种算法的"支点"会导致程序更容易优化,并且可能是性能的净改进。
- 平行多次怎么样?例如,几个线程在同一集合上迭代?或者甚至是一个线程,例如"这个集合是否有相同的元素两次?".
- 谢谢,我给这个问题加了一个解释。你的观点是正确的,但在我看来,我相信这是行不通的。
- "非常罕见"。我不同意,不能从迭代器中区分ITerable和Iterator的程序员并不罕见。"你知道他们是谁,可以帮助他们。"这通常不是你的工作,在公司里,"帮助他们"不会被很好地理解,特别是如果是另一个部门。
- @瓦特克:这是你的应用程序/库/框架,你需要支持它。帮助那些拒绝阅读API并且无法理解为什么不遵守规则而导致API崩溃的疯狂程序员,我理解,这就是支持。根据我的经验,这是很明显的。
- @洛特:在一个完美的世界里,你是对的。在公司政治中,另一个部门的代码不正确会导致冲突。如果另一个部门有更多的政治影响力,你的帮助将被视为"试图掩盖无能"。不管你是对是错,它都不会起作用。
- +1用于编辑。我被教导过,这是合同编程。如果一方不遵守,另一方也不需要遵守。
- @在这里,我可以请您注意这个问题:stackoverflow.com/question s/701088/…谢谢!
- @Becomingguru:专注于内存管理可能会变得愚蠢。我的观点是,许多"二通"算法在第一次通过时都会显著减少数据量;第二次通过是不必要的,因为它在处理较小的数据结构。
- @瓦特克:如果你的组织的公司政治运作不正常,以致于帮助冲突,你应该找一个更好的组织来工作。编写无用的代码来解决组织问题是一个巨大的失败等待发生。
- @海科格拉赫:更重要的是,如果一方不遵守,另一方就不能强制执行。如果他们不服从,他们就写了这个错误;你不能纠正他们拒绝服从的行为。
- @洛特:我已经做了。但据我所知,大多数大公司的情况都差不多。大型官僚机构总是效率低下。
- @瓦特克:在过去的30年里,我从来没有在一个地方工作过,在那里帮助别人成为冲突或被认为是很糟糕的。在100多个地点,API合同从来都不是问题。帮助那些无法遵循API的疯狂程序员的复杂代码是——很简单——糟糕的。
- +1用于解决2个问题,以这样的长度传递一个大数据。我同意——如果您似乎需要对完全相同、不变的整个数据重复两次,那么就需要解决一个设计问题。
- @S.lott这是一种令人讨厌的态度,如果你不能设想一个需要多次通过的案例,那么对它的需要是不值得考虑的,或者它不能作为一个有价值的问题的基础。我相信你一定听说过高斯-赛德尔或其他迭代算法来解线性方程。你能一次通过吗?线性方程组也不是火箭科学。这里所需要的只是一个问题的直接答案,而不是一个自鸣得意的关于糟糕算法的讲座,而OP甚至没有问。
- @斯雷恩:假设其他人都知道一些需要多次传递数据的算法,这是一种令人讨厌的态度。可悲的是,很少有人有同样深刻的洞察力。因为我不知道,我无法提供有意义的评论。你可以提供有用的信息(比如一个具体的例子,即高斯-塞德尔)。或者你可以抱怨,因为我们中的一些人没有那么丰富的数学背景。
- @S.lott@nikow高斯-塞德尔的概念就操作而言是次要的,当然还有维基百科。最初的问题不是关于任何多路径算法的存在,直到你的自我引导你相信它是存在的,并且没有这种算法值得考虑。如果你能回答欧普的直接问题,如果不是,那么失去自以为是的态度,不要对欧普讲错了他的问题有多无意义,这会更有帮助。
- @斯雷恩:不知道一个例子不是"自我"。这是缺乏良好榜样的。"自以为是的态度"无所不知"?在讲课?真的。你一定很生气。对不起,我的回答让你很不安。我认为它们都是如何设计算法的很好的例子,这样一来,通过Python的一部分就不再是问题了。既然你有一个反例,你可以自由地写自己的答案。这似乎比点名更有成效。
- @S.lott"你可以自由地写下你自己的答案",我不喜欢用不相干的答案来污染它。无知当然不是自我,但断言或暗示操作可能是错误的,仅仅设想(甚至不使用)多次通过是。这也是假设性的,在这种情况下也是不正确的。正如您所说,您的示例可能很好,但与op的问题无关,后者是关于iterable和迭代器之间的区别。如果你必须的话,可以不征求别人的意见来教训别人,但是不管正确与否,失去这种态度会有很长的路要走。
- @斯雷恩:这个问题表明是一个设计问题。通过预防和避免来解决设计问题似乎比在不需要的地方尝试增加复杂性更简单。再一次。我只能为引起这种毫无根据的愤怒而道歉。这个问题表明了一个设计问题,通过避免更好地解决了。请发表你自己的答案。我看不到改变这一点,因为我仍然相信这是正确的方法,除了你确定的情况。
- +1-"编写需要ITerable的API。当有人拒绝阅读API文档或者在阅读之后拒绝跟随它时,他们的东西就会断裂。这让我意识到我在哪里想错了。iterable与iterator是API的一部分,也是相关函数的设计(不管它是您得到的还是您返回的)。
- 尽管这是一个很有价值的答案,你知道问题的答案吗?
1 2 3 4 5 6 7 8 9
| import itertools
def process(iterable):
work_iter, backup_iter= itertools.tee(iterable)
for item in work_iter:
# bla bla
if need_to_startover():
for another_item in backup_iter: |
雷蒙德从吉多借来的该死的时光机器…
因为Python的鸭子打字,
如果定义next()和__iter__()方法返回自身,则任何对象都是不可重写的。
如果对象本身没有next()方法,那么__iter__()可以返回任何具有next()方法的对象。
您可以参考这个问题来了解Python中的ITerability。
- 试试这个:class a(object):def uuiter uuuuuu(self):return iter([1,2,3])def next(self):yield 7
- 实际上,这是一个duck类型的问题:它可以隐藏语义/概念上的差异。它允许我们在范围(3)中为i而不是在iter(范围(3))中为i编写,但可能会导致细微的问题。
- 对不起,我没有完全明白这一点?有什么问题吗?
- @瓦特克,你评论中的代码应该演示什么?
- @彼得多布罗戈斯特:我不记得了,那是3年前的事了;-)