哪种方法更好?使用元组,如:
if number in (1, 2):
或列表,例如:
if number in [1, 2]:
建议将哪一种用于此类用途以及为什么(逻辑和性能都明智)?
答案 0 :(得分:34)
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
这里第二个字节码在{em> one 步骤中将(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
source。对于其他 Python实现,您希望改为使用不可变对象。
也就是说,使用Python 3.2及更高版本时,最佳选项是使用 set literal :
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
或frozenset
的成本几乎可以保证比使用元组的成本更高。测试
设置成员资格测试是O(1)和快速;针对元组的测试是O(n)最坏的情况。虽然再次测试集合必须计算散列(更高的常量成本,缓存为不可变的tupes),但对于除第一个元素之外的元组进行测试的成本总是会更高。所以平均来说,集合很快就会更快:
>>> 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