elseが最も多く行われたときにif-elif-elif-elseステートメントを作成する最も効率的な方法は?


99

99%の時間でelseステートメントが実行されるin if-elif-elif-elseステートメントがあります。

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

この構成は多く行われますが、他の条件に達する前にすべての条件を通過するため、Pythonicは言うまでもなく、これはあまり効率的ではないと感じています。一方、これらの条件のいずれかが満たされているかどうかを知る必要があるため、とにかくそれをテストする必要があります。

これをより効率的に行うことができるかどうか、またどのように行うことができるか、これが単にそれを行うための最良の方法であるかを誰かが知っていますか?


ことができますsortあなたは上の場合/他...チェーンを実行しているもの、のいずれかの条件が一端であるために一致することを、すべての要素、およびすべての残りは他にあるような?もしそうなら、それがより速く/よりエレガントであるかどうかを見ることができます。ただし、パフォーマンスの問題がない場合は、最適化について心配するには早すぎます。
Patashu 2013年


4
3つの特別なケースに共通するものはありますか?たとえばif not something.startswith("th"): doThisMostOfTheTime()else句で別の比較を行うことができます。
Tim Pietzcker 2013年

3
@ kramer65 if / elifのこのような長いチェーンの場合...遅くなる可能性がありますが、実際にコードのプロファイルを作成し、最も時間がかかる部分を最適化することから始めてください。
jorgeca 2013年

1
これらの比較はsomething、の値ごとに1回だけ実行されますか、または同じ値に対して同様の比較が複数回実行されますか?
Chris Pitman 2013年

回答:


98

コード...

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には、個別のキーミスごとに新しいアイテムを追加するという追加のメモリオーバーヘッドがあるため、無制限の個別のキーミスが予想される場合は、オプション#3を使用します。元の構成。


2
Pythonにはswitchステートメントがありますか?
nathan hayfield 2013年

うーん...これまでのところ、私が気にしていないpythonについて聞いたのはこれだけです...何かがあるに
ちがい

2
-1あなたはaの使用dictは遅いと言っていますが、実際のタイミングでは、それが2番目に速いオプションであることを示しています。
Marcin

11
@Marcin私はそれdict.get()が遅いと言っています2.py-つまり、それらすべての中で最も遅いです。

記録では、3と4は、try / except構成でキーエラーをキャプチャするよりも劇的に高速です。
Jeff

78

辞書を作成します。

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

今すぐ使用してください:

options.get(something, doThisMostOfTheTime)()

辞書にsomething見つからない場合は、デフォルト値を返しますoptionsdict.getdoThisMostOfTheTime

いくつかのタイミング比較:

脚本:

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()

options = collections.defaultdict(lambda: doThisMostOfTheTime, {'this': doThis,'that' :doThat, 'there':doThere}); options[something]()わずかに効率的です。

かっこいいアイデアですが、読みにくいです。また、optionsdict を分離して再構築を回避し、ロジックの一部(すべてではない)を使用のポイントから遠くに移動することもできます。それでも、素敵なトリック!
アンダースヨハンソン2013年

7
これがより効率的かどうか知っていますか?私の推測では、1つまたは3つの単純な条件チェックではなく、ハッシュルックアップを実行しているため、処理が遅くなります。問題は、コードのコンパクトさではなく効率についてです。
ブライアンオークリー2013年

2
@BryanOakleyいくつかのタイミング比較を追加しました。
Ashwini Chaudhary 2013年

1
実際には、より効率的であるはずですtry: options[key]() except KeyError: doSomeThingElse()if key in options: options[key]()辞書を2回検索しているためですkey
hardmooth

8

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

こんにちはフォズ。先端をありがとう。実際、私は既にpypyを使用しています(それが大好きです)が、速度の改善が必要です.. :)
kramer65

しかたがない!これの前に、「this」、「that」、「there」のハッシュを事前に計算し、文字列ではなくハッシュコードを比較してみました。これは元の2倍遅いことが判明したため、文字列の比較は既に内部でかなり最適化されているようです。
フォズ2013年

3

ここでは、動的条件を辞書に変換した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的な方法ではない可能性があります。


0

人々execはセキュリティ上の理由で警告しますが、これはそのための理想的なケースです。
簡単なステートマシンです。

Codes = {}
Codes [0] = compile('blah blah 0; nextcode = 1')
Codes [1] = compile('blah blah 1; nextcode = 2')
Codes [2] = compile('blah blah 2; nextcode = 0')

nextcode = 0
While True:
    exec(Codes[nextcode])
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.