while una tiene sentencias recomendación otra numeros más grande for expresión else elif ejemplos dentro cuál cuando condicional comparar bucle python performance if-statement

una - sentencias en python



La forma más eficiente de hacer una declaración if-elif-elif-else cuando se hace más lo demás? (4)

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

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

Este constructo está hecho mucho , pero como repasa todas las condiciones antes de que llegue al otro, tengo la sensación de que no es muy eficiente, y mucho menos de Pythonic. Por otro lado, necesita saber si se cumple alguna de esas condiciones, por lo que debe probarlo de todos modos.

¿Alguien sabe si y cómo se puede hacer de manera más eficiente o es simplemente la mejor manera de hacerlo?


¿Eres capaz de usar pypy?

Mantener el código original pero ejecutarlo en pypy me da una velocidad 50 veces mayor.

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


Crearía un diccionario:

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

Ahora usa solo:

options.get(something, doThisMostOfTheTime)()

Si no se encuentra something en el dict de options 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, verificar la clave usando key in options es la forma más eficiente aquí:

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


El código...

options.get(something, doThisMostOfTheTime)()

... parece que debería ser más rápido, pero en realidad es más lento que el constructo if ... elif ... else , porque tiene que llamar a una función, que puede ser una sobrecarga de rendimiento significativa en un ciclo 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 desde el time(1) .

La opción n. ° 4 tiene la sobrecarga de memoria adicional de agregar un nuevo ítem por cada falla clave, así que si está esperando un número ilimitado de fallas de teclas distintas, iría con la opción n. ° 3, que sigue siendo una mejora significativa en la construcción original.


Ese es un ejemplo de un si 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 no ser la forma más pitónica de hacerlo, porque es menos legible para quienes no dominan Python.