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


101

目前,我正在这样做:

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

但是我想要一个可以放在简单if语句中的表达式。是否有内置的东西可以使这段代码显得不太笨拙?

any()False如果iterable为空,则返回,但如果不是,则可能会遍历所有项目。我只需要检查第一项即可。


有人问我要做什么。我编写了一个函数,该函数执行SQL查询并产生其结果。有时,当我调用此函数时,我只想知道查询是否返回了任何内容,并据此做出决定。


2
该代码的另一个问题是您不能将其打包为一个函数,因为它将吞噬第一个元素。好问题。
andrewrk

2
就我而言,我根本不需要该元素,我只想知道至少有一个元素。
巴斯蒂安·莱纳德(BastienLéonard)2010年

2
哈哈!我在试图找到相同解决方案的用例!
丹尼尔(Daniel)


Answers:


134

any如果为True,则不会超出第一个元素。万一迭代器产生一些虚假的东西,您可以编写any(True for _ in iterator)


这似乎对我有用,具有重新审视的功能。您可以轻松地测试任何停止成功的方法:先运行any((x > 100 for x in xrange(10000000))),再运行any((x > 10000000 for x in xrange(100000000)))-第二步需要更长的时间。
chbrown 2012年

1
这适用于“至少x”的情况sum(1 for _ in itertools.islice(iterator, max_len)) >= max_len
Dave Butler

11
同样,如果您需要检查迭代器是否为空,则可以使用all(False for _ in iterator)它将检查迭代器是否为空。(如果迭代器为空,则全部返回True,否则在看到第一个False元素时停止)
KGardevoir 2014年

22
这种解决方案的最大问题是,如果它不是空的,那么您实际上不能使用迭代器的返回值,对吗?
肯·威廉姆斯

42

在Python 2.6+中,如果名称sentinel绑定到迭代器无法产生的值,

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

如果您不知道迭代器可能产生的结果,请使用以下命令创建自己的标记(例如,在模块顶部)

sentinel = object()

否则,您可以在哨兵角色中使用您“知道”(基于应用程序考虑)迭代器可能无法产生的任何值。


1
真好!对于我的用例来说,if not next(iterator, None):就足够了,因为我确定None不会成为项目的一部分。感谢您指出正确的方向!
wasabigeek

1
@wasabi请记住,not对于任何虚假对象,例如空列表,False和零,它将返回True。is not None更安全,我认为更清晰。
Caagr98

21

这并不是真正干净,但是它显示了一种无损地将其打包在函数中的方法:

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

这并不是真正的pythonic,在特定情况下,可能会有更好(但不太通用)的解决方案,例如下一个默认解决方案。

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

这不是一般性的,因为None在许多可迭代对象中都可以是有效元素。


这可能是执行此操作的最佳方法。但是,这将有助于了解OP打算做什么?可能有一个更优雅的解决方案(毕竟,这是Python)。
rossipedia 2010年

谢谢,我想我会用的next()
巴斯蒂安·莱纳德(BastienLéonard)2010年

1
@Bastien,很好,但是请使用适当的哨兵(请参阅我的答案)。
Alex Martelli 2010年

3
此解决方案存在大量内存泄漏。将tee在itertools将不得不每一个元素不断从原来的迭代器的情况下,any_check以往需要推进。这比仅将原始迭代器转换为列表要糟糕。
2011年

1
@RafałDowgird 这比仅将原始迭代器转换为列表更糟糕。并非如此-考虑无限序列。
Piotr Dobrogost

6

您可以使用:

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

但这对于代码阅读器来说有点不解


2
..(您可以使用任何1项可迭代项代替[None])
mykhal 2010年

5

最好的方法是使用peekablefrom more_itertools

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

请注意,如果您保留对旧迭代器的引用,则该迭代器将变得高级。从那时起,您必须使用新的可窥视迭代器。但是,实际上,peekable期望是修改该旧迭代器的唯一代码,因此无论如何您都不应保留对旧迭代器的引用。


3

关于什么:

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

4
有趣的一个!但是,如果next()返回的是False,那是因为它真正产生了该怎么办?
巴斯蒂安·莱奥纳德

@BastienLéonard创建一个类class NotSet: pass,然后检查if next(i, NotSet) is NotSet: print("Iterator is empty")
Elijas

-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
不保证每个迭代器。>>> def it():...产生1 ...产生2 ...产生3 ... >>> i = it()>>> i .__ length_hint__追溯(最近一次调用为最新):“文件” <标准输入>”,第1行,在<模块> AttributeError的: '发电机'对象没有属性' length_hint '
andrewrk

3
对于具有多个零项的迭代器,它返回0可能也是合法的,因为这只是一个提示。
格伦·梅纳德

-1

这是一个过大的迭代器包装器,通常可以检查是否存在下一项(通过转换为布尔值)。当然效率很低。

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]

-2

有点晚了,但是...您可以将迭代器变成一个列表,然后使用该列表:

# 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
这是低效的,因为它会枚举整个列表。不适用于无限发电机。
becko 2015年

@becko:同意。但这在原始问题中似乎并非如此。
詹斯(Jens)2015年

3
另一个问题是,迭代器可以生成对象的无限量,这会导致内存溢出,而事实上,该程序将永远不会到达下一个声明
威廉·Onsem
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.