Python 迭代器中有 hasnext() 方法吗?

241
Python 迭代器有 hasnext 方法吗?

目前还没有这种东西。我认为,正确的做法是在迭代器本身中添加“下一个”和“是否有下一个”方法,因为这些方法不应该被独立定义。 - Shiv Krishna Jaiswal
(对于上下文,Java和C#中的迭代器都有hasNext()Next()。Python通过在生成器结束时抛出异常来避免这种情况。而next(gen, default_value)习语允许您消除该异常,而无需使用try..except。) - smci
20个回答

364

避免捕获 StopIteration 的替代方法是使用 next(iterator, default_value)

例如:

>>> a = iter('hi')
>>> print(next(a, None))
h
>>> print(next(a, None))
i
>>> print(next(a, None))
None

这样,您可以检查None以查看是否已到达迭代器的末尾,如果您不想使用异常方式进行操作。

如果您的可迭代对象可能包含None值,则必须定义一个哨兵值并检查它:

>>> sentinel = object()
>>> a = iter([None, 1, 2])
>>> elem = next(a, sentinel)
>>> if elem is sentinel:
...     print('end')
... 
>>>

99
如果你将None作为“标记值(sentinel)”,请确保你的迭代器中没有包含任何None值。你也可以使用 sentinel = object()next(iterator, sentinel)is运算符进行测试。 - sam boosalis
4
跟随 @samboosalis 的建议,我宁愿使用内置的 unittest.mock.sentinel 对象,这样你就可以编写明确的 next(a, sentinel.END_OF_ITERATION),然后检查 if next(...) == sentinel.END_OF_ITERATION - ClementWalter
这比异常更漂亮。 - Dee
11
问题在于,这种方式会消耗掉迭代器中的下一个值。Java 中的 hasNext 不会消耗下一个值。 - Alan Franzoni

155

没有这样的方法。迭代结束是通过异常来指示的。请查看文档


92
"宁可犯错,也不要过问许可。" - Roger Pate
171
"宁可得到原谅,也不要事先获得许可。":检查迭代器是否有下一个元素并不是在请求许可。有些情况下,您想要测试下一个元素的存在性而不消耗它。如果有一个unnext()方法可以在调用next()检查元素是否存在后将第一个元素放回,则我会接受try catch解决方案。 - Giorgio
22
@Giorgio,如果不执行生成该元素的代码(你不知道生成器是否会执行yield),就无法确定另一个元素是否存在。当然,编写一个适配器来存储next()的结果并提供has_next()move_next()也不难。注意保持原文意思不变,同时让翻译更加通俗易懂。 - avakar
7
同样的想法可以用来实现hasNext()方法(在成功时生成、缓存并返回true,或者在失败时返回false)。然后,hasNext()next()都将依赖于一个共同的底层getNext()方法和缓存项。如果提供适配器很容易实现,我真的看不出为什么next()不应该在标准库中。 - Giorgio
5
您的意思是例如可以在读取文件时更改文件的迭代器?我同意这可能是一个问题(它会影响到任何提供 next()hasNext() 方法的库,而不仅仅是假设的Python库)。因此,如果流中的内容取决于何时读取元素,则next()hasNext()变得棘手。 - Giorgio
显示剩余6条评论

49

不可以,但是你可以实现自己的可迭代包装类来实现:

from collections.abc import Iterator

class hn_wrapper(Iterator):
    def __init__(self, it):
        self.it = iter(it)
        self._hasnext = None
    def __iter__(self): 
        return self
    def __next__(self):
        if self._hasnext:
            result = self._thenext
        else:
            result = next(self.it)
        self._hasnext = None
        return result
    def hasnext(self):
        if self._hasnext is None:
            try: 
                self._thenext = next(self.it)
            except StopIteration: 
                self._hasnext = False
            else:
                self._hasnext = True
        return self._hasnext

然后你可以像这样使用它:

x = hn_wrapper('ciao')
while x.hasnext():
    print(next(x))

它会发出信号。

c
i
a
o

15
вҖңfaithfully transcribing an algorithm from a reference implementation in JavaвҖқжҳҜйңҖиҰҒhas_nextж–№жі•зҡ„жңҖзіҹзі•зҡ„еҺҹеӣ гҖӮPythonзҡ„и®ҫи®ЎдҪҝеҫ—ж— жі•дҪҝз”ЁfilterжЈҖжҹҘж•°з»„жҳҜеҗҰеҢ…еҗ«дёҺз»ҷе®ҡи°“иҜҚеҢ№й…Қзҡ„е…ғзҙ гҖӮPythonзӨҫеҢәзҡ„еӮІж…ўе’Ңзӣ®е…үзҹӯжө…д»ӨдәәйңҮжғҠгҖӮ - Jonathan Cast
好的答案,我会将其复制以用作从Java代码中提取某些设计模式的示例。 - madtyn
1
我正在使用Python3,但这段代码给了我“TypeError: iter() returned non-iterator”错误。 - madtyn
3
@JonathanCast 我不确定我理解了。在Python中,通常会使用mapany而不是filter,但是你可以使用SENTINEL = object();next(filter(predicate,arr),SENTINEL)不是SENTINEL,或者忘记一个SENTINEL并且只是使用try:except捕获StopIteration - juanpa.arrivillaga
更符合 Python 风格的做法是像 next() 一样实现 hasnext(),即将方法重命名为 hn_wrapper.__hasnext__(),然后定义一个顶层函数来调用该方法:def hasnext(iterable): return iterable.__hasnext__() - user3064538

17

除了所有关于StopIteration的提及外,Python for循环也能实现您想要的功能:

>>> it = iter('hello')
>>> for i in it:
...     print(i)
...
h
e
l
l
o

10

尝试使用任何迭代器对象的__length_hint__()方法:

iter(...).__length_hint__() > 0

6
我一直很好奇为什么Python会有那些__ xxx __方法?它们看起来很丑。 - mP.
8
合理的问题!通常情况下,公开函数的语法是方法的语法(例如len实际上是调用__len__)。虽然length_hint没有这样的内置函数,但它实际上是一个未决议案(PEP424)。 - fulmicoton
1
@mP. 这些函数存在是因为有时候需要它们。它们故意设计得很丑陋,因为它们被视为最后的手段:如果你使用它们,你知道你正在做一些非Pythonic和潜在危险的事情(这也可能随时停止工作)。 - Arne Babenhauserheide
3
__init____main__ 这样的命名方式?在我看来,无论你如何辩解,这都有点混乱。 - user1363990

8
你可以使用 itertools.tee 对迭代器进行复制,并检查复制后的迭代器是否出现 StopIteration

5

3

13
Python使用异常来控制流程?听起来相当不好理解。 - mP.
5
正确做法是使用异常处理错误,而不是定义正常的控制流程。 - Giorgio

2

1

也许只是我个人的看法,但我喜欢https://stackoverflow.com/users/95810/alex-martelli的回答,不过我认为这篇文章更易读:

from collections.abc import Iterator  # since python 3.3 Iterator is here

class MyIterator(Iterator):  # need to subclass Iterator rather than object
  def __init__(self, it):
    self._iter = iter(it)
    self._sentinel = object()
    self._next = next(self._iter, self._sentinel)
    
  def __iter__(self): 
    return self
  
  def __next__(self):        # __next__ vs next in python 2
    if not self.has_next():
      next(self._iter)  # raises StopIteration

    val = self._next
    self._next = next(self._iter, self._sentinel)
    return val
  
  def has_next(self):
    return self._next is not self._sentinel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接