Python - 缩短 if / for 循环

3 投票
3 回答
2564 浏览
提问于 2025-04-17 05:48

我有几行代码,用来遍历一个列表里的字典,我想把它简化一下。现在的代码运行得很好,但我觉得代码有点多,我想了解一下如何让Python(或者其他语言)的代码更高效。

for d in dev['devices']:
    if d['name'] == devName:
        devFound = True
        break

‘dev’的结构有点复杂,但我关心的数据是:
dev(字典) > devices(列表) > 0-n(字典)

在里面的每个编号字典里,有一个叫‘name’的值(根据其他地方的搜索值会有所不同),这个值需要和用户输入的内容(devName)进行比较。

任何建议都非常感谢!

3 个回答

1

这是对Cédric Julien回答的一个变体,因为在某些(少见的)情况下,它可能会失败:

any(True for d in dev["devices"] if d['name'] == devName)

这里有一个(虽然不常见,但确实可能发生的)例子,说明为什么any(True …)能得到正确的结果,而any(d …)却不行:

>>> class special_dict(dict):
...     def __nonzero__(self):
...         return False  # All special_dict objects are False
...     
>>> dev = {'devices': [special_dict(name="DEVNAME") for _ in xrange(10)]}
>>> any(d for d in dev["devices"] if d['name'] == "DEVNAME")  # Incorrect
False
>>> any(True for d in dev["devices"] if d['name'] == "DEVNAME")  # Correct
True

实际上,special_dict对象的值被认为是False,所以在any()中测试d的真假是没有意义的。不过,使用True是有效的。

附注:时间测试表明,any(True … for … if … == …)这种写法比双重写法any(… == … for …)要快:

python -m timeit -s "dev = {'devices': [{'name': 'BADNAME'} for _ in xrange(100)]}" "any(d['name'] == 'DEVNAME' for d in dev['devices'])"
100000 loops, best of 3: 16.3 usec per loop

python -m timeit -s "dev = {'devices': [{'name': 'BADNAME'} for _ in xrange(100)]}" "any(True for d in dev['devices'] if d['name'] == 'DEVNAME' )"
100000 loops, best of 3: 9.42 usec per loop

原因是第二个生成器最多只返回一个值(True)。通过拆解这两个生成器的Python代码,可以看到这一点:

In [8]: def f(my_list):
   ...:     return any(x == 11 for x in my_list)
In [12]: f.func_code.co_consts[1]
Out[12]: <code object <genexpr> at 0x1041f98b0, file "<ipython-input-8-384ce7986872>", line 2>
In [13]: dis.dis(_)
  2           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                17 (to 23)
              6 STORE_FAST               1 (x)
              9 LOAD_FAST                1 (x)
             12 LOAD_CONST               0 (11)
             15 COMPARE_OP               2 (==)
             18 YIELD_VALUE         
             19 POP_TOP             
             20 JUMP_ABSOLUTE            3
        >>   23 LOAD_CONST               1 (None)
             26 RETURN_VALUE        

这段代码包含了YIELD_VALUEPOP_TOP,相比于本回答的版本,这些操作会消耗额外的时间:

In [14]: def g(my_list):
   ....:     return any(True for x in my_list if x == 11)

In [15]: g.func_code.co_consts[1]
Out[15]: <code object <genexpr> at 0x1041f9630, file "<ipython-input-14-735c68947d80>", line 2>
In [16]: dis.dis(g.func_code.co_consts[1])
  2           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                23 (to 29)
              6 STORE_FAST               1 (x)
              9 LOAD_FAST                1 (x)
             12 LOAD_CONST               0 (11)
             15 COMPARE_OP               2 (==)
             18 POP_JUMP_IF_FALSE        3
             21 LOAD_GLOBAL              0 (True)
             24 YIELD_VALUE         
             25 POP_TOP             
             26 JUMP_ABSOLUTE            3
        >>   29 LOAD_CONST               1 (None)
             32 RETURN_VALUE        
4

你可以试试 any() 这个函数:

any(d for d in dev["devices"] if d['name'] == devName)
7

基本上是一样的,只不过用了一些内置函数和生成器重新写了一遍:

devFound = any(d['name'] == devName for d in dev['devices'])

撰写回答