操作符详解之成员测试(Python)
转载请注明出处:https://www.jianshu.com/u/5e6f798c903a
成员测试使用 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 raiseIndexError
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__
。