Python列表 - 理解和多次引用对象

时间:2012-07-02 11:16:58

标签: python list-comprehension

这是一个人为的例子,用于演示在for循环和列表理解中多次引用相同的字典项。首先,for循环:

dict_index_mylists = {0:['a', 'b', 'c'], 1:['b', 'c', 'a'], 2:['c', 'a', 'b']}

# for-loop
myseq = []
for i in [0, 1, 2]:
    interim = dict_index_mylists[i]
    if interim[0] == 'b' or interim[1] == 'c' or interim[2] == 'a':    
        myseq.append(interim)

在for循环中,临时列表从字典对象引用,然后在if条件中多次引用,这可能是有意义的,特别是如果字典非常大和/或存储。然后,'临时'引用可能是不必要的,因为Python字典已针对性能进行了优化。

这是对for循环的列表理解:

# list-comprehension
myseq = [dict_index_mylists[i] for i in [0, 1, 2] if dict_index_mylists[i][0] == 'b' or dict_index_mylists[i][1] == 'c' or dict_index_mylists[i][2] == 'a']

问题是:

一个。 list-comprehension是否对字典项进行了多次引用,还是引用并保留了本地“临时”列表?

湾什么是最佳列表推导表达式,它在同一个字典项上包含多个条件,并且字典非常大?

3 个答案:

答案 0 :(得分:1)

您似乎只询问常见子表达式的优化。在列表理解中,它将多次索引到字典中。 Python是动态的,很难知道像dict_index_mylists[i]这样的操作会产生什么样的副作用,所以CPython只是简单地执行操作。

像PyPy这样的其他实现使用JIT并且可以优化子表达式,但很难确切知道它将提前做什么。

如果您非常关注性能,则需要考虑各种选项以确定哪种方法最佳。

答案 1 :(得分:0)

我不是查看python字节码的专家,但这是我今天早上学习新东西的尝试:

def dostuff():
    myseq = [dict_index_mylists[i] for i in [0, 1, 2] if dict_index_mylists[i][0] == 'b' or dict_index_mylists[i][1] == 'c' or dict_index_mylists[i][2] == 'a']

import dis
dis.dis(dostuff)

如果你查看输出(下面),有4次调用LOAD_GLOBAL,所以它看起来不像python存储临时列表。至于你的第二个问题,你所拥有的可能与你能做的一样好。它并没有你想象的那么糟糕。 dict对象通过散列函数访问项目,因此无论字典大小如何,它们的查找复杂度为O(1)。当然,你总是可以使用timeit并比较两个实现(使用loop和list-comp),然后选择更快的实现。分析(一如既往)是你的朋友。

APENDIX(dis.dis(dostuff)的输出)

5           0 BUILD_LIST               0
            3 DUP_TOP             
            4 STORE_FAST               0 (_[1])
            7 LOAD_CONST               1 (0)
           10 LOAD_CONST               2 (1)
           13 LOAD_CONST               3 (2)
           16 BUILD_LIST               3
           19 GET_ITER            
      >>   20 FOR_ITER                84 (to 107)
           23 STORE_FAST               1 (i)
           26 LOAD_GLOBAL              0 (dict_index_mylists)
           29 LOAD_FAST                1 (i)
           32 BINARY_SUBSCR       
           33 LOAD_CONST               1 (0)
           36 BINARY_SUBSCR       
           37 LOAD_CONST               4 ('b')
           40 COMPARE_OP               2 (==)
           43 JUMP_IF_TRUE            42 (to 88)
           46 POP_TOP             
           47 LOAD_GLOBAL              0 (dict_index_mylists)
           50 LOAD_FAST                1 (i)
           53 BINARY_SUBSCR       
           54 LOAD_CONST               2 (1)
           57 BINARY_SUBSCR       
           58 LOAD_CONST               5 ('c')
           61 COMPARE_OP               2 (==)
           64 JUMP_IF_TRUE            21 (to 88)
           67 POP_TOP             
           68 LOAD_GLOBAL              0 (dict_index_mylists)
           71 LOAD_FAST                1 (i)
           74 BINARY_SUBSCR       
           75 LOAD_CONST               3 (2)
           78 BINARY_SUBSCR       
           79 LOAD_CONST               6 ('a')
           82 COMPARE_OP               2 (==)
           85 JUMP_IF_FALSE           15 (to 103)
      >>   88 POP_TOP             
           89 LOAD_FAST                0 (_[1])
           92 LOAD_GLOBAL              0 (dict_index_mylists)
           95 LOAD_FAST                1 (i)
           98 BINARY_SUBSCR       
           99 LIST_APPEND         
          100 JUMP_ABSOLUTE           20
      >>  103 POP_TOP             
          104 JUMP_ABSOLUTE           20
      >>  107 DELETE_FAST              0 (_[1])
          110 STORE_FAST               2 (myseq)
          113 LOAD_CONST               0 (None)
          116 RETURN_VALUE 

答案 2 :(得分:0)

第一点:没有(期望'myseq')在这里“创建”,既不在forloop也不在listcomp版本的代码中 - 它只是对现有dict项的引用。

现在回答你的问题:list comp版本将进行查找(对每个dict.__getitem__表达式调用dict_index_mylists[i]。每个查找将返回对同一列表的引用你可以通过保留对dict项目的本地引用来避免这些额外的查找,即:

myseq = [
    item for item in (dict_index_mylists[i] for i in (0, 1, 2)) 
    if item[0] == 'b' or item[1] == 'c' or item[2] == 'a'
    ]

但是为了编写listcomp而编写listcomp没有意义。

请注意,如果您不关心原始排序并希望将其应用于整个dict,则使用dict.itervalues()会更简单。

第二个问题,“最优”不是绝对的。你想要优化什么?空间 ?时间 ?可读性?