in操作符实际调用y.__contains__(x),若未定义则退而使用迭代或__getitem__协议;性能差异源于不同容器__contains__的内部实现:list/tuple为o(n)遍历,str为优化子串搜索,set/dict为o(1)哈希查找。

in操作符本身不“检查对象方法”,它检查的是某个值是否为容器的成员。所谓“性能差异”,本质是不同容器类型实现__contains__的方式不同,而不是in在调用方法时有快慢之分。
in背后真正调用的是什么?
当你写x in y时,Python会按顺序尝试:
- 先调用
y.__contains__(x)(如果定义了) - 若未定义,则退而使用迭代协议:逐个调用
__iter__(),再对每个元素用==比较 - 若连
__iter__都没有,还会尝试__getitem__(如旧式序列)
所以性能瓶颈不在“调用方法”这个动作本身,而在__contains__内部怎么查——是遍历?哈希?还是子串扫描?
常见容器的查找机制与耗时特征
不同内置类型对__contains__的实现策略直接决定in的速度:
- list / tuple:线性遍历,最坏O(n)。10万元素里找末尾项,得比10万次
- str:优化过的子串搜索(Boyer-Moore等),平均O(n),但常数较大;短子串很快,长子串可能比set慢得多
- set / frozenset:哈希表查找,平均O(1),不随数据量增长明显变慢
-
dict:同样哈希查找键,O(1);但
val in d.values()会退化为O(n),因为值没索引
自定义类的性能可控点
如果你自己写类并希望in高效,关键不是“加个方法”,而是怎么实现__contains__:
- 若底层用list存储但频繁查成员,应同步维护一个辅助set来加速
__contains__ - 避免在
__contains__里做I/O、网络请求或复杂计算 - 若数据有序,可改用二分查找(如
bisect模块),比纯遍历快,但仍是O(log n),不如哈希
实测建议:别猜,要测
实际性能受数据规模、硬件、Python版本影响。简单验证方法:
- 用
timeit.timeit对比相同数据在list/set中执行in的耗时 - 注意预热:首次运行可能含编译/缓存开销,多跑几次取中位数
- 测试典型场景:比如查存在性 vs 查不存在性(后者在list里更慢)
例如,百万级数据下,999999 in list_range可能要几十毫秒,而999999 in set_range稳定在0.1微秒左右。











