Comprensión de Pools de Objetos Enteros y Mecanismo Interno
Pools de Enteros Pequeños
Los enteros se utilizan ampliamente en los programas. Para optimizar el rendimiento, Python emplea un pool de objetos para enteros pequeños, evitando la asignación y liberación frecuentes de memoria.
Python define enteros pequeños en el rango [-5, 257). Estos objetos enteros se crean de antemano y no se recolectan como basura. En un programa Python, todos los enteros dentro de este rango comparten el mismo objeto.
De manera similar, los caracteres individuales también siguen este patrón. Sin embargo, cuando se definen dos cadenas idénticas, su conteo de referencias es 0, lo que activa la recolección de basura.
Pools de Enteros Grandes
En un terminal, cada ejecución crea nuevos objetos, mientras que en entornos como PyCharm, todo el código se carga en memoria como una unidad. Esto permite un pool de objetos para enteros grandes dentro del mismo bloque de código, compartiendo objetos idénticos.
Mecanismo Interno
Consideremos el siguiente ejemplo de código:
cadena1 = "PythonGC"
cadena2 = "PythonGC"
cadena3 = "PythonGC"
cadena4 = "PythonGC"
cadena5 = "PythonGC"
cadena6 = "PythonGC"
cadena7 = "PythonGC"
cadena8 = "PythonGC"
cadena9 = "PythonGC"
Python no crea nueve objetos separados; utiliza el mecanismo interno para que todas las variables apunten a la misma instancia en memoria. Esto se basa en el conteo de referencias para determinar cuándo liberar el objeto.
Recolección de Basura (GC) en Python
Lenguajes modernos como Java o C# utilizan mecanismos de recolección de basura, a diferenica de C o C++ donde la gestión de memoria es manual. Python adopta un enfoque híbrido: el conteo de referencias como método principal, complementado con recolección generacional y marcado-eliminación.
Conteo de Referencias
En Python, cada objeto tiene un contador de referencias integrado en su estructura. Este contador aumenta cuando se crea una nueva referencia y disminuye cuando se elimina una referencia.
Por ejemplo, la estructura de un objeto puede definirse conceptualmente como:
struct ObjetoPy {
int contador_ref;
struct tipo_objeto *tipo;
};
Cuando el contador de referencias llega a cero, el objeto se libera automáticamente.
Ventajas:
- Simplicidad en la implementación.
- Liberación inmediata de memoria cuando no hay referencias.
Desventajas:
- Consumo de recursos para mantener el conteo.
- Problemas con referencias circulares.
Referencias Circulares
Las referencias circulares ocurren cuando dos o más objetos se refieren entre sí, manteniendo sus contadores de referencias por encima de cero incluso cuando ya no están en uso. Por ejemplo:
listaA = []
listaB = []
listaA.append(listaB)
listaB.append(listaA)
Esto provoca fugas de memoria, ya que los objetos no se pueden liberar automáticamente.
Recolección Generacional
Para manejar referencias circulares, Python utiliza recolección generacional. Los objetos se dividen en generaciones (0, 1, 2), donde los objetos nuevos se asignan a la generación 0. Los objetos que sobreviven a la recolección se promocionan a generaciones superiores.
El recolector de basura ejecuta análisis más frecuentes en generaciones más jóvenes, basado en la hipótesis de que los objetos nuevos tienden a ser de corta vida.
Módulo gc
El módulo gc proporciona funciones para interactuar con el recolector de basura. Se utiliza principalmente para manejar referencias circulares.
Conteo de Referencias en Acción
Para observar el conteo de referencias, se puede usar el módulo sys:
import sys
objeto = "ejemplo"
print(sys.getrefcount(objeto)) # Muestra el conteo, incluyendo la referencia de la función
Fugas de Memia por Referencias Circulares
El siguiente ejemplo demuestra una fuga de memoria cuando el recolector de basura está deshabilitado:
import gc
class ClaseDemo:
def __init__(self):
print(f'Objeto creado, id: {hex(id(self))}')
def funcion_fuga():
while True:
instancia1 = ClaseDemo()
instancia2 = ClaseDemo()
instancia1.sig = instancia2
instancia2.sig = instancia1
del instancia1
del instancia2
gc.disable()
funcion_fuga()
Uso del Módulo gc
El módulo gc ofrece funciones como collect() para iniciar la recolección y set_threshold() para ajustar los umbrales de recolección automática.
import gc
class ClaseDemo:
def __init__(self):
print(f'Objeto creado, id: {hex(id(self))}')
def demostrar_gc():
gc.set_debug(gc.DEBUG_LEAK)
obj1 = ClaseDemo()
obj2 = ClaseDemo()
obj1.sig = obj2
obj2.sig = obj1
del obj1
del obj2
print("Basura no alcanzable:", gc.collect())
print("Objetos en gc.garbage:", gc.garbage)
if __name__ == '__main__':
demostrar_gc()
La recolección automática se activa cuando se importa gc y está habilitada. Los objetos con métodos __del__ pueden causar problemas con la recolección de basura, por lo que se recomienda evitarlos cuando sea posible.