如何让生成器/迭代器在耗尽时评估为False?

16 投票
3 回答
6091 浏览
提问于 2025-04-17 05:28

在Python中,其他空的对象会被认为是False,比如空列表、空字符串等等。那么,我该如何让迭代器和生成器也能像这些空对象一样被认为是False呢?

3 个回答

4

一个“空的东西”自动就不是一个迭代器。容器可以是空的,也可以不是,你可以从这些容器中获取迭代器,但当迭代器用完时,它们并不会变成“假”的状态。

一个很好的例子是 sys.stdin。如果在输入结束时让 sys.stdin 变成“假”的状态,那就会有问题,因为你无法知道这个输入流是否真的结束,除非你尝试从中读取输入。想要迭代器变成“假”的主要原因是为了“窥探”一下,看看下一个值是否有效;但对于 sys.stdin 来说,这显然不太实际。

这里还有一个例子

(x for x in xrange(1000) if random.randrange(0, 2))

在不知道这个生成器是否会再返回更多数字的情况下,你无法判断,实际上你得找出下一个值是什么。

解决办法就是直接从迭代器中获取下一个值。如果它是空的,你的循环就会结束,或者如果你不在循环中,你会得到一个 StopIteration 的异常。

11

Guido不希望生成器和迭代器以那种方式工作。

对象默认是“真”的,只有在它们定义了__len__方法并返回零,或者定义了__nonzero__方法并返回False(在Python 3.x中叫做__bool__)时,才会被认为是“假”。

你可以给自定义的迭代器添加这些方法,但这并不符合Guido的初衷。他拒绝在已知即将到来的长度的迭代器上添加__len__方法。因此,我们才有了__length_hint__这个方法。

所以,判断一个迭代器是否为空的唯一方法就是调用next(),看看它是否会抛出StopIteration异常。

在ASPN上,我相信有一些使用这种技术的示例,用于提前查看的包装器。如果获取了一个值,它会被保存起来,以便在下一个next()调用时使用。

7

在Python中,默认情况下所有对象的值都是 True。如果你想让某些对象的值变成 False,那么这个对象的类必须有一个 __len__ 方法(当长度为 0 时返回 False),或者有一个 __nonzero__ 方法(返回 False 时也返回 False)。注意:在Python 3.x中,__nonzero__ 被替换成了 __bool__

因为迭代器的协议设计得比较简单,而且有很多类型的迭代器或生成器在生成值之前并不知道是否还有更多的值可以生成,所以 True/False 的判断并不包含在迭代器协议中。

如果你真的想要这种判断行为,你需要自己实现。一个方法是把生成器或迭代器放在一个类里,这个类提供你需要的功能。

需要注意的是,这段代码只有在抛出 StopIteration 之后才会评估为 False

作为额外说明,这段代码在Python 2.4及以上版本都能工作。

try:
    next
except NameError:       # doesn't show up until python 2.6
    def next(iter):
        return iter.next()

Empty = object()

class Boolean_Iterator(object):
    """Adds the abilities
    True/False tests:  True means there /may/ be items still remaining to be used
    """
    def __init__(self, iterator):
        self._iter = iter(iterator)
        self._alive = True
    def __iter__(self):
        return self
    def __next__(self):
        try:
            result = next(self._iter)
        except StopIteration:
            self._alive = False
            raise
        return result
    next = __next__                     # python 2.x
    def __bool__(self):
        return self._alive
    __nonzero__ = __bool__              # python 2.x

如果你还想要提前查看(或窥视)行为,这段代码可以做到(在抛出 StopIteration 之前就评估为 False):

try:
    next
except NameError:       # doesn't show up until python 2.6
    def next(iter):
        return iter.next()

Empty = object()

class Iterator(object):
    """Adds the abilities
    True/False tests:  True means there are items still remaining to be used
    peek(): get the next item without removing it from the sequence
    """
    def __init__(self, iterator):
        self._iter = iter(iterator)
        self._peek = Empty
        self.peek()
    def __next__(self):
        peek, self._peek = self._peek, Empty
        self.peek()
        if peek is not Empty:
            return peek
        raise StopIteration
    next = __next__                     # python 2.x
    def __bool__(self):
        return self._peek is not Empty
    __nonzero__ = __bool__              # python 2.x
    def peek(self):
        if self._peek is not Empty:
            return self._peek
        self._peek = next(self._iter, Empty)
        return self._peek

请记住,当底层的迭代器或生成器的时机对其生成的值很重要时,窥视行为可能不太合适。

另外要注意,第三方代码,甚至标准库,可能会依赖于迭代器或生成器总是评估为 True。如果你想要窥视而不需要布尔值,可以去掉 __nonzero____bool__ 方法。

撰写回答