当else完成时,制作if-elif-elif-else语句的最有效方法是什么?

kra*_*r65 90 python performance if-statement

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

if something == 'this':
    doThis()
elif something == 'that':
    doThat()
elif something == 'there':
    doThere()
else:
    doThisMostOfTheTime()
Run Code Online (Sandbox Code Playgroud)

这个结构已经完成了很多,但是因为它在碰到其他条件之前就已经超过了每个条件,所以我感觉这不是很有效,更不用说Pythonic了.另一方面,它确实需要知道是否满足任何条件,所以无论如何它应该测试它.

有人知道是否以及如何更有效地完成这项工作,或者这只是最好的方法吗?

Aya*_*Aya 91

代码...

options.get(something, doThisMostOfTheTime)()
Run Code Online (Sandbox Code Playgroud)

...看起来应该是快,但它实际上是比慢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
Run Code Online (Sandbox Code Playgroud)

2.py

something = 'something'
options = {'this': 1, 'that': 2, 'there': 3}

for i in xrange(1000000):
    the_thing = options.get(something, 4)
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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]
Run Code Online (Sandbox Code Playgroud)

...并注意他们使用的CPU时间量......

1.py: 160ms
2.py: 170ms
3.py: 110ms
4.py: 100ms
Run Code Online (Sandbox Code Playgroud)

...使用用户时间time(1).

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

  • @Marcin我说`dict.get()`比较慢,这是`2.py`--它们中最慢的. (11认同)
  • python有switch语句吗? (3认同)
  • @nathanhayfield号 (2认同)
  • -1您说使用dict较慢,但实际上计时表明它是第二快的选择。 (2认同)

Ash*_*ary 75

我要创建一个字典:

options = {'this': doThis,'that' :doThat, 'there':doThere}
Run Code Online (Sandbox Code Playgroud)

现在使用:

options.get(something, doThisMostOfTheTime)()
Run Code Online (Sandbox Code Playgroud)

如果somethingoptionsdict中找不到,则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()
Run Code Online (Sandbox Code Playgroud)

结果:

>>> 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
Run Code Online (Sandbox Code Playgroud)

对于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
Run Code Online (Sandbox Code Playgroud)

因此,对于正常的字典,检查密钥使用key in options是最有效的方法:

if key in options:
   options[key]()
else:
   doSomethingElse()
Run Code Online (Sandbox Code Playgroud)

  • 你知道这是否更有效率?我猜它是慢的,因为它正在进行哈希查找,而不是简单的条件检查或三次.问题是关于效率而不是代码的紧凑性. (7认同)
  • @BryanOakley我添加了一些时序比较. (2认同)
  • 实际上,执行 ```try: options[key]() 应该更有效,除了 KeyError: doSomeThingElse()```(因为使用 `if key in options: options[key]()` 你正在搜索字典`key` 两次 (2认同)

foz*_*foz 7

你能用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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)


Art*_*ião 5

下面是一个将动态条件翻译为字典的 if 示例。

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]
Run Code Online (Sandbox Code Playgroud)

这是一种方法,但可能不是最 Pythonic 的方法,因为对于不熟悉 Python 的人来说可读性较差。