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

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

目前,我正在这样做:

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

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

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


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

Currently I’m doing this:

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

But I would like an expression that I can place inside a simple if statement. Is there anything built-in which would make this code look less clumsy?

any() returns False if an iterable is empty, but it will potentially iterate over all the items if it’s not. I only need it to check the first item.


Someone asks what I’m trying to do. I have written a function which executes an SQL query and yields its results. Sometimes when I call this function I just want to know if the query returned anything and make a decision based on that.


回答 0

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

any won’t go beyond the first element if it’s True. In case the iterator yields something false-ish you can write any(True for _ in iterator).


回答 1

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

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

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

sentinel = object()

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

In Python 2.6+, if name sentinel is bound to a value which the iterator can’t possibly yield,

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

If you have no idea of what the iterator might possibly yield, make your own sentinel (e.g. at the top of your module) with

sentinel = object()

Otherwise, you could use, in the sentinel role, any value which you “know” (based on application considerations) that the iterator can’t possibly yield.


回答 2

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

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在许多可迭代对象中都可以是有效元素。

This isn’t really cleaner, but it shows a way to package it in a function losslessly:

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

This isn’t really pythonic, and for particular cases, there are probably better (but less general) solutions, like the next default.

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

This isn’t general because None can be a valid element in many iterables.


回答 3

您可以使用:

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

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

you can use:

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

but it’s a bit nonexplanatory for the code reader


回答 4

最好的方法是使用peekablefrom more_itertools

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

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

The best way to do that is with a peekable from more_itertools.

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

Just beware if you kept refs to the old iterator, that iterator will get advanced. You have to use the new peekable iterator from then on. Really, though, peekable expects to be the only bit of code modifying that old iterator, so you shouldn’t be keeping refs to the old iterator lying around anyway.


回答 5

关于什么:

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

What about:

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

回答 6

__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)).

__length_hint__ estimates the length of list(it) – it’s private method, though:

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)).

回答 7

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

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]

This is an overkill iterator wrapper that generally allows to check whether there’s a next item (via conversion to boolean). Of course pretty inefficient.

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)

Output:

False
[]
True
[1, 2, 3]

回答 8

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

# 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.

A little late, but… You could turn the iterator into a list and then work with that list:

# 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.