惯用Python:文字中的`in`关键字

时间:2017-11-09 19:40:09

标签: python idiomatic

在文字上使用in运算符时,该字面值是列表,集合还是元组最惯用的是什么?

e.g。

for x in {'foo', 'bar', 'baz'}:
    doSomething(x)

...

if val in {1, 2, 3}:
    doSomethingElse(val)

我没有看到列表中的任何好处,但元组不可避免地意味着可能被高效的解释器提升或重用。对于if,如果重复使用,则会带来效率优势。

哪个是最惯用的,哪个在cpython中效果最好?

2 个答案:

答案 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,它不会赢,但您可能正在使用forif中的数据。没有背景,很难说出最佳选择是什么。

我认为这是一个可读,可扩展且高效的示例。在函数定义中记住全局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)