Самый эффективный способ сделать выражение if-elif-elif-else, когда else сделан больше всего?
У меня есть оператор if-elif-elif-else, в котором 99% времени выполняется оператор else:
if something == 'this':
doThis()
elif something == 'that':
doThat()
elif something == 'there':
doThere()
else:
doThisMostOfTheTime()
Эта конструкция выполняется много, но поскольку она проходит каждое условие до того, как оно попадает в другое, у меня возникает ощущение, что это не очень эффективно, не говоря уже о Pythonic. С другой стороны, ему нужно знать, выполнено ли какое-либо из этих условий, поэтому он должен все равно протестировать.
Кто-нибудь знает, если и как это можно сделать более эффективно или это просто лучший способ сделать это?
Ответы
Ответ 1
Код...
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, которая все еще является значительное улучшение исходной конструкции.
Ответ 2
Я бы создал словарь:
options = {'this': doThis,'that' :doThat, 'there':doThere}
Теперь используйте только:
options.get(something, doThisMostOfTheTime)()
Если something
не найден в options
dict, то dict.get
вернет значение по умолчанию doThisMostOfTheTime
Некоторые сравнения времени:
Script:
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
является наиболее эффективным способом:
if key in options:
options[key]()
else:
doSomethingElse()
Ответ 3
Можете ли вы использовать 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
Ответ 4
Это пример 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.