当else完成的时候,最有效的方法是做一个if-elif-elif-else语句?

我有一个if-elif-elif-else语句,其中99%的时间,else语句被执行:

if something == 'this': doThis() elif something == 'that': doThat() elif something == 'there': doThere() else: doThisMostOfTheTime() 

这个构造做了很多 ,但是因为它在碰到其他东西之前就已经完成 ,所以我觉得这个效率不是很高,更不用说Pythonic了。 另一方面,它确实需要知道这些条件是否满足,所以它应该testing它。

有没有人知道是否以及如何更有效地做到这一点,或者这只是最好的办法吗?

代码…

 options.get(something, doThisMostOfTheTime)() 

…看起来应该更快,但实际上它比ifelifelse构造更慢,因为它必须调用一个函数,这可能是一个严格的循环中的性能开销。

考虑这些例子…

1.py

 something = 'something' for i in xrange(1000000): if something == 'this': the_thing = 1 elif something == 'that': the_thing = 2 elif something == 'there': the_thing = 3 else: the_thing = 4 

2.py

 something = 'something' options = {'this': 1, 'that': 2, 'there': 3} for i in xrange(1000000): the_thing = options.get(something, 4) 

3.py

 something = 'something' options = {'this': 1, 'that': 2, 'there': 3} for i in xrange(1000000): if something in options: the_thing = options[something] else: the_thing = 4 

4.py

 from collections import defaultdict something = 'something' options = defaultdict(lambda: 4, {'this': 1, 'that': 2, 'there': 3}) for i in xrange(1000000): the_thing = options[something] 

…并记下它们使用的CPU时间量

 1.py: 160ms 2.py: 170ms 3.py: 110ms 4.py: 100ms 

…使用从time(1)的用户时间。

选项#4确实有额外的内存开销,为每个不同的键未命中添加一个新的项目,所以如果你期待无数个不同的键未命中,我会select#3,这仍然是一个重大的改进原始的构造。

我会创build一个字典:

 options = {'this': doThis,'that' :doThat, 'there':doThere} 

现在只用:

 options.get(something, doThisMostOfTheTime)() 

如果在options字典中没有find,那么dict.get将返回默认值doThisMostOfTheTime

一些时间比较:

脚本:

 from random import shuffle def doThis():pass def doThat():pass def doThere():pass def doSomethingElse():pass options = {'this':doThis, 'that':doThat, 'there':doThere} lis = range(10**4) + options.keys()*100 shuffle(lis) def get(): for x in lis: options.get(x, doSomethingElse)() def key_in_dic(): for x in lis: if x in options: options[x]() else: doSomethingElse() def if_else(): for x in lis: if x == 'this': doThis() elif x == 'that': doThat() elif x == 'there': doThere() else: doSomethingElse() 

结果:

 >>> from so import * >>> %timeit get() 100 loops, best of 3: 5.06 ms per loop >>> %timeit key_in_dic() 100 loops, best of 3: 3.55 ms per loop >>> %timeit if_else() 100 loops, best of 3: 6.42 ms per loop 

对于10**5不存在的密钥和100个有效密钥::

 >>> %timeit get() 10 loops, best of 3: 84.4 ms per loop >>> %timeit key_in_dic() 10 loops, best of 3: 50.4 ms per loop >>> %timeit if_else() 10 loops, best of 3: 104 ms per loop 

所以,对于一个正常的字典来说, key in options这里使用key in options检查key in options是最有效的方法:

 if key in options: options[key]() else: doSomethingElse() 

你能使用pypy吗?

保持你原来的代码,但在pypy上运行它给我一个50倍的速度。

CPython的:

 matt$ python Python 2.6.8 (unknown, Nov 26 2012, 10:25:03) [GCC 4.2.1 Compatible Apple Clang 3.0 (tags/Apple/clang-211.12)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> >>> from timeit import timeit >>> timeit(""" ... if something == 'this': pass ... elif something == 'that': pass ... elif something == 'there': pass ... else: pass ... """, "something='foo'", number=10000000) 1.728302001953125 

Pypy:

 matt$ pypy Python 2.7.3 (daf4a1b651e0, Dec 07 2012, 23:00:16) [PyPy 2.0.0-beta1 with GCC 4.2.1] on darwin Type "help", "copyright", "credits" or "license" for more information. And now for something completely different: ``a 10th of forever is 1h45'' >>>> >>>> from timeit import timeit >>>> timeit(""" .... if something == 'this': pass .... elif something == 'that': pass .... elif something == 'there': pass .... else: pass .... """, "something='foo'", number=10000000) 0.03306388854980469 

这是dynamic条件翻译成字典的例子。

 selector = {lambda d: datetime(2014, 12, 31) >= d : 'before2015', lambda d: datetime(2015, 1, 1) <= d < datetime(2016, 1, 1): 'year2015', lambda d: datetime(2016, 1, 1) <= d < datetime(2016, 12, 31): 'year2016'} def select_by_date(date, selector=selector): selected = [selector[x] for x in selector if x(date)] or ['after2016'] return selected[0] 

这是一种方式,但可能不是Python的最Python的方式,因为不太熟悉Python的人可读性差。