"in" 在生成器中的定义

6
为什么生成器(generators)中定义了in运算符?
>>> def foo():
...     yield 42
... 
>>> 
>>> f = foo()
>>> 10 in f
False

有哪些可能的用途?

我知道range(...)对象有一个定义了__contains__函数,因此我们可以执行以下操作:

>>> r = range(10)
>>> 4 in r
True
>>> r.__contains__
<method-wrapper '__contains__' of range object at 0x7f82bd51cc00>

但是上面的f没有__contains__方法。

1
Python开发人员使用PEPs记录他们的设计决策(和讨论),您可以在线浏览。我猜生成器是在早期的Python 2中添加的,比如2.4之类的版本,作为一个开始。 - Ulrich Eckhardt
2
你说的“为什么”是什么意思?似乎在生成器上使用in只是尝试消耗它,直到找到元素并返回True,或者生成器用完了,此时返回False。这似乎是从生成器中获得非常直观、预期的行为。 - Omer Tuchfeld
2个回答

4
"What are the possible use cases?" 用于检查生成器是否会产生某个值。
Dunder方法作为与特定语法相关联的“钩子”。__contains__并不是一种一对一映射到x in y的方法。语言最终定义了这些运算符的语义。
成员测试文档中,我们可以看到有几种评估x in y的方法,取决于涉及的对象的各种属性。我已经突出了生成器对象的相关属性,它们没有定义__contains__但是可迭代,即它们定义了一个__iter__方法:
运算符 in 和 not in 用于测试成员资格。表达式 x in s 的值为 True,如果 x 是序列 s 的成员,否则返回 False。表达式 x not in s 返回其反值。所有内置的序列和集合类型都支持这种成员资格测试,包括字典,其中 in 操作符检查字典是否包含指定的键。对于诸如列表、元组、集合、冻结集、字典或 collections.deque 等容器类型,表达式 x in y 等效于 any(x is e or x == e for e in y)
对于字符串和字节类型,当且仅当 x 是 y 的子串时,表达式 x in y 的值为 True。等价的测试是 y.find(x) != -1。空字符串始终被认为是任何其他字符串的子串,因此 "" in "abc" 将返回 True。
对于定义了 __contains__() 方法的用户定义类,如果 y.__contains__(x) 返回 true 值,则 x in y 返回 True,否则返回 False。
对于未定义 contains() 但定义了 __iter__() 的用户定义类,如果在迭代 y 时产生了某个值 z,使得表达式 x is z or x == z 为真,则 x in y 返回 True。如果在迭代过程中引发异常,则好像 in 引发了该异常。
最后,尝试旧式迭代协议:如果一个类定义了 __getitem__(),则当且仅当存在非负整数索引 i 使得 x is y[i] or x == y[i],且没有更低的整数索引引发 IndexError 异常时,x in y 的值为 True。如果引发任何其他异常,则好像 in 引发了该异常。
运算符 not in 的定义与 in 相反。

总之,x in y 的定义适用于以下对象:

  1. 字符串或字节,其定义为子字符串关系。
  2. 定义了__contains__的类型。
  3. 迭代器类型,即定义了__iter__的类型。
  4. 旧式迭代协议(依赖于__getitem__)。

生成器属于第三种情况。

更广泛的观点是,除非你真正理解它们在做什么,否则你不应该直接使用dunder方法。即使你真的理解了,最好还是避免使用。

通常,通过使用类似以下内容来增加可信度或简洁性并不值得:

x.__lt__(y)

代替:

x < y

你至少应该明白,这种情况可能发生:

>>> (1).__lt__(3.)
NotImplemented
>>>

如果你只是天真地做一些像 filter((1).__lt__, iterable) 这样的事情,那么你可能有一个错误


生成器会归为第三类,对吗? - chepner

2
生成器是可迭代的,因此具有一个 .__iter__ 方法,可用于检查成员资格。关于这个行为的描述在成员测试操作文档中。
对于未定义 __contains__() 但定义了 __iter__() 的用户定义类,如果在迭代 y 时产生一些值 z,其中表达式 x is z or x == z 为真,则 x in y 为 True。如果在迭代期间引发异常,则好像 in 引发了该异常。
重点在这里! 这将弹出整个生成器,你的示例中的 42 并不包括被测试的值 10
>>> def foo():
...     yield 5
...     yield 10
...
>>> f = foo()
>>> 10 in f
True
>>> 10 in f
False

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接