Самый эффективный способ сделать утверждение if-elif-elif-else, когда остальное сделано больше всего?


У меня есть оператор in if-elif-elif-else, в котором в 99% случаев выполняется оператор else:

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

эта конструкция делается большое, но поскольку она идет за каждое условие, прежде чем она попадает еще мне кажется, что это не очень эффективно, не говоря уже весть. С другой стороны, он должен знать, выполняются ли какие-либо из этих условий, поэтому он должен проверить его в любом случае.

кто-нибудь знает, если и как это можно сделать больше эффективно или это просто лучший способ сделать это?

4 83

4 ответа:

код...

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]

...и обратите внимание на количество процессорного времени, которое они используют...

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

...использование времени пользователя от time(1).

опция #4 имеет дополнительные затраты памяти на добавление нового элемента для каждого отдельного пропуска ключа, поэтому если вы ожидаете неограниченное количество различных ключевых промахов, я бы пошел с вариантом № 3, который по-прежнему является значительным улучшением исходной конструкции.

я бы создал словарь:

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

теперь использовать только:

options.get(something, doThisMostOfTheTime)()

если something не найден в options дикт потом 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 is самый эффективный способ здесь:

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

Это пример 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]

это выход, но не может быть наиболее подходящие для Python способ сделать это, потому что это менее читаемо, для которых не владеет в Python.