Python 为什么`试试。。。除了`比`如果`快之外?
在我的代码中,我有一个列表Python 为什么`试试。。。除了`比`如果`快之外?,python,performance,try-catch,python-3.4,Python,Performance,Try Catch,Python 3.4,在我的代码中,我有一个列表l,我正在从中创建一个列表字典。(我正在对具有相同键的对象进行分组。通过一个try语句和一个if条件来实现它,我在line_profiler中注意到前者似乎效率更高: Line # Hits Time Per Hit % Time Line Contents ============================================================== 293 44378450 59805
l
,我正在从中创建一个列表字典。(我正在对具有相同键的对象进行分组。通过一个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:
我知道,使用try
,只有在引发异常时才会进入,
(因此,在很少的异常情况下,它的总体成本低于每次测试条件),但在这里,即使是每次命中的时间,异常(1.3+30.7µs)也比测试条件(36.5µs)慢。我认为引发异常比检查一个键是否在字典中花费更多(
中的只测试散列键,不?这不是行搜索)。那么这是为什么呢?额外的运行时来自.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')
您应该认为,在每一次迭代中,如果条件检查,您就会失去一些测试时间。如果您不引发异常,那么使用try-except,您的代码会更快,否则处理异常会花费更多的时间
换句话说,如果您确信您的异常是异常的(它只发生在异常情况下),那么使用try-except会更便宜。否则,如果您在大约50%的情况下排除了异常,那么最好使用if
(“请求原谅比请求允许更容易”)尝试……除非实际引发的异常数量与执行循环的次数相当,否则
的速度较慢。在您的情况下,异常只会在循环迭代中引发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。您可以查看列表中有多少实际元素,以及字典中有多少键,以确定这是否是原因
假设hits
列是特定行的执行次数,您可以看到,该行-
d[key].append(element)
执行了2234869次,而异常只引发了-57358次,仅占元素总数的2.56%。这是Python 2.x吗?它是Python 3.4。顺便问一下,为什么要投否决票?if
版本必须查找键两次,一次在if
测试中,一次在get
中。如果这是Python 2,它会更慢,因为d.keys()
是一个列表,而不是一个视图,所以查找涉及一个线性搜索,而不是一个基于散列的测试集。Pythonic的方法是使用d=defaultdict(list)
。然后d[key].append(element)
将始终有效。好的,但是如果版本(如果该键存在),您仍要在中进行两次查找。FWIW,关于if
vs的相对速度有一些很好的信息,除了
d[key].append(element)