如何让生成器/迭代器在耗尽时评估为False?
在Python中,其他空的对象会被认为是False,比如空列表、空字符串等等。那么,我该如何让迭代器和生成器也能像这些空对象一样被认为是False呢?
3 个回答
一个“空的东西”自动就不是一个迭代器。容器可以是空的,也可以不是,你可以从这些容器中获取迭代器,但当迭代器用完时,它们并不会变成“假”的状态。
一个很好的例子是 sys.stdin
。如果在输入结束时让 sys.stdin
变成“假”的状态,那就会有问题,因为你无法知道这个输入流是否真的结束,除非你尝试从中读取输入。想要迭代器变成“假”的主要原因是为了“窥探”一下,看看下一个值是否有效;但对于 sys.stdin
来说,这显然不太实际。
这里还有一个例子
(x for x in xrange(1000) if random.randrange(0, 2))
在不知道这个生成器是否会再返回更多数字的情况下,你无法判断,实际上你得找出下一个值是什么。
解决办法就是直接从迭代器中获取下一个值。如果它是空的,你的循环就会结束,或者如果你不在循环中,你会得到一个 StopIteration
的异常。
Guido不希望生成器和迭代器以那种方式工作。
对象默认是“真”的,只有在它们定义了__len__方法并返回零,或者定义了__nonzero__方法并返回False(在Python 3.x中叫做__bool__)时,才会被认为是“假”。
你可以给自定义的迭代器添加这些方法,但这并不符合Guido的初衷。他拒绝在已知即将到来的长度的迭代器上添加__len__方法。因此,我们才有了__length_hint__这个方法。
所以,判断一个迭代器是否为空的唯一方法就是调用next(),看看它是否会抛出StopIteration异常。
在ASPN上,我相信有一些使用这种技术的示例,用于提前查看的包装器。如果获取了一个值,它会被保存起来,以便在下一个next()调用时使用。
在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__
方法。