Cómo optimizar sus scripts de Python para un mejor rendimiento

Como Optimizar Sus Scripts De Python Para Un Mejor Rendimiento



Optimizar los scripts de Python para un mejor rendimiento implica identificar y abordar los cuellos de botella en nuestro código, haciéndolo ejecutar más rápido y más eficientemente. Python es un lenguaje de programación potente y popular que se utiliza en numerosas aplicaciones hoy en día, incluido el análisis de datos, proyectos de ML (aprendizaje automático), desarrollo web y muchas más. La optimización del código Python es una estrategia para mejorar la velocidad y eficiencia del programa de desarrollador al realizar cualquier actividad utilizando menos líneas de código, menos memoria o recursos adicionales. Un código grande e ineficiente puede ralentizar el programa, lo que puede resultar en una baja satisfacción del cliente y una posible pérdida financiera, o la necesidad de más trabajo para solucionar y solucionar problemas.

Es necesario al realizar una tarea que requiere procesar varias acciones o datos. Por lo tanto, cambiar y mejorar algunos bloques de código y funcionalidades ineficaces puede tener resultados sorprendentes como los siguientes:

  1. Aumentar el rendimiento de la aplicación.
  2. Cree código legible y organizado
  3. Simplifique la supervisión y depuración de errores
  4. Conservar un poder computacional considerable, etc.

Perfile su código

Antes de comenzar a optimizar, es esencial identificar las partes del código del proyecto que lo ralentizan. Las técnicas para crear perfiles en Python incluyen los paquetes cProfile y perfil. Utilice estas herramientas para medir la rapidez con la que se ejecutan determinadas funciones y líneas de código. El módulo cProfile produce un informe que detalla cuánto tiempo tarda en ejecutarse cada función de script. Este informe puede ayudarnos a encontrar funciones que se estén ejecutando con lentitud para que podamos mejorarlas.







Fragmento de código:



importar cPerfil como CP
definición calcular suma ( número de entrada ) :
suma_de_números_de_entrada = 0
  mientras número de entrada > 0 :
suma_de_números_de_entrada + = número de entrada % 10
número de entrada // = 10
  imprimir ( 'La suma de todos los dígitos del número de entrada es: 'suma_de_números_de_entrada'' )
  devolver suma_de_números_de_entrada
definición función_principal ( ) :
CP. correr ( 'calcularSuma(9876543789)' )
si __nombre__ == '__principal__' :
función_principal ( )

El programa realiza un total de cinco llamadas a funciones como se ve en la primera línea del resultado. Los detalles de cada llamada a función se muestran en las siguientes líneas, incluida la cantidad de veces que se invocó la función, la duración total del tiempo en la función, la duración del tiempo por llamada y la cantidad total de tiempo en la función (incluido todas las funciones a las que se llama).



Además, el programa imprime un informe en la pantalla de aviso que muestra que el programa completa el tiempo de ejecución de todas sus tareas en 0,000 segundos. Esto muestra qué tan rápido es el programa.





Elija la estructura de datos adecuada

Las características de rendimiento dependen de la estructura de datos. En particular, los diccionarios son más rápidos para realizar búsquedas que las listas de almacenamiento de uso general. Seleccione la estructura de datos que sea más adecuada para las operaciones que realizaremos con sus datos, si las conoce. El siguiente ejemplo investiga la efectividad de diferentes estructuras de datos para un proceso idéntico para determinar si un elemento en la estructura de datos está presente.



Evaluamos el tiempo que lleva comprobar si un elemento está presente en cada estructura de datos (una lista, un conjunto y un diccionario) y los comparamos.

OptimizeDataType.py:

importar tiempoi como TT
importar aleatorio como rndobj
# Generar una lista de números enteros
lista_datos_aleatorios = [ rndobj. randint ( 1 , 10000 ) para _ en rango ( 10000 ) ]
# Crea un conjunto a partir de los mismos datos.
conjunto_de_datos_aleatorios = colocar ( lista_datos_aleatorios )

# Crea un diccionario con los mismos datos que las claves.
obj_DataDictionary = { en uno: Ninguno para en uno en lista_datos_aleatorios }

# Elemento a buscar (existe en los datos)
número_aleatorio_para_buscar = rndobj. elección ( lista_datos_aleatorios )

# Mida el tiempo para verificar la membresía en una lista
lista_hora = tt. tiempoi ( lambda : número_aleatorio_a_buscar en lista_datos_aleatorios , número = 1000 )

# Mida el tiempo para verificar la membresía en un conjunto
fijar tiempo = tt. tiempoi ( lambda : número_aleatorio_a_buscar en conjunto_de_datos_aleatorios , número = 1000 )

# Mida el tiempo para verificar la membresía en un diccionario
dict_time = tt. tiempoi ( lambda : número_aleatorio_a_buscar en obj_DataDictionary , número = 1000 )

imprimir ( F 'Tiempo de verificación de membresía de la lista: {list_time:.6f} segundos' )
imprimir ( F 'Establecer tiempo de verificación de membresía: {set_time:.6f} segundos' )
imprimir ( F 'Tiempo de verificación de membresía del diccionario: {dict_time:.6f} segundos' )

Este código compara el rendimiento de listas, conjuntos y diccionarios al realizar comprobaciones de membresía. En general, los conjuntos y diccionarios son sustancialmente más rápidos que las listas para las pruebas de membresía porque utilizan búsquedas basadas en hash, por lo que tienen una complejidad temporal promedio de O(1). Las listas, por otro lado, deben realizar búsquedas lineales, lo que da como resultado pruebas de membresía con complejidad temporal O(n).

  Una captura de pantalla de una computadora Descripción generada automáticamente

Utilice las funciones integradas en lugar de bucles

Se pueden utilizar numerosas funciones o métodos integrados en Python para realizar tareas típicas como filtrar, ordenar y mapear. Usar estas rutinas en lugar de crear bucles propios ayuda a acelerar el código porque con frecuencia están optimizadas para el rendimiento.

Creemos un código de muestra para comparar el rendimiento de la creación de bucles personalizados utilizando las funciones integradas para trabajos típicos (como map(), filter() y sorted()). Evaluaremos qué tan bien funcionan los distintos métodos de mapeo, filtración y clasificación.

BuiltInFunctions.py:

importar tiempoi como TT
# Lista de muestra de números_lista
lista_numeros = lista ( rango ( 1 , 10000 ) )

# Función para elevar al cuadrado la lista de números usando un bucle
definición cuadrado_usando_bucle ( lista_numeros ) :
resultado_cuadrado = [ ]
    para en uno en lista_números:
resultado_cuadrado. adjuntar ( en uno ** 2 )
    devolver resultado_cuadrado
# Función para filtrar lista_números pares usando un bucle
definición filtro_even_usando_loop ( lista_numeros ) :
resultado_filtro = [ ]
    para en uno en lista_números:
        si en uno % 2 == 0 :
resultado_filtro. adjuntar ( en uno )
    devolver resultado_filtro
# Función para ordenar lista_números usando un bucle
definición sort_using_loop ( lista_numeros ) :
    devolver ordenado ( lista_numeros )
# Medir el tiempo para elevar al cuadrado la lista de números usando map()
tiempo_mapa = tt. tiempoi ( lambda : lista ( mapa ( lambda x:x** 2 , lista_numeros ) ) , número = 1000 )
# Medir el tiempo para filtrar la lista de números pares usando filter()
tiempo_filtro = tt. tiempoi ( lambda : lista ( filtrar ( lambda x: x % 2 == 0 , lista_numeros ) ) , número = 1000 )
# Medir el tiempo para ordenar la lista de números usando sorted()
tiempo_ordenado = tt. tiempoi ( lambda : ordenado ( lista_numeros ) , número = 1000 )
# Medir el tiempo para elevar al cuadrado la lista de números usando un bucle
loop_map_time = tt. tiempoi ( lambda : cuadrado_usando_loop ( lista_numeros ) , número = 1000 )
# Medir el tiempo para filtrar la lista de números pares usando un bucle
loop_filter_time = tt. tiempoi ( lambda : filter_even_using_loop ( lista_numeros ) , número = 1000 )
# Medir el tiempo para ordenar la lista de números usando un bucle
loop_sorted_time = tt. tiempoi ( lambda : sort_using_loop ( lista_numeros ) , número = 1000 )
imprimir ( 'La lista de números contiene 10000 elementos' )
imprimir ( F 'Tiempo del mapa(): {map_time:.6f} segundos' )
imprimir ( F 'Tiempo de filtro(): {filter_time:.6f} segundos' )
imprimir ( F 'Tiempo ordenado(): {sorted_time:.6f} segundos' )
imprimir ( F 'Tiempo de bucle (mapa): {loop_map_time:.6f} segundos' )
imprimir ( F 'Tiempo de bucle (filtro): {loop_filter_time:.6f} segundos' )
imprimir ( F 'Tiempo de bucle (ordenado): {loop_sorted_time:.6f} segundos' )

Probablemente observemos que las funciones integradas (map(), filter() y sorted()) son más rápidas que los bucles personalizados para estas tareas comunes. Las funciones integradas en Python ofrecen un enfoque más conciso y comprensible para llevar a cabo estas tareas y están altamente optimizadas para el rendimiento.

Optimizar los bucles

Si es necesario escribir los bucles, existen algunas técnicas que podemos aplicar para acelerarlos. Generalmente, el bucle range() es más rápido que iterar hacia atrás. Esto se debe a que range() genera un iterador sin invertir la lista, lo que puede ser una operación costosa para listas largas. Además, dado que range() no crea una nueva lista en la memoria, utiliza menos memoria.

OptimizeLoop.py:

importar tiempoi como TT
# Lista de muestra de números_lista
lista_numeros = lista ( rango ( 1 , 100000 ) )
# Función para iterar sobre la lista en orden inverso
definición iteración_inversa_bucle ( ) :
resultado_reverso = [ ]
    para j en rango ( solo ( lista_numeros ) - 1 , - 1 , - 1 ) :
resultado_reverso. adjuntar ( lista_numeros [ j ] )
    devolver resultado_reverso
# Función para iterar sobre la lista usando range()
definición iteración_rango_bucle ( ) :
rango_resultado = [ ]
    para k en rango ( solo ( lista_numeros ) ) :
rango_resultado. adjuntar ( lista_numeros [ k ] )
    devolver rango_resultado
# Medir el tiempo que lleva realizar la iteración inversa
tiempo_inverso = tt. tiempoi ( iteración_inversa_bucle , número = 1000 )
# Medir el tiempo que lleva realizar la iteración del rango.
rango_tiempo = tt. tiempoi ( iteración_rango_bucle , número = 1000 )
imprimir ( 'La lista de números contiene 100.000 registros' )
imprimir ( F 'Tiempo de iteración inversa: {reverse_time:.6f} segundos' )
imprimir ( F 'Tiempo de iteración de rango: {range_time:.6f} segundos' )

Evite llamadas a funciones innecesarias

Hay cierta sobrecarga cada vez que se llama a una función. El código se ejecuta más rápidamente si se evitan llamadas a funciones innecesarias. Por ejemplo, en lugar de ejecutar repetidamente una función que calcula un valor, intente almacenar el resultado del cálculo en una variable y usarlo.

Herramientas para crear perfiles

Para obtener más información sobre el rendimiento de su código, además de la creación de perfiles integrada, podemos utilizar paquetes de creación de perfiles externos como cProfile, Pyflame o SnakeViz.

Resultados de caché

Si nuestro código necesita realizar cálculos costosos, podríamos considerar almacenar en caché los resultados para ahorrar tiempo.

Código de refactorización

Refactorizar el código para que sea más fácil de leer y mantener es a veces una parte necesaria para optimizarlo. Un programa más rápido también puede ser más limpio.

Utilice la compilación justo a tiempo (JIT)

Bibliotecas como PyPy o Numba pueden proporcionar una compilación JIT que puede acelerar significativamente ciertos tipos de código Python.

Actualizar Python

Asegúrese de estar utilizando la última versión de Python, ya que las versiones más recientes suelen incluir mejoras de rendimiento.

Paralelismo y concurrencia

Para procesos que se pueden paralelizar, investigue las técnicas de sincronización y paralelo como multiprocesamiento, subprocesamiento o asincio.

Recuerde que la evaluación comparativa y la elaboración de perfiles deben ser los principales impulsores de la optimización. Concéntrese en mejorar las áreas de nuestro código que tienen los efectos más significativos en el rendimiento y pruebe constantemente sus mejoras para asegurarse de que tengan los efectos deseados sin introducir más defectos.

Conclusión

En conclusión, la optimización del código Python es crucial para mejorar el rendimiento y la eficacia de los recursos. Los desarrolladores pueden aumentar considerablemente la velocidad de ejecución y la capacidad de respuesta de sus aplicaciones Python utilizando diversas técnicas, como seleccionar las estructuras de datos apropiadas, aprovechar las funciones integradas, reducir los bucles adicionales y administrar la memoria de manera efectiva. La evaluación comparativa y la elaboración de perfiles continuos deben dirigir los esfuerzos de optimización, garantizando que los avances del código coincidan con los requisitos de rendimiento del mundo real. Para garantizar el éxito del proyecto a largo plazo y reducir la posibilidad de introducir nuevos problemas, la optimización del código debe equilibrarse constantemente con los objetivos de legibilidad y mantenibilidad del código.