web-dev-qa-db-fra.com

Le moyen le plus efficace de faire une déclaration if-Elif-elif-else quand le reste est fait le plus?

J'ai une déclaration dans if-Elif-elif-else dans laquelle 99% du temps, l'instruction else est exécutée:

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

Cette construction est faite beaucoup, mais comme elle couvre toutes les conditions avant de toucher le reste, j'ai le sentiment que ce n'est pas très efficace, encore moins Pythonic. D'autre part, il doit savoir si certaines de ces conditions sont remplies, il doit donc le tester quand même.

Est-ce que quelqu'un sait si et comment cela pourrait être fait plus efficacement ou est-ce simplement la meilleure façon de le faire?

87
kramer65

Le code...

options.get(something, doThisMostOfTheTime)()

... semble devoir être plus rapide, mais en réalité c'est plus lent que la construction if ... Elif ... else, car elle doit appeler une fonction , ce qui peut représenter une surcharge de performances significative dans une boucle serrée.

Considérez ces exemples ...

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]

... et notez la quantité de temps CPU qu'ils utilisent ...

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

... en utilisant le temps utilisateur de time(1) .

L’option n ° 4 entraîne une surcharge de mémoire supplémentaire liée à l’ajout d’un nouvel élément pour chaque clé manquante distincte. Par conséquent, si vous attendez un nombre illimité d’éléments manquants distincts, je choisirais l’option n ° 3, qui constitue toujours une amélioration significative par rapport à la construction d'origine.

89
Aya

Je créerais un dictionnaire:

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

Maintenant, utilisez simplement:

options.get(something, doThisMostOfTheTime)()

Si something ne figure pas dans le options dict, alors dict.get retournera la valeur par défaut doThisMostOfTheTime

Quelques comparaisons de temps:

Scénario:

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

Résultats:

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

Pour 10**5 clés inexistantes et 100 clés valides ::

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

Donc, pour un dictionnaire normal, vérifier la clé en utilisant key in options est le moyen le plus efficace ici:

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

Pouvez-vous utiliser Pypy?

Conserver votre code d'origine mais l'exécuter sur pypy me permet d'accélérer de 50 fois.

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

C'est un exemple de if avec des conditions dynamiques traduites en dictionnaire.

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]

C'est un moyen, mais peut-être pas le moyen le plus pythonique de le faire car il est moins lisible pour qui ne parle pas couramment Python.

0
Arthur Julião