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


Python中的其他空对象评估为False - 我如何才能获得迭代器/生成器呢?


11433
2017-11-02 05:40


起源



答案:


Guido不希望生成器和迭代器以这种方式运行。

默认情况下,对象为true。只有当它们定义返回零的__len__或返回的__nonzero__时,它们才可以是假的  (后者在Py3.x中称为__bool__)。

您可以将其中一种方法添加到自定义迭代器中,但它与Guido的意图不匹配。他拒绝将__len__添加到即将到来的长度已知的迭代器中。这就是我们得到__length_hint__的方式。

因此,判断迭代器是否为空的唯一方法是调用 下一个() 在它上面看看它是否会升起 的StopIteration

在ASPN上,我相信有一些配方使用这种技术进行前瞻性包装。如果获取了一个值,则会将其保存为即将进行的next()调用。


7
2017-11-02 05:48



我并不是说所有迭代器/生成器都应该以这种方式运行,只是有时它对它们有用。对于那些时代,我的回答提供了一种方法。 - Ethan Furman
Guido建议没有迭代器/生成器应该这样做。 - Raymond Hettinger
关于Python的一个好处是,如果您需要/想要采用与“已批准”方法不同的方式,它不会(通常)阻碍您。 (不要让我开始 sum()ING str小号! ;) - Ethan Furman
这被称为“违背语言”。这意味着您的迭代器将无法在假定的代码中使用 bool(it) 总是 真正。 Guido能够提供这样的代码示例,包括他编写的一些代码。 (只是因为你可以指定False,True = 1,0并不意味着你应该;-) - Raymond Hettinger
确实!你可以发布这样的代码链接吗?我很好奇为什么人们会费心去检查永远存在的事物的真值 True。 - Ethan Furman


答案:


Guido不希望生成器和迭代器以这种方式运行。

默认情况下,对象为true。只有当它们定义返回零的__len__或返回的__nonzero__时,它们才可以是假的  (后者在Py3.x中称为__bool__)。

您可以将其中一种方法添加到自定义迭代器中,但它与Guido的意图不匹配。他拒绝将__len__添加到即将到来的长度已知的迭代器中。这就是我们得到__length_hint__的方式。

因此,判断迭代器是否为空的唯一方法是调用 下一个() 在它上面看看它是否会升起 的StopIteration

在ASPN上,我相信有一些配方使用这种技术进行前瞻性包装。如果获取了一个值,则会将其保存为即将进行的next()调用。


7
2017-11-02 05:48



我并不是说所有迭代器/生成器都应该以这种方式运行,只是有时它对它们有用。对于那些时代,我的回答提供了一种方法。 - Ethan Furman
Guido建议没有迭代器/生成器应该这样做。 - Raymond Hettinger
关于Python的一个好处是,如果您需要/想要采用与“已批准”方法不同的方式,它不会(通常)阻碍您。 (不要让我开始 sum()ING str小号! ;) - Ethan Furman
这被称为“违背语言”。这意味着您的迭代器将无法在假定的代码中使用 bool(it) 总是 真正。 Guido能够提供这样的代码示例,包括他编写的一些代码。 (只是因为你可以指定False,True = 1,0并不意味着你应该;-) - Raymond Hettinger
确实!你可以发布这样的代码链接吗?我很好奇为什么人们会费心去检查永远存在的事物的真值 True。 - Ethan Furman


默认情况下,Python中的所有对象都评估为 True。为了支持 False 评估对象的类必须有一个 __len__ 方法 (0  - > False),或者a __nonzero__ 方法 (False  - > False)。注意: __nonzero__ ==> __bool__ 在Python 3.x.

因为迭代器协议有意保持简单,并且因为有许多类型的迭代器/生成器在尝试生成它们之前无法知道是否有更多值要生成, True/False 评估不是迭代器协议的一部分。

如果你真的想要这种行为,你必须自己提供。一种方法是将生成器/迭代器包装在提供缺少功能的类中。

请注意,此代码仅评估为 False    StopIteration 已被提出。

作为奖励,此代码适用于蟒蛇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

如果你还想要前瞻(或偷看)行为,这个代码将完成这个诀窍(它评估为 False  之前  StopIteration 被提出):

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

请记住,当底层迭代器/生成器的时间与其生成的值相关时,查看行为是不合适的。

还要记住,第三方代码以及可能的stdlib可能依赖于始终进行求值的迭代器/生成器 True。如果你想偷看没有布尔,删除 __nonzero__ 和 __bool__ 方法。


5
2017-11-02 05:41



有趣的问答,可以利用 inspect.getgeneratorstate() 为了这? - Chris_Rands


“空事物”自动不是迭代器。容器可以是空的,也可以不是,并且可以在容器上获取​​迭代器,但这些迭代器在耗尽时不会出现错误。

迭代器不会变得虚假的一个很好的例子是 sys.stdin。制作的问题 sys.stdin 当它到达输入的末尾时,如果没有尝试消耗它的输入,就无法真正知道你是否已经到达这样一个流的末尾。想要迭代器是假的主要原因是'偷看'看看下一个项是否有效;但对于 sys.stdin,这显然不实用。

这是另一个例子

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

没有办法知道这个生成器是否会返回任何更多的数字而不做一堆工作,你实际上必须找出下一个值是什么。

解决方案是从迭代器中获取下一个值。如果它是空的,你的循环将退出,或者你会得到一个 StopIteration 例如,如果你不在循环中。


4
2017-11-02 06:06



一个空的迭代器仍然是一个迭代器,而偷看则是 主要 原因,不是 只要 原因。而且,它使用起来很笨重 StopIteration 在布尔测试中。 - Ethan Furman
迭代器不能为空;只有容器可以是空的。迭代器是一个“位置”,如“开头”或“第23行第5列”。 - SingleNegationElimination
我得考虑一下。尽管如此,我的代码可以被认为是 True 如果不是'过去', False 除此以外。 - Ethan Furman
当你读 sys.stdin 什么时候 ctrl-D 已被按下,迭代器将提升 StopException 好像它是空的,但是流不关闭,实际上,它有效地“重新打开”,允许进一步阅读。所有迭代器类型都没有一致的空虚概念。唯一的功能是始终可用 next(),这可能会提高 StopIteraton 或不。 - SingleNegationElimination
你说'StopException' - 你的意思是 StopIteration?一旦 StopIteration 已经提出,应该继续提高 next() 调用或迭代器 被打破 - Ethan Furman