我尝试比较dict和if-else,如下所示更快。
d = {
str : lambda x: "'%s'" % str(x),
int : lambda x: str(x),
float: lambda x: str(x),
}
items = ['a', 'b', 'c', 1, 2, 3, 4, 5, 1.0]
def use_dict():
r = []
for i in items:
r.append(d[type(i)](i))
return r
def use_if():
r = []
for i in items:
if isinstance(i, str):
r.append("'%s'" % str(i))
elif isinstance(i, (int, float)):
r.append(str(i))
return r
if __name__ == '__main__':
from timeit import timeit
print 'use_dict:', timeit(use_dict)
# -> use_dict: 9.21109666657
print 'use_if :', timeit(use_if)
# -> use_if : 10.9568739652
我发现dict比if-else快。这意味着当我想写一个switch语句时,dict是更好的解决方案。但我怀疑为什么dict更快?任何人都可以解释它。感谢。
答案 0 :(得分:3)
如果您想了解代码的执行方式,请查看dis模块。
一个简单的例子......
import dis
# Here are the things we might want to do
def do_something_a():
print 'I did a'
def do_something_b():
print 'I did b'
def do_something_c():
print 'I did c'
# Case 1
def f1(x):
if x == 1:
do_something_a()
elif x == 2:
do_something_b()
elif x == 3:
do_something_c()
# Case 2
FUNC_MAP = {1: do_something_a, 2: do_something_b, 3: do_something_c}
def f2(x):
FUNC_MAP[x]()
# Show how the functions execute
print 'Case 1'
dis.dis(f1)
print '\n\nCase 2'
dis.dis(f2)
...输出......
Case 1
18 0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (1)
6 COMPARE_OP 2 (==)
9 POP_JUMP_IF_FALSE 22
19 12 LOAD_GLOBAL 0 (do_something_a)
15 CALL_FUNCTION 0
18 POP_TOP
19 JUMP_FORWARD 44 (to 66)
20 >> 22 LOAD_FAST 0 (x)
25 LOAD_CONST 2 (2)
28 COMPARE_OP 2 (==)
31 POP_JUMP_IF_FALSE 44
21 34 LOAD_GLOBAL 1 (do_something_b)
37 CALL_FUNCTION 0
40 POP_TOP
41 JUMP_FORWARD 22 (to 66)
22 >> 44 LOAD_FAST 0 (x)
47 LOAD_CONST 3 (3)
50 COMPARE_OP 2 (==)
53 POP_JUMP_IF_FALSE 66
23 56 LOAD_GLOBAL 2 (do_something_c)
59 CALL_FUNCTION 0
62 POP_TOP
63 JUMP_FORWARD 0 (to 66)
>> 66 LOAD_CONST 0 (None)
69 RETURN_VALUE
Case 2
29 0 LOAD_GLOBAL 0 (FUNC_MAP)
3 LOAD_FAST 0 (x)
6 BINARY_SUBSCR
7 CALL_FUNCTION 0
10 POP_TOP
11 LOAD_CONST 0 (None)
14 RETURN_VALUE
...所以很容易看出哪个函数必须执行最多的指令。
至于哪个实际上更快,这是你必须通过分析代码来检查的。
if / elif / else结构逐个比较它给一个可能值序列的密钥,直到它在一些if语句的条件下找到匹配,然后从if内部读取它应该执行的内容块。这可能需要很长时间,因为必须为每次查找进行如此多的检查(n/2
平均n
个可能的值)。
if语句序列比switch语句更难以优化的原因是条件检查(C ++中parens里面的内容)可能会改变下一次检查中涉及的某个变量的状态,所以你必须按顺序完成它们。对switch语句的限制消除了这种可能性,因此顺序无关紧要(我认为)。
Python词典are implemented as hash tables。这个想法是这样的:如果你可以处理任意大数并拥有无限的RAM,你可以创建一个庞大的函数指针数组,只需将你的查找值转换为整数并将其作为索引。查找几乎是即时的。
当然,你不能这样做,但你可以创建一个具有一定可管理长度的数组,将查找值传递给hash function(根据查找值生成一些整数),然后%您的结果与数组的长度,以获得该数组范围内的索引。这样,查找需要花费尽可能多的时间来调用哈希函数一次,获取模数,并跳转到索引。如果不同可能的查找值的数量足够大,则与那些n / 2条件检查相比,散列函数的开销变得可以忽略不计。
(实际上,由于许多不同的查找值将不可避免地映射到相同的索引,因此它并不那么简单。您必须检查并解决可能的冲突,这可以通过多种方式完成。仍然是,它如上所述。)
答案 1 :(得分:2)
我猜这是因为items
中的元素不是字符串的情况,if..elif
解决方案实际上最终会调用isinstance()
函数两次,这可能会增加成本。而python中的函数调用成本很高。
尽管您的dict
解决方案在所有情况下都只调用type()
一次。
例如,我将items
的列表转换为所有字符串,if..elif
解决方案的时间更快 -
d = {
str : lambda x: "'%s'" % str(x),
int : lambda x: str(x),
float: lambda x: str(x),
}
items1 = ['a', 'b', 'c', 1, 2, 3, 4, 5, 1.0]
items = ['a','b','c','d','e','f','g','h','i','j']
def use_dict():
r = []
for i in items:
r.append(d[type(i)](i))
return r
def use_if():
r = []
for i in items:
if isinstance(i, str):
r.append("'%s'" % str(i))
elif isinstance(i, (int, float)):
r.append(str(i))
return r
if __name__ == '__main__':
from timeit import timeit
print('use_dict:', timeit(use_dict))
print('use_if :', timeit(use_if))
在所有字符串上运行它的结果 -
C:\Users\anandsk>python a.py
use_dict: 7.891252114975529
use_if : 6.850442551614534