Python 解释器将第二种形式替换为第一种形式.
这是因为从常量加载元组是一项操作,但列表将是 3 项操作;加载两个整数内容并构建一个新的列表对象。
因为您使用的是无法通过其他方式访问的列表文字,所以它被替换为元组:
>>> import dis
>>> dis.dis(compile('number in [1, 2]', '<stdin>', 'eval'))
1 0 LOAD_NAME 0 (number)
3 LOAD_CONST 2 ((1, 2))
6 COMPARE_OP 6 (in)
9 RETURN_VALUE
这里第二个字节码加载一个(1, 2)
元组作为常量,在one步。将其与创建未在成员资格测试中使用的列表对象进行比较:
>>> dis.dis(compile('[1, 2]', '<stdin>', 'eval'))
1 0 LOAD_CONST 0 (1)
3 LOAD_CONST 1 (2)
6 BUILD_LIST 2
9 RETURN_VALUE
这里对于长度为 N 的列表对象需要 N+1 个步骤。
这种替换是 CPython 特定的窥孔优化;看到Python/peephole.c source. For other那么,Python 实现中,您应该坚持使用不可变对象。
也就是说,best使用 Python 3.2 及更高版本时的选项是使用设置文字:
if number in {1, 2}:
因为窥视孔优化器会将其替换为frozenset()
针对集合的对象和成员资格测试是 O(1) 常量运算:
>>> dis.dis(compile('number in {1, 2}', '<stdin>', 'eval'))
1 0 LOAD_NAME 0 (number)
3 LOAD_CONST 2 (frozenset({1, 2}))
6 COMPARE_OP 6 (in)
9 RETURN_VALUE
此优化添加于Python 3.2但没有向后移植到 Python 2。
因此,Python 2 优化器无法识别此选项以及构建set
or frozenset
几乎可以肯定,从内容中获取比使用元组进行测试的成本更高。
集合成员资格测试是 O(1) 并且速度很快;对元组进行测试的最坏情况是 O(n)。尽管针对集合的测试必须计算哈希值(较高的恒定成本,针对不可变类型进行缓存),但针对元组的测试的成本除了第一个元素之外总是会更高。所以平均而言,集合很容易更快:
>>> import timeit
>>> timeit.timeit('1 in (1, 3, 5)', number=10**7) # best-case for tuples
0.21154764899984002
>>> timeit.timeit('8 in (1, 3, 5)', number=10**7) # worst-case for tuples
0.5670104179880582
>>> timeit.timeit('1 in {1, 3, 5}', number=10**7) # average-case for sets
0.2663505630043801
>>> timeit.timeit('8 in {1, 3, 5}', number=10**7) # worst-case for sets
0.25939063701662235