在文字上使用in
运算符时,该字面值是列表,集合还是元组最惯用的是什么?
e.g。
for x in {'foo', 'bar', 'baz'}:
doSomething(x)
...
if val in {1, 2, 3}:
doSomethingElse(val)
我没有看到列表中的任何好处,但元组不可避免地意味着可能被高效的解释器提升或重用。对于if
,如果重复使用,则会带来效率优势。
哪个是最惯用的,哪个在cpython中效果最好?
答案 0 :(得分:7)
Python提供了一个反汇编程序,因此您通常只需检查字节码:
In [4]: def checktup():
...: for _ in range(10):
...: if val in (1, 2, 3):
...: print("foo")
...:
In [5]: def checkset():
...: for _ in range(10):
...: if val in {1, 2, 3}:
...: print("foo")
...:
In [6]: import dis
对于tuple
字面值:
In [7]: dis.dis(checktup)
2 0 SETUP_LOOP 32 (to 34)
2 LOAD_GLOBAL 0 (range)
4 LOAD_CONST 1 (10)
6 CALL_FUNCTION 1
8 GET_ITER
>> 10 FOR_ITER 20 (to 32)
12 STORE_FAST 0 (_)
3 14 LOAD_GLOBAL 1 (val)
16 LOAD_CONST 6 ((1, 2, 3))
18 COMPARE_OP 6 (in)
20 POP_JUMP_IF_FALSE 10
4 22 LOAD_GLOBAL 2 (print)
24 LOAD_CONST 5 ('foo')
26 CALL_FUNCTION 1
28 POP_TOP
30 JUMP_ABSOLUTE 10
>> 32 POP_BLOCK
>> 34 LOAD_CONST 0 (None)
36 RETURN_VALUE
对于set
- 文字:
In [8]: dis.dis(checkset)
2 0 SETUP_LOOP 32 (to 34)
2 LOAD_GLOBAL 0 (range)
4 LOAD_CONST 1 (10)
6 CALL_FUNCTION 1
8 GET_ITER
>> 10 FOR_ITER 20 (to 32)
12 STORE_FAST 0 (_)
3 14 LOAD_GLOBAL 1 (val)
16 LOAD_CONST 6 (frozenset({1, 2, 3}))
18 COMPARE_OP 6 (in)
20 POP_JUMP_IF_FALSE 10
4 22 LOAD_GLOBAL 2 (print)
24 LOAD_CONST 5 ('foo')
26 CALL_FUNCTION 1
28 POP_TOP
30 JUMP_ABSOLUTE 10
>> 32 POP_BLOCK
>> 34 LOAD_CONST 0 (None)
36 RETURN_VALUE
您会注意到,在这两种情况下,函数都会LOAD_CONST
,即两次都已优化。即使更好,在set
文字的情况下,编译器也保存了frozenset
,在构造函数期间,窥孔优化器已经设法弄清楚了可以成为set
的不可变等价物。
注意,在Python 2上,编译器每次构建一个集合!:
In [1]: import dis
In [2]: def checkset():
...: for _ in range(10):
...: if val in {1, 2, 3}:
...: print("foo")
...:
In [3]: dis.dis(checkset)
2 0 SETUP_LOOP 49 (to 52)
3 LOAD_GLOBAL 0 (range)
6 LOAD_CONST 1 (10)
9 CALL_FUNCTION 1
12 GET_ITER
>> 13 FOR_ITER 35 (to 51)
16 STORE_FAST 0 (_)
3 19 LOAD_GLOBAL 1 (val)
22 LOAD_CONST 2 (1)
25 LOAD_CONST 3 (2)
28 LOAD_CONST 4 (3)
31 BUILD_SET 3
34 COMPARE_OP 6 (in)
37 POP_JUMP_IF_FALSE 13
4 40 LOAD_CONST 5 ('foo')
43 PRINT_ITEM
44 PRINT_NEWLINE
45 JUMP_ABSOLUTE 13
48 JUMP_ABSOLUTE 13
>> 51 POP_BLOCK
>> 52 LOAD_CONST 0 (None)
55 RETURN_VALUE
答案 1 :(得分:0)
IMO,基本上没有"惯用语"如问题所示使用文字值。这些价值观看起来像#34;魔术数字"对我来说。使用文字来表现"表现"可能是错误的,因为它牺牲了边际收益的可读性。在性能真正重要的情况下,使用文字不太可能有用,并且无论如何都有更好的选择。
我认为惯用的事情是将这些值存储在全局变量或类变量中,特别是如果你在多个地方使用它们(但即使你不是这样)。这提供了一些关于价值目的是什么的文档,并使其更容易更新。然后,您可以在函数/方法定义中记住这些值,以便在必要时提高性能。
关于哪种类型的数据结构最合适,这取决于您的程序执行的操作以及它如何使用数据。例如,订购有关系吗?使用if x in y
,它不会赢,但您可能正在使用for
和if
中的数据。没有背景,很难说出最佳选择是什么。
我认为这是一个可读,可扩展且高效的示例。在函数定义中记住全局ITEMS
会使查找更快,因为items
位于函数的本地名称空间中。如果您查看反汇编代码,则会看到items
通过LOAD_FAST
而不是LOAD_GLOBAL
进行查找。这种方法还避免制作项目列表的多个副本,如果它足够大可能是相关的(尽管如果它足够大,你可能不会尝试内联它)。就个人而言,我不会在当时的大多数中进行这些优化,但在某些情况下它们会很有用。
# In real code, this would have a domain-specific name instead of the
# generic `ITEMS`.
ITEMS = {'a', 'b', 'c'}
def filter_in_items(values, items=ITEMS):
matching_items = []
for value in values:
if value in items:
matching_items.append(value)
return matching_items
def filter_not_in_items(values, items=ITEMS):
non_matching_items = []
for value in values:
if value not in items:
non_matching_items.append(value)
return non_matching_items
print(filter_in_items(('a', 'x'))) # -> ['a']
print(filter_not_in_items(('a', 'x'))) # -> ['x']
import dis
dis.dis(filter_in_items)