一个检查迭代器是否至少产生一个元素的一行代码?

145

目前我正在做这个:

try:
    something = next(iterator)
    # ...
except StopIteration:
    # ...

但我想要一个可以放在简单的if语句中的表达式。有没有内置的东西可以使这段代码看起来不那么笨拙?我只需要检查第一个项目。


4
那段代码的问题是,你不能把它打包成一个函数,因为它会吞掉第一个元素。好问题。 - andrewrk
2
在我的情况下,我根本不需要元素,我只想知道至少有一个元素。 - Bastien Léonard
2
哈哈!我的使用情况与您一样,试图找到相同的解决方案! - Daniel
相关链接:https://dev59.com/uHRB5IYBdhLWcg3wUVtd - Mr_and_Mrs_D
2
还有相关内容:Python 迭代器中的 hasNext? - user2314737
9个回答

183
if any(True for _ in iterator):
    print('iterator had at least one element')

if all(False for _ in iterator):
    print('iterator was empty')

请注意,如果可迭代对象至少包含一个元素,则此操作将消耗第一个元素。

这对我来说似乎可行,使用re.finditer。你可以轻松地测试任何在第一次成功时都会停止:运行 any((x > 100 for x in xrange(10000000))),然后运行 any((x > 10000000 for x in xrange(100000000))) -- 第二个应该花费更长的时间。 - chbrown
1
这适用于“至少x”的情况 sum(1 for _ in itertools.islice(iterator, max_len)) >= max_len - Dave Butler
14
同样地,如果您需要检查迭代器是否为空,可以使用all(False for _ in iterator)来检查迭代器是否为空。(all返回True如果迭代器为空,否则它会在看到第一个False元素时停止) - KGardevoir
52
这个解决方案的一个大问题是,如果迭代器返回值不为空,你实际上无法使用它,对吗? - Ken Williams
10
注意!这将消耗迭代器的第一个项,因此它会被丢失,无法再使用。 - ovimunt
2
你可以使用 if list(iterator) == [] 或者 if not any(iterator) 来检查它是否为空,或者使用 if any(iterator) 来检查它是否包含某些内容。 - Break

63
将一个哨兵值作为默认值传递给next():
sentinel = object()

if next(iterator, sentinel) is sentinel:
    print('iterator was empty')

你还可以使用任何你“知道”(基于应用程序考虑)迭代器不可能产生的值作为哨兵值。


2
太好了!对于我的用例,if not next(iterator, None): 就足够了,因为我确定 None 不会是其中的一项。感谢您指引我正确的方向! - wasabigeek
2
@wasabi 请记住,not 对于任何假值对象都会返回 True,例如空列表、False 和零。is not None 更安全,而且在我看来更清晰明了。 - Kyuuhachi

28

这并不是真正的清晰简洁,但它展示了一种无损封装的方式:

def has_elements(iter):
  from itertools import tee
  iter, any_check = tee(iter)
  try:
    any_check.next()
    return True, iter
  except StopIteration:
    return False, iter

has_el, iter = has_elements(iter)
if has_el:
  # not empty

这并不是非常符合 Python 编程风格,对于特定的情况,可能会有更好(但不太通用)的解决方案,例如next函数。

first = next(iter, None)
if first:
  # Do something

这不是通用的,因为在许多可迭代对象中,None 可能是一个有效的元素。


这可能是最好的方法。但是,了解 OP 正在尝试做什么会有所帮助。毕竟,这是 Python,可能有更优雅的解决方案。 - rossipedia
1
谢谢,我想我会使用next() - Bastien Léonard
1
@Bastien,好的,但要使用适当的“哨兵”(请参见我的答案)。 - Alex Martelli
3
这个解决方案存在严重的内存泄漏问题。itertools中的“tee”在任何需要推进的情况下都必须保留原始迭代器中的每个元素。这比仅将原始迭代器转换为列表还要糟糕。 - Rafał Dowgird
1
@RafałDowgird 这比将原始迭代器转换为列表更糟糕。 不完全是这样 - 考虑无限序列。 - Piotr Dobrogost
@AlexMartelli - None 可能是一个完全合适的哨兵值,例如在 @BastienLéonard 的原始用例中,检查 SQL 查询是否有有效结果。 - Ken Williams

16

最好的方法是使用来自more_itertoolspeekable

from more_itertools import peekable
iterator = peekable(iterator)
if iterator:
    # Iterator is non-empty.
else:
    # Iterator is empty.

注意,如果您保留了对旧迭代器的引用,则该迭代器将被推进。从那时起,您必须使用新的可窥探迭代器。 peekable 希望是唯一修改旧迭代器的代码。


6
提醒一下,more_itertools不是Python标准库的一部分。 - tejasvi88

7

您可以使用:

if zip([None], iterator):
    # ...
else:
    # ...

但是对于代码读者来说有点不够清晰易懂


2
返回翻译文本:(你可以使用任何一个包含一个元素的可迭代对象,而不是[None]) - mykhal

6

关于:

In [1]: i=iter([])

In [2]: bool(next(i,False))
Out[2]: False

In [3]: i=iter([1])

In [4]: bool(next(i,False))
Out[4]: True

5
有趣的问题!但如果next()返回False是因为它确实是被yield出来的值怎么办? - Bastien Léonard
@BastienLéonard 创建一个类 class NotSet: pass,然后检查 if next(i, NotSet) is NotSet: print("Iterator is empty") - Elijas Dapšauskas

0

这是一个过度封装的迭代器,通常允许检查是否有下一个项目(通过转换为布尔值)。当然,非常低效。

class LookaheadIterator ():

    def __init__(self, iterator):
        self.__iterator = iterator
        try:
            self.__next      = next (iterator)
            self.__have_next = True
        except StopIteration:
            self.__have_next = False

    def __iter__(self):
        return self

    def next (self):
        if self.__have_next:
            result = self.__next
            try:
                self.__next      = next (self.__iterator)
                self.__have_next = True
            except StopIteration:
                self.__have_next = False

            return result

        else:
            raise StopIteration

    def __nonzero__(self):
        return self.__have_next

x = LookaheadIterator (iter ([]))
print bool (x)
print list (x)

x = LookaheadIterator (iter ([1, 2, 3]))
print bool (x)
print list (x)

输出:

False
[]
True
[1, 2, 3]

这似乎是唯一符合原始要求(不使用非标准库)的答案:它允许您检查是否存在剩余元素而不消耗元素。不确定为什么它被投票降低了。当然,它只适用于您自己的LookaheadIterator(您不能将其与从标准库返回的迭代器一起使用),但即使如此,它似乎也符合OP的用例。 - not-just-yeti

-1

__length_hint__ 估计list(it)的长度 - 它是一个私有方法:

x = iter( (1, 2, 3) )
help(x.__length_hint__)
      1 Help on built-in function __length_hint__:
      2 
      3 __length_hint__(...)
      4     Private method returning an estimate of len(list(it)).

4
并非每个迭代器都能保证有__length_hint__属性。
def it(): ... yield 1 ... yield 2 ... yield 3 ... i = it() i.length_hint Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'generator' object has no attribute 'length_hint'
- andrewrk
3
如果一个迭代器有多个元素,它返回0也许是合法的,因为这只是一个提示。 - Glenn Maynard

-4
有点晚了,但是...你可以把迭代器转换成列表,然后使用该列表进行操作:
# Create a list of objects but runs out the iterator.
l = [_ for _ in iterator]

# If the list is not empty then the iterator had elements; else it was empty.
if l :
    pass # Use the elements of the list (i.e. from the iterator)
else :
    pass # Iterator was empty, thus list is empty.

4
这种方式效率低下,因为它枚举整个列表。对于无限生成器不起作用。 - a06e
@becko:同意。但这似乎不是原问题的情况。 - Jens
3
另一个问题是迭代器可能会生成无限数量的对象,这可能导致内存溢出,同时程序将永远无法到达下一条语句。 - Willem Van Onsem

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