成员测试使用 in 和 not in 作为操作符。如果 x 是 s 中的一员,x in s 便会返回 True ;否则返回 False。x not in s与 x in s 的真值相反。所有内置序列(sequences)和集合(set)类型都支持成员测试;字典也支持该操作,不过字典将键(key)作为测试对象。对于容器类型(如 list、tuple、set、frozenset、dict 或 collections.deque),表达式 x in y 相当于 any(x is e or x == e for e in y)。
另外,对于 string 和 bytes 类型,仅当 x 是 y 的子串(substring)时,x in y 会返回 True。在这种情况下,成员测试等效于 y.find(x) != -1。由于空字符串被视作任何字符的子串,所以 "" in "abd" 将会返回 True。
对某个对象进行成员测试时,依据对象所持方法的不同,可分为如下三种情况:
第一种情况是在用户(user-defined)定义类中已定义了 __contains__() 方法。那么在进行成员测试时,便会调用该方法。x in y 与 y.__contains__(x) 的真值相同,也就是说如果后者返回 True,前者也会返回 True。
class Fib(object):
def __reset(self):
self.a = 1
self.b = 1
def __init__(self):
self.__reset()
def __contains__(self, item):
print("调用 __contains__")
self.__reset()
while True:
self.a, self.b = self.b, self.a + self.b
if item == self.a:
return True
elif item < self.a:
return False
def __reset(self):
self.a = 1
self.b = 1
a_fib = Fib()
print(3 in a_fib)
输出:
调用 __contains__
True
第二种情况是在用户定义类中并没有定义 __contains__(),但定义了 __iter__()。那么在执行 x in y 时,便会通过 __iter__() 对 y 进行迭代,如果在迭代过程中产生了与 x 相等的值,就会返回 True。在迭代过程中,只要确定存在与 x 相等的值,便会停止迭代,不会执行完全部的迭代过程。如果在迭代过程中引发了异常,则如同在成员测试中引发的异常一样。
class Fib(object):
def __reset(self):
self.a = 1
self.b = 1
def __init__(self):
self.__reset()
def __iter__(self):
"""测试该方法前,请删除__contains__"""
print("调用 __iter__()")
self.__reset()
return self
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
print("停止迭代")
raise StopIteration()
return self.a # 返回下一个值
a_fib = Fib()
print(3 in a_fib)
输出:
调用 __iter__()
True
第三种情况是 __contains__()和 __iter__() 都没有定义,但定义了 __getitem__()。那么在执行 x in y 时,便会通过 __getitem__() 对 y 中各项逐一索引,仅当存在 x == y[i]( i 为非负整数)时,会返回 True。如果在迭代过程中引发了异常,则如同在成员测试中引发的异常一样。
tips:定义了 __getitem__() ,但没有定义 __iter__()时,会尝试使用旧式迭代协议,也就是通过 __getitem__() 逐一索引各个元素的方式,进行迭代。另外,我没有看懂文档中的这句话:"all lower integer indices do not raise IndexError exception. "
class Fib(object):
def __reset(self):
self.a = 1
self.b = 1
def __init__(self):
self.__reset()
def __getitem__(self, item):
"""测试该方法前,请删除__contains__和__iter__"""
self.__reset()
print("调用 __getitem__")
for x in range(item):
self.a, self.b = self.b, self.a + self.b
if self.a > 100000: # 退出循环的条件
print("停止迭代")
raise StopIteration()
return self.a
a_fib = Fib()
print(3 in a_fib)
输出:
调用 __getitem__
True
小结:综合以上三种情况,可见成员测试会优先使用 __contains__,如果没有定义该方法才会使用 __iter__(),如果前面两个方法都未定义才会尝试 __getitem__。