else가 가장 많이 수행 될 때 if-elif-elif-else 문을 만드는 가장 효율적인 방법은 무엇입니까?


99

99 %의 시간 동안 else 문이 실행되는 if-elif-elif-else 문이 있습니다.

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

이 구조는 많이 수행 되지만 다른 조건에 도달하기 전에 모든 조건을 검토하기 때문에 Pythonic은 말할 것도없고 매우 효율적이지 않다고 느낍니다. 반면에 이러한 조건이 충족되는지 알 필요가 있으므로 어쨌든 테스트해야합니다.

이것이 더 효율적으로 수행 될 수 있는지, 아니면 이것이 가능한 최선의 방법인지 아는 사람이 있습니까?


할 수 있습니다 sort당신이 당신의 경우 / 다른 ... 체인을 실행하는 것, 조건 중 하나가 한쪽 끝에있는에 대한 일치하는 모든 요소 및 모든 나머지는 다른에 있는지 등? 그렇다면 그것이 더 빠르고 우아한 지 아닌지 알 수 있습니다. 그러나 성능 문제가 없다면 최적화에 대해 걱정하기에는 너무 이릅니다.
Patashu


4
세 가지 특별한 경우의 공통점이 있습니까? 예를 들어, 절 if not something.startswith("th"): doThisMostOfTheTime()에서 다른 비교를 수행 하고 수행 할 수 else있습니다.
Tim Pietzcker 2013 년

3
@ kramer65 if / elif의 긴 체인이라면 속도가 느릴 수 있지만 실제로 코드를 프로파일 링 하고 가장 많은 시간이 걸리는 부분을 최적화하여 시작하십시오.
jorgeca 2013-06-18

1
이러한 비교는의 값당 한 번만 something수행됩니까, 아니면 동일한 값에 대해 유사한 비교가 여러 번 수행됩니까?
Chris Pitman

답변:


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
파이썬에는 switch 문이 있습니까?
nathan hayfield

우 ... 물론 지금까지 내가 뭔가있을 묶여 추측 위해 ... 난 상관하지 않는 것이 파이썬에 대해 들어 본 유일한 이잖아
나단 목초 밭

2
-1 a를 사용하는 dict것이 더 느리다고 말하지만 실제로 타이밍이 두 번째로 빠른 옵션임을 보여줍니다.
Marcin

11
@Marcin 나는 그것이 dict.get()더 느리다는 것을 말하고 2.py있습니다.
Aya

기록을 위해 3 개와 4 개는 try / except 구조에서 주요 오류를 캡처하는 것보다 훨씬 빠릅니다.
Jeff

78

나는 사전을 만들 것이다.

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

이제 다음을 사용하십시오.

options.get(something, doThisMostOfTheTime)()

dict에 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]()약간 더 효율적입니다.
Aya

멋진 아이디어이지만 읽을 수는 없습니다. 또한 재 options구축을 피하기 위해 dict 를 분리 하여 논리의 일부 (전부는 아님)를 사용 지점에서 멀리 이동 시키고 싶을 것입니다 . 그래도 멋진 트릭!
Anders Johansson

7
당신이 할 이 더 효율적인지? 내 생각 엔 간단한 조건부 검사 또는 세 번이 아닌 해시 조회를 수행하기 때문에 속도가 느립니다. 문제는 코드의 간결함보다는 효율성에 관한 것입니다.
Bryan Oakley

2
@BryanOakley 나는 몇 가지 타이밍 비교를 추가했습니다.
Ashwini Chaudhary 2013-06-18

1
실제로 수행하는 것이 더 효율적이어야합니다 try: options[key]() except KeyError: doSomeThingElse()( if key in options: options[key]()사전을 두 번 검색하기 때문에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

안녕 Foz. 팁 고마워. 사실 난 이미 (사랑을) pypy를 사용하고,하지만, 난 여전히 속도 향상이 필요 ... :)
kramer65

오 잘! 그 전에 'this', 'that', 'there'에 대한 해시를 미리 계산 한 다음 문자열 대신 해시 코드를 비교해 보았습니다. 그것은 원본보다 두 배 느린 것으로 판명되었으므로 문자열 비교가 이미 내부적으로 꽤 잘 최적화 된 것처럼 보입니다.
foz

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]

그것은 방법이지만 파이썬에 능숙하지 않은 사람이 읽기가 어렵 기 때문에 가장 비단뱀적인 방법은 아닐 수 있습니다.


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.