哪种方法更好?使用元组,例如:
if number in (1, 2):
或清单,例如:
if number in [1, 2]:
建议将哪一种用于此类用途,以及为什么使用(出于逻辑和性能考虑)?
CPython解释器 将第二种形式替换为第一种形式 。
这是因为从常量加载元组是一个操作,但是列表将是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)元组为常数,在 一个 步骤。将其与创建成员资格测试中未使用的列表对象进行比较:
(1, 2)
>>> 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资料来源。然后,对于 其他 Python实现,您要坚持使用不可变的对象。
Python/peephole.c
也就是说,在使用Python 3.2及更高版本时, 最好的 选择是使用 set文字 :
if number in {1, 2}:
因为窥视孔优化器将用frozenset()对象代替它,并且针对集合的成员资格测试是O(1)常数操作:
frozenset()
>>> 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优化器无法识别此选项,并且几乎可以肯定构建aset或frozenset从内容构建的成本比使用元组进行测试的成本更高。
set
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