¿La forma más eficiente de hacer una statement if-elif-elif-else cuando más se hace el rest?

Tengo una sentencia if-elif-elif-else en la que el 99% de las veces se ejecuta la sentencia else:

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

Este constructo se hace mucho , pero como pasa por todas las condiciones antes de que llegue a otra cosa, tengo la sensación de que esto no es muy eficiente, por no hablar de Pythonic. Por otro lado, necesita saber si se cumple alguna de esas condiciones, por lo que debería probarlo de todos modos.

¿Alguien sabe si y cómo podría hacerse esto de manera más eficiente o es simplemente la mejor manera de hacerlo?

El código…

 options.get(something, doThisMostOfTheTime)() 

… parece que debería ser más rápido, pero en realidad es más lento que la construcción ifelifelse , porque tiene que llamar a una función, lo que puede ser una sobrecarga de rendimiento significativa en un circuito cerrado.

Considera estos ejemplos …

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] 

… y tenga en cuenta la cantidad de tiempo de CPU que utilizan …

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

… usando el tiempo del usuario de vez en time(1) .

La opción # 4 tiene la sobrecarga de memoria adicional de agregar un nuevo elemento por cada falta de clave distinta, por lo que si está esperando una cantidad ilimitada de fallas de teclas distintas, optaría por la opción # 3, que sigue siendo una mejora significativa en El constructo original.

Me gustaría crear un diccionario:

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

Ahora usa solo:

 options.get(something, doThisMostOfTheTime)() 

Si no se encuentra something en las options dict, entonces dict.get devolverá el valor predeterminado doThisMostOfTheTime

Algunas comparaciones de tiempo:

Guión:

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

Resultados:

 >>> 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 claves inexistentes y 100 claves válidas ::

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

Por lo tanto, para un diccionario normal, buscar la clave usando la key in options es la forma más eficiente aquí:

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

¿Eres capaz de usar pypy?

Mantener su código original pero ejecutarlo en pypy me da una aceleración 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 

Ese es un ejemplo de un if con condiciones dinámicas traducidas a un diccionario.

 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 una forma, pero puede que no sea la forma más pythonica de hacerlo porque es menos legible para quien no es fluido en Python.