it-swarm.com.de

Der effizienteste Weg, um eine if-Elif-elif-else-Aussage zu machen, wann das else am meisten getan wird?

Ich habe eine if-Elif-elif-else-Anweisung, in der 99% der Zeit die else-Anweisung ausgeführt wird:

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

Dieses Konstrukt ist fertig viel, aber da es alle Bedingungen durchläuft, bevor es auf die anderen trifft, habe ich das Gefühl, dass dies nicht sehr effizient ist, geschweige denn Pythonic. Andererseits muss es wissen, ob eine dieser Bedingungen erfüllt ist, daher sollte es es trotzdem testen.

Weiß jemand, ob und wie dies effizienter durchgeführt werden kann, oder ist dies einfach der beste Weg, dies zu tun?

87
kramer65

Der Code...

options.get(something, doThisMostOfTheTime)()

... sieht so aus, als müsste es schneller sein, ist aber tatsächlich langsamer als das Konstrukt if ... Elif ... else, weil es eine Funktion aufrufen muss , was in einer engen Schleife ein erheblicher Leistungsaufwand sein kann.

Betrachten Sie diese Beispiele ...

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]

... und notieren Sie die CPU-Zeit, die sie verbrauchen ...

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

... mit der Benutzerzeit von time(1) .

Option Nr. 4 hat den zusätzlichen Speicheraufwand für das Hinzufügen eines neuen Elements für jeden eindeutigen Schlüsselfehler. Wenn Sie also eine unbegrenzte Anzahl von eindeutigen Schlüsselfehlern erwarten, würde ich Option Nr. 3 wählen, die immer noch eine erhebliche Verbesserung darstellt das ursprüngliche Konstrukt.

89
Aya

Ich würde ein Wörterbuch erstellen:

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

Verwenden Sie jetzt nur:

options.get(something, doThisMostOfTheTime)()

Wenn something nicht im options Diktat gefunden wird, dann dict.get gibt den Standardwert doThisMostOfTheTime zurück

Einige Zeitvergleiche:

Skript:

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

Ergebnisse:

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

Zum 10**5 nicht vorhandene Schlüssel und 100 gültige Schlüssel ::

>>> %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

Für ein normales Wörterbuch wird der Schlüssel also mit key in options ist hier der effizienteste Weg:

if key in options:
   options[key]()
else:
   doSomethingElse()
74

Können Sie pypy verwenden?

Wenn Sie Ihren Originalcode behalten, ihn aber auf pypy ausführen, wird die Geschwindigkeit für mich um das 50-fache erhöht.

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
7
foz

Dies ist ein Beispiel für ein If mit dynamischen Bedingungen, das in ein Wörterbuch übersetzt wurde.

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]

Es ist ein Weg, aber möglicherweise nicht der pythonischste, weil er weniger lesbar ist, wenn Sie nicht fließend Python sprechen.

0
Arthur Julião