110 votes

La manière la plus efficace de faire une déclaration if-elif-elif-else lorsque le else est le plus utilisé ?

J'ai une instruction if-elif-elif-else dans laquelle, dans 99 % des cas, l'instruction else est exécutée :

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

Cette construction se fait beaucoup mais comme il passe en revue chaque condition avant d'atteindre l'autre, j'ai l'impression que ce n'est pas très efficace, et encore moins pythonique. D'un autre côté, il a besoin de savoir si l'une de ces conditions est remplie, donc il devrait quand même la tester.

Quelqu'un sait-il si et comment cela pourrait être fait plus efficacement ou est-ce simplement la meilleure façon de procéder ?

0 votes

Pouvez-vous sort les éléments sur lesquels vous exécutez votre chaîne if/else..., de sorte que tous les éléments pour lesquels l'une des conditions correspondra se trouvent à une extrémité, et tous les autres à l'autre ? Si c'est le cas, vous pourriez voir si cela est plus rapide/plus élégant ou non. Mais rappelez-vous, s'il n'y a pas de problème de performance, il est trop tôt pour se préoccuper de l'optimisation.

1 votes

0 votes

@Patashu - Malheureusement, j'ai un énorme problème de performance. Ce programme fonctionne jour et nuit en analysant d'énormes quantités de données. C'est seulement parce que mes compétences en C++ ne sont pas à la hauteur (et j'adore Python) que je ne le réécris pas

112voto

Aya Points 13144

Le code...

options.get(something, doThisMostOfTheTime)()

...semble devoir être plus rapide, mais il est en réalité plus lent que la méthode if ... elif ... else car elle doit appeler une fonction, ce qui peut représenter un surcoût important en termes de performances 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 d'utilisation de time(1) .

L'option n°4 entraîne une surcharge de mémoire supplémentaire due à l'ajout d'un nouvel élément pour chaque absence de clé distincte, donc si vous prévoyez un nombre illimité d'absences de clé distinctes, j'opterais pour l'option n°3, qui reste une amélioration significative de la construction originale.

3 votes

Python dispose-t-il d'une instruction switch ?

0 votes

Ugh...bien, jusqu'à présent c'est la seule chose que j'ai entendu à propos de Python que je n'aime pas...je suppose qu'il devait y avoir quelque chose...

2 votes

-1 Vous dites que l'utilisation d'un dict est plus lente, mais vos chronométrages montrent en fait que c'est la deuxième option la plus rapide.

84voto

Ashwini Chaudhary Points 94431

Je créerais un dictionnaire :

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

Maintenant, utilisez juste :

options.get(something, doThisMostOfTheTime)()

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

Quelques comparaisons de temps :

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

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

Para 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

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

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

0 votes

options = collections.defaultdict(lambda: doThisMostOfTheTime, {'this': doThis,'that' :doThat, 'there':doThere}); options[something]() est marginalement plus efficace.

0 votes

Idée cool, mais pas aussi lisible. De plus, vous voudrez probablement séparer les options dict pour éviter de le reconstruire, déplaçant ainsi une partie (mais pas toute) de la logique loin du point d'utilisation. Mais bon, c'est un bon truc !

7 votes

Est-ce que vous connaître Est-ce plus efficace ? Je pense que c'est plus lent puisqu'il s'agit d'une consultation de hachage plutôt que d'une simple vérification conditionnelle ou trois. La question porte sur l'efficacité plutôt que sur la compacité du code.

9voto

foz Points 353

Pouvez-vous utiliser pypy ?

En gardant votre code original mais en l'exécutant sur pypy, j'ai obtenu une accélération de 50x.

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

0 votes

Salut Foz. Merci pour le conseil. En fait, j'utilise déjà pypy (je l'adore), mais j'ai encore besoin d'améliorations de la vitesse :)

0 votes

Eh bien ! Avant cela, j'ai essayé de pré-calculer un hachage pour 'ceci', 'cela' et 'là', puis de comparer des codes de hachage au lieu de chaînes de caractères. Cela s'est avéré deux fois plus lent que l'original, il semble donc que les comparaisons de chaînes de caractères soient déjà bien optimisées en interne.

4voto

Arthur Julião Points 710

Voici un exemple d'un if avec des conditions dynamiques traduit dans un 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 ce n'est peut-être pas le moyen le plus pythonique de le faire, car il est moins lisible pour ceux qui ne maîtrisent pas Python.

0voto

user3319934 Points 23

Les gens mettent en garde contre exec pour des raisons de sécurité, mais c'est un cas idéal pour cela.
C'est une machine à état simple.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X