在我的代码中,我有一个列表l
,我正在创建一个列表字典。 (我正在对具有相同key
)的对象进行分组。通过try
语句和if
条件实现它,我在line_profiler中注意到前者看起来效率更高:
Line # Hits Time Per Hit % Time Line Contents
==============================================================
293 44378450 59805020 1.3 16.9 for element in l:
# stuff that compute 'key' from 'element'
302 2234869 2235518 1.0 0.6 try:
303 2234869 82486133 36.9 23.3 d[key].append(element)
304 57358 72499 1.3 0.0 except KeyError:
305 57358 1758248 30.7 0.5 d[key] = [element]
VS
Line # Hits Time Per Hit % Time Line Contents
==============================================================
293 44378450 60185880 1.4 14.0 for element in l:
# stuff that compute 'key' from 'element'
307 2234869 81683512 36.5 19.1 if key in d.keys():
308 2177511 76393595 35.1 17.8 d.get(key).append(element)
309 else:
310 57358 1717679 29.9 0.4 d[key] = [element]
我理解,对于try
,您只会在引发异常时进入except
(因此除了极少数例外情况,因此每次测试条件的总体成本更低) ,甚至Time per hit
对于异常(1.3 +30.7μs)比对于测试条件(36.5μs)更慢。我认为提高异常比检查密钥是否在字典中更加昂贵(in
只测试散列密钥,不是吗?它不是行搜索)。那为什么呢?
答案 0 :(得分:4)
额外的运行时来自.keys()
调用。如果您想阻止额外的通话,并且仍然使用if
和else
,请尝试以下操作:
obj = d.get(key)
if obj:
obj.append(element)
else:
d[key] = [element]
或者,您可以使用defaultdict
在后台执行此操作。例如:
from collections import defaultdict
d = defaultdict(list)
d['123'].append('abc')
答案 1 :(得分:1)
你应该认为,在每次迭代中,如果条件检查,你会花一些时间进行测试。如果你没有引发异常,你的代码会更快地尝试除外,否则需要花费更多的时间来处理异常。
换句话说,如果你确定你的异常是非常的(只在特殊情况下才会发生),你使用try-except会更便宜。否则,如果除了约50%的例外情况外,最好使用if。
(“请求宽恕比允许更容易”)
答案 2 :(得分:1)
try...except
才会变慢。在您的情况下,异常仅占循环迭代的2.5%。
让我们分析下面的四种情况 -
def func1():
l = [1,2,3,4]
d = {}
for e in l:
k = e - 1
try:
d[k].append(e)
except KeyError:
d[k] = [e]
return d
def func2():
l = [1,2,3,4]
d = {}
for e in l:
k = e - 1
if k in d.keys():
d.get(k).append(e)
else:
d[k] = [e]
return d
def func3():
l = [1,2,3,4]
d = {}
for e in l:
k = 1
try:
d[k].append(e)
except KeyError:
d[k] = [e]
return d
def func4():
l = [1,2,3,4]
d = {}
for e in l:
k = 1
if k in d.keys():
d.get(k).append(e)
else:
d[k] = [e]
return d
此时间结果 -
In [7]: %timeit func1()
The slowest run took 4.17 times longer than the fastest. This could mean that an intermediate result is being cached
100000 loops, best of 3: 2.55 µs per loop
In [8]: %timeit func2()
1000000 loops, best of 3: 1.77 µs per loop
In [10]: %timeit func3()
The slowest run took 4.34 times longer than the fastest. This could mean that an intermediate result is being cached
1000000 loops, best of 3: 2.01 µs per loop
In [11]: %timeit func4()
The slowest run took 6.83 times longer than the fastest. This could mean that an intermediate result is being cached
100000 loops, best of 3: 2.4 µs per loop
在func1()
和func2()
的情况下,每个元素都会进入一个单独的列表,因此对于每个键,try..except
块会引发并捕获异常。在这种情况下,func2()
会更快。
在func3()
和func4()
的情况下,异常只抛出一次,因此异常的开销只发生一次,而对每个密钥仍然检查条件(即使它的在这种情况下,try..except
更快。
我猜你的情况可能会发生类似的情况,其中同一个密钥被多次计算,因此try..except
块的速度更快。您可以查看列表中有多少实际元素与字典中有多少个键,以确定是否可能是原因。
假设hits
列是特定行执行的次数,您可以看到,行 -
d[key].append(element)
执行了2234869次,而异常只提出了 - 57358次,这只是元素总数的2.56%。