`in` 为生成器定义

`in` defined for generator

为什么要为生成器定义 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__方法。

生成器是可迭代的,因此有一个 .__iter__ 方法,可用于检查成员资格

Expressions docs on Membership test operations

中描述了其行为方式

For user-defined classes which do not define __contains__() but do define __iter__(), x in y is True if some value z, for which the expression x is z or x == z is true, is produced while iterating over y. If an exception is raised during the iteration, it is as if in raised that exception.

强调我的!

这会弹出整个生成器,而您使用 42 的示例根本不包括 10

的测试值
>>> def foo():
...     yield 5
...     yield 10
...
>>> f = foo()
>>> 10 in f
True
>>> 10 in f
False

“可能的用例是什么?”检查生成器是否会产生一些值。

Dunder 方法作为 hooks 用于与它们关联的特定语法。 __contains__ 不是某种到 x in y 的一对一映射。语言最终定义了这些运算符的语义。

documentation of membership testing 中,我们看到 x in y 有多种计算方法,具体取决于所涉及对象的各种属性。我已经为生成器对象突出了相关的一个,它没有定义 __contains__ 但是是可迭代的,即它们定义了一个 __iter__ 方法:

The operators in and not in test for membership. x in s evaluates to True if x is a member of s, and False otherwise. x not in s returns the negation of x in s. All built-in sequences and set types support this as well as dictionary, for which in tests whether the dictionary has a given key. For container types such as list, tuple, set, frozenset, dict, or collections.deque, the expression x in y is equivalent to any(x is e or x == e for e in y).

For the string and bytes types, x in y is True if and only if x is a substring of y. An equivalent test is y.find(x) != -1. Empty strings are always considered to be a substring of any other string, so "" in "abc" will return True.

For user-defined classes which define the __contains__() method, x in y returns True if y.__contains__(x) returns a true value, and False otherwise.

For user-defined classes which do not define contains() but do define __iter__(), x in y is True if some value z, for which the expression x is z or x == z is true, is produced while iterating over y. If an exception is raised during the iteration, it is as if in raised that exception.

Lastly, the old-style iteration protocol is tried: if a class defines __getitem__(), x in y is True if and only if there is a non-negative integer index i such that x is y[i] or x == y[i], and no lower integer index raises the IndexError exception. (If any other exception is raised, it is as if in raised that exception).

The operator not in is defined to have the inverse truth value of in.

总而言之,x in y 将为以下对象定义:

  1. 是字符串还是字节,定义为子串关系
  2. 定义 __contains__
  3. 的类型
  4. 作为迭代器的类型,即定义 __iter__
  5. 旧式迭代协议(依赖于__getitem__

生成器分为 3。

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

通常不值得尝试通过使用具有以下效果的东西来变得可信或简洁:

x.__lt__(y)

而不是:

x < y

你至少应该明白,这可能会发生:

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

如果您只是天真地做类似 filter((1).__lt__, iterable) 的事情,那么您可能遇到了错误