当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)()
…看起来应该更快,但实际上它比if
… elif
… else
构造更慢,因为它必须调用一个函数,这可能是一个严格的循环中的性能开销。
考虑这些例子…
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的人可读性差。