Course
Recogida de Basura en Python: Conceptos y mecanismos clave
La gestión de la memoria es un aspecto importante de la programación, aunque a menudo se pasa por alto. Si no se gestiona adecuadamente, puede provocar aplicaciones lentas, fallos inesperados o incluso fugas de memoria. Afortunadamente, Python se encarga de ello mediante un proceso conocido como recolección de basura, que es un sistema incorporado que gestiona automáticamente la memoria.
Pero, ¿cómo funciona la recogida de basura de Python y por qué debería importarte? Comprender este proceso es clave para escribir un código eficiente y sin errores que funcione bien, incluso cuando tus proyectos crezcan en complejidad.
Al final de este artículo, conocerás los fundamentos de la recolección de basura en Python y por qué es importante, comprenderás cómo Python utiliza el recuento de referencias y la recolección generacional de basura para gestionar la memoria y aprenderás algunos consejos para evitar las fugas de memoria. Al final te animo a que te inscribas también en nuestro curso Escribir código Python eficiente, que te enseñará a asignar recursos en tu entorno y a considerar la sobrecarga, temas más amplios de los que la recogida de basuras es sólo una parte.
¿Qué es la Recogida de Basura en Python?
La recogida de basura es la forma que tiene Python de gestionar automáticamente la memoria, garantizando que tus aplicaciones funcionen sin problemas al liberar la memoria que ya no está en uso. En términos más sencillos, es como tener un conserje invisible que limpia tu código, deshaciéndose de los objetos que ya no son necesarios.
"Python recogiendo basura". Imagen de Dall-E 2
¿Por qué necesitamos la recolección de basura en Python?
En cualquier aplicación, se crean objetos para almacenar datos, realizar cálculos o gestionar tareas. Sin embargo, una vez que estos objetos han cumplido su función, siguen ocupando espacio de memoria hasta que se eliminan explícitamente. Si estos objetos no utilizados se acumulan, pueden hacer que tu aplicación utilice más memoria de la necesaria, provocando un rendimiento más lento y posibles fallos.
La recogida de basura evita estas situaciones. Detecta automáticamente cuando un objeto ya no es necesario (ninguna parte de tu código hace referencia a él), y entonces lo elimina de la memoria de forma segura. Este proceso ayuda a:
- Evita las fugas de memoria: Al limpiar automáticamente los objetos no utilizados, la recogida de basura reduce el riesgo de fugas de memoria, cuando no se libera la memoria que ya no se necesita.
- Optimiza el rendimiento: Al liberar memoria, la recogida de basura ayuda a mantener el rendimiento de tu aplicación, especialmente en programas de larga ejecución o que manejan gran cantidad de datos.
- Simplifica el desarrollo: Como Python gestiona la memoria automáticamente, los desarrolladores pueden centrarse en escribir código en lugar de gestionar la memoria.
Cómo implementa Python la Recogida de Basura automáticamente
La recolección de basura de Python es un complejo sistema diseñado para gestionar la memoria automáticamente, permitiendo a los desarrolladores centrarse en escribir código en lugar de preocuparse por la gestión de la memoria. Python utiliza principalmente dos mecanismos para implementar la recogida de basura:el recuento de referencias y la recogida de basura generacional. Estos mecanismos trabajan juntos para garantizar que la memoria se gestiona de forma eficiente, minimizando las posibilidades de fugas de memoria y optimizando el rendimiento de tu aplicación.
Antes de examinar estos mecanismos, es importante señalar que este proceso de recogida de basura es más relevante para CPython, la implementación más utilizada de Python. Mientras que otras implementaciones como PyPy o Jython pueden gestionar la recogida de basura de forma diferente, CPython depende en gran medida de estos dos métodos para mantener tus aplicaciones funcionando sin problemas.
Recuento de referencia
El recuento de referencias es el método fundamental que utiliza Python para gestionar la memoria. En esencia, el recuento de referencias consiste en llevar la cuenta del número de referencias (o punteros) a un objeto en memoria. Cada vez que se crea una nueva referencia a un objeto, Python aumenta la cuenta de referencias de ese objeto. A la inversa, cuando una referencia se elimina o sale del ámbito, Python disminuye el recuento de referencias. Así es como funciona:
- Referencias de seguimiento: Cada objeto en Python tiene un contador de referencias, que se actualiza cada vez que el objeto es referenciado o desreferenciado. Por ejemplo, asignar un objeto a una variable o pasarlo a una función aumenta su recuento de referencias, mientras que eliminar la variable lo disminuye.
- Desasignación de memoria: Cuando la cuenta de referencias de un objeto llega a cero, lo que significa que ninguna parte de tu código está utilizando el objeto, Python desasigna automáticamente la memoria que ocupa.
A pesar de su eficacia, el recuento de referencia tiene limitaciones. La limitación más significativa es su incapacidad para manejar referencias cíclicas, que se producen cuando dos o más objetos se referencian entre sí, formando un ciclo. En estos casos, el recuento de referencias nunca llega a cero, lo que impide que se recupere memoria. Aquí es donde entra en juegola recogida de basura generacional .
Recogida de basura generacional
Para superar las limitaciones del recuento de referencias, Python también emplea la recolección generacional de basura. Este método avanzado está diseñado para tratar las referencias cíclicas y mejorar la eficacia de la gestión de la memoria. La idea central de la basura generacional se basa en la observación de que la mayoría de los objetos son de corta duración (temporales) o de larga duración (persistentes). Al categorizar los objetos en función de su antigüedad, Python optimiza el proceso de recogida de basura. Así funciona la recogida de basura generacional:
-
Generaciones: El recolector de basura de Python organiza los objetos en tres generaciones: Generación 0 (la más joven), Generación 1 (de mediana edad) y Generación 2 (la más mayor). Los objetos nuevos se colocan en la Generación 0, y si sobreviven a la recogida de basura, pasan a la generación siguiente.
-
Dar prioridad a los objetos más jóvenes: El recolector de basura se ejecuta con más frecuencia en los objetos más jóvenes (Generación 0) porque es más probable que estos objetos queden inutilizados rápidamente. A medida que los objetos envejecen y pasan a generaciones superiores, se recogen con menos frecuencia. Este enfoque reduce la sobrecarga de la recogida de basura, centrándose más en los objetos que probablemente se descartarán pronto.
-
Manejo de referencias cíclicas: La recogida de basura generacional es especialmente eficaz para identificar y recoger objetos implicados en referencias cíclicas. Durante el proceso de recolección, el recolector de basura de Python puede detectar estos ciclos y recuperar la memoria, evitando las fugas de memoria causadas por las referencias cíclicas.
Cómo activar manualmente la Recogida de Basura de Python
Aunque el sistema de recolección de basura de Python está diseñado para gestionar la memoria automáticamente, hay situaciones en las que puede ser útil gestionar manualmente la recolección de basura. Echemos un vistazo.
Iniciar la recogida de basura en Python
En la mayoría de los casos, el recolector de basura de Python se ejecuta automáticamente, limpiando los objetos no utilizados sin ninguna intervención. Sin embargo, hay situaciones en las que puedes querer activar manualmente la recogida de basura para liberar memoria. Esto es especialmente útil en aplicaciones de larga duración o en procesos que consumen mucha memoria, en los que el uso de la memoria debe gestionarse más estrechamente.
Para activar manualmente la recogida de basura en Python, puedes utilizar la función gc.collect()
. Esta función obliga al recolector de basura a ejecutarse inmediatamente, recuperando la memoria que ya no está en uso. Así es como funciona:
import gc
# Trigger garbage collection manually
gc.collect()
Cuando llames a gc.collect()
, el recolector de basura de Python realizará una recolección completa, examinando todos los objetos de la memoria y retirando los que ya no estén referenciados. Esto puede ser especialmente útil en situaciones como:
- Aplicaciones intensivas en memoria: En aplicaciones que procesan grandes cantidades de datos o crean muchos objetos, activar manualmente la recogida de basura puede ayudar a liberar memoria en puntos críticos, reduciendo el riesgo de sobrecarga de memoria.
- Procesos de larga duración: En servicios o aplicaciones que se ejecutan durante periodos prolongados, como servidores o tareas en segundo plano, gestionar manualmente la recogida de basura puede ayudar a mantener una huella de memoria estable, garantizando que la aplicación siga respondiendo y siendo eficiente a lo largo del tiempo.
Sin embargo, es importante utilizar gc.collect()
con criterio. La recogida manual frecuente de basura puede introducir una sobrecarga innecesaria, ya que el proceso puede consumir muchos recursos. Normalmente se utiliza mejor en situaciones concretas en las que has identificado posibles problemas de memoria o necesitas liberar memoria en un momento preciso.
Desactivar la recogida de basura
En algunos casos, puede resultarte beneficioso desactivar temporalmente la recogida automática de basura de Python. Esto puede ser útil en situaciones en las que la sobrecarga de la recogida de basura podría afectar negativamente al rendimiento, como en aplicaciones en tiempo real, secciones de código de rendimiento crítico o durante operaciones en las que quieras minimizar las interrupciones.
Para desactivar el recolector de basura, puedes utilizar la función gc.disable()
:
import gc
# Disable automatic garbage collection
gc.disable()
Cuando se desactiva la recogida de basura, Python dejará de recoger y reasignar automáticamente los objetos no utilizados. Esto puede dar lugar a un rendimiento más predecible en determinadas situaciones, ya que evita que el recolector de basura se ejecute inesperadamente durante operaciones críticas.
Sin embargo, desactivar la recogida de basura conlleva sus riesgos:
- Fugas de memoria: Sin la recogida de basura, los objetos no utilizados permanecen en memoria hasta que finaliza el proceso, lo que puede provocar fugas de memoria. Esto es especialmente problemático en aplicaciones de larga ejecución, donde el uso de memoria puede crecer sin control.
- Referencias cíclicas: Como las referencias cíclicas no se resuelven automáticamente sin la recolección de basura, desactivar el recolector de basura puede agravar los problemas de memoria si hay ciclos en tu código.
Por estas razones, es esencial volver a activar la recogida de basura después de que se haya ejecutado la sección de tu código cuyo rendimiento es crítico. Puedes volver a activar el recolector de basura utilizando gc.enable()
:
# Re-enable automatic garbage collection
gc.enable()
Veamos algunas buenas prácticas para la recogida manual de basura:
- Utilízalo con moderación: La recogida manual de basura sólo debe aplicarse cuando sea necesario. El uso excesivo puede provocar una degradación del rendimiento.
- Combínalo con Perfiles: Antes de activar o desactivar manualmente la recogida de basura, considera la posibilidad de elaborar un perfil de tu aplicación para conocer sus patrones de uso de la memoria. Esto puede ayudarte a determinar si es necesaria la intervención manual y dónde tendrá mayor impacto.
- Reactivar cuando sea necesario: Si desactivas la recogida de basura, recuerda volver a activarla en cuanto la sección crítica de código esté completa. Esto garantiza una gestión eficaz de la memoria a largo plazo.
Si sabes cómo y cuándo utilizar la recogida manual de basura, podrás controlar mejor la gestión de la memoria de tu aplicación. Esto no sólo ayuda a optimizar el rendimiento, sino que también evita problemas relacionados con la memoria que podrían afectar a la estabilidad de tu aplicación.
En la siguiente sección, exploraremos los problemas habituales de la recolección de basura en Python y daremos consejos prácticos para depurarlos y resolverlos.
Consideraciones prácticas para desarrolladores de Python
Gestionar la memoria de forma eficiente es fundamental para escribir aplicaciones Python eficaces. Aunque el sistema de recogida de basura de Python se encarga de la mayoría de las tareas de gestión de memoria de forma automática, hay pasos prácticos que los desarrolladores pueden dar para evitar errores comunes y optimizar el uso de la memoria. En esta sección, exploraremos estrategias para evitar fugas de memoria, tratar con referencias cíclicas y gestionar grandes cantidades de objetos en aplicaciones Python.
Evitar fugas de memoria
Las fugas de memoria se producen cuando los objetos que ya no se necesitan no se desasignan correctamente, haciendo que la aplicación consuma más memoria con el tiempo. Esto puede provocar una degradación del rendimiento o incluso hacer que la aplicación se bloquee. Para controlar los objetos en memoria, puedes utilizar el módulo gc
de Python. Si observas el número de objetos en memoria, puedes ver aumentos inesperados, que podrían indicar una fuga de memoria:
import gc
# Get a list of all objects tracked by the garbage collector
all_objects = gc.get_objects()
print(f"Number of tracked objects: {len(all_objects)}")
Si sospechas que un objeto no se está recogiendo de la basura, la función gc.get_referrers()
ayuda a identificar qué mantiene el objeto en la memoria. Esta función devuelve una lista de objetos que hacen referencia al objeto dado, permitiéndote determinar si estas referencias son necesarias o pueden eliminarse:
some_object = ...
referrers = gc.get_referrers(some_object)
print(f"Object is being referenced by: {referrers}")
Otra forma de evitar las fugas de memoria es utilizar referencias débiles, sobre todo en situaciones de almacenamiento en caché. El módulo weakref
te permite crear referencias que no aumenten el número de referencias del objeto:
import weakref
class MyClass:
pass
obj = MyClass()
weak_obj = weakref.ref(obj)
print(weak_obj()) # Access the object
del obj # Delete the strong reference
print(weak_obj()) # None, as the object is now collected
Tratar con referencias cíclicas
Las referencias cíclicas se producen cuando dos o más objetos se referencian entre sí, creando un bucle que impide que sus recuentos de referencias lleguen nunca a cero. Esto puede provocar fugas de memoria si el recolector de basura no es capaz de detectar y recoger estos objetos.
El recolector de basura generacional de Python está diseñado para manejar referencias cíclicas, pero sigue siendo mejor no crear ciclos innecesarios en primer lugar. Utilizando el módulo gc
, puedes detectar objetos que formen parte de ciclos de referencia activando una recogida y comprobando la lista gc.garbage
, que contiene objetos que forman parte de ciclos y no se han podido recoger:
import gc
# Trigger garbage collection and get the number of uncollectable objects
gc.collect()
uncollectable_objects = gc.garbage
print(f"Number of uncollectable objects: {len(uncollectable_objects)}")
Para evitar las referencias cíclicas, considera la posibilidad de romper el ciclo eliminando explícitamente las referencias cuando ya no sean necesarias. Puedes hacerlo poniendo la referencia en None
o utilizando referencias débiles, que son especialmente buenas para evitar los ciclos de referencia fuerte. Además, simplificar el diseño de tus estructuras de datos y relaciones entre objetos puede disminuir las posibilidades de crear ciclos.
Gestionar un gran número de objetos
Crear y destruir un gran número de objetos en poco tiempo puede poner a prueba el recolector de basura de Python. Piensa en aplicaciones que manejen grandes conjuntos de datos, procesen datos en tiempo real o realicen situaciones complejas.
Una idea es crear y eliminar objetos por lotes. En lugar de crear y destruir objetos de uno en uno, agrupar estas operaciones por lotes puede reducir la frecuencia de la recogida de basura y permitir que el recolector de basura trabaje con mayor eficacia. Por ejemplo:
objects = []
for i in range(1000):
obj = SomeClass()
objects.append(obj)
# Process all objects at once, then delete them
del objects[:]
gc.collect() # Optionally trigger a manual garbage collection
Otra consideración es optimizar el tiempo de vida de los objetos, garantizando que sean de vida corta o larga. Los objetos de vida corta se recogen rápidamente, y los de vida larga se trasladan a generaciones superiores, donde se recogen con menos frecuencia.
Para escenarios en los que los objetos se crean y destruyen con frecuencia, una reserva de objetos puede ser una técnica útil. Una reserva de objetos reutiliza un número fijo de objetos, lo que reduce la carga del recolector de basura y mejora el rendimiento en entornos con restricciones de memoria. Aquí tienes un ejemplo de implementación de una reserva de objetos:
class ObjectPool:
def __init__(self, size):
self.pool = [SomeClass() for _ in range(size)]
def get(self):
return self.pool.pop()
def release(self, obj):
self.pool.append(obj)
Las agrupaciones de objetos son especialmente beneficiosas en aplicaciones o juegos en tiempo real, donde el rendimiento es crítico, y la sobrecarga de la creación y destrucción frecuente de objetos puede ser significativa.
Conclusión
En este artículo, hemos explorado el sistema de recogida de basura de Python, abarcando desde cómo gestiona automáticamente la memoria hasta las intervenciones manuales que puedes realizar para mejorar el rendimiento. La combinación del recuento de referencias y la recolección generacional de basura permite a Python gestionar la memoria con eficacia, aunque hay ocasiones en las que la intervención manual es beneficiosa. Si sigues las prácticas recomendadas que se describen en esta guía, podrás evitar las fugas de memoria, mejorar el rendimiento y tener un mayor control sobre la forma en que tus aplicaciones gestionan la memoria.
Si quieres explorar temas más avanzados sobre la gestión de la memoria y la optimización general del código, tienes a tu disposición varios recursos estupendos de DataCamp. Puedes leer nuestros tutoriales Cómo escribir clases eficientes en memoria en Python y Perfiles de memoria en Python, ambos útiles para detectar cuellos de botella en el rendimiento. Además, aprender a escribir código Python más eficiente siempre es útil para cualquier carrera.
Conviértete en Ingeniero de Datos
Desarrolla tus habilidades en Python para convertirte en un ingeniero de datos profesional.
Escritora y profesional de los datos con experiencia a la que le apasiona capacitar a los aspirantes a expertos en el espacio de los datos.
Preguntas frecuentes sobre la recogida de basura en Python
¿Cómo puedo controlar el uso de memoria en las aplicaciones Python?
Utilizando herramientas como memory_profiler
, tracemalloc
, y objgraph
. Estas herramientas ayudan a rastrear el consumo de memoria, identificar las fugas de memoria y optimizar su uso.
¿Cómo puedo activar manualmente la recolección de basura en Python?
Puedes hacerlo utilizando el módulo gc
. Funciones como gc.collect()
te permiten forzar un ciclo de recogida de basura, lo que puede ser útil para depurar u optimizar el uso de la memoria en escenarios concretos.
¿Se puede desactivar la recogida de basura de Python?
Sí, puedes desactivar la recolección de basura de Python utilizando el módulo gc
. Llamando a gc.disable()
, puedes evitar que se ejecute el recolector de basura.
Aprende Python con DataCamp
Course
Writing Functions in Python
Course
Object-Oriented Programming in Python
tutorial
Las mejores técnicas para gestionar valores perdidos que todo científico de datos debe conocer
tutorial
Aprendizaje automático de datos categóricos con el tutorial de Python
tutorial
Tutorial de multiprocesamiento en Python
tutorial
Búsqueda binaria en Python: guía completa para una búsqueda eficiente
tutorial
21 herramientas esenciales de Python
tutorial