Contador de referencias.

Chema Cortés py en ch3m4.org
Lun Mayo 10 19:01:14 CEST 2004


El Lunes, 10 de Mayo de 2004 15:03, Pepe Aracil escribió:

> Tengo algunas preguntillas para quedarme tranquilo si alguna vez me dejo
> referencias circulares sin depurar.
>
> ¿Se encarga el gc de purgar todos los objetos a los que ya no se puede
> acceder o tendré que lanzar un proceso "manual"?

Si te refieres a si hay diferencia alguna entre automático o manual, ninguna 
diferencia, a no ser que configures el gc para que no recolecte (con fines de 
depuración, claro está)

> ¿Habeis tenido problemas de memoria porque el gc no ha actuado como se
> esperaba?

Pues sí, y es un problema grave de los recolectores de basura, no sólo de 
python.

Por ejemplo, es un error común cuando usas varias ventanas, donde los 
controles de una ventana están enlazados con los controles de otra, lo que 
impide la destrucción de la ventana que enlaza y de todos los controles que 
la componen. A medida que se ejecuta la aplicación, la memoria se va llenando 
de ventanas y controles que no hay quien los elimine.

Los recolectores de basura están provistos de algoritmos que manejan lo mejor 
que pueden las referencias circulares, por lo que casi se puede decir que 
cuando surgen problemas hay que buscar primero en el diseño de la aplicación 
que se esté haciendo. Aún así, hay una situación irresoluble que ocurre 
cuando las referencias circulares se establecen entre objetos que tienen su 
propio destructor (método __del__) ya que no se puede saber el orden en que 
hay que proceder a destruirlos. El gc simplemente los va acumulando en un 
contenedor llamado 'gc.garbage'

Un ejemplo:

>>> class P:
...   def __del__(self): print "BORRAR"
...
>>> p=P()
>>> q=P()
>>> p.x=q
>>> q.x=p
>>> del p,q
>>>

No hay mensaje, luego no ha destruído ningún objeto. Veamos dónde paran:

>>> import gc
>>> gc.collect ()
4
>>> gc.garbage
[<__main__.P instance at 0x402f8cac>, <__main__.P instance at 0x402f8ecc>]
>>>

A pesar de que hemos forzado la recolección de basura y que se han obtenido 4 
referencias a destruir, seguimos sin ver los mensajes del destructor; las 
referencias están guardadas en gc.garbage y los objetos no han sido 
destruídos. Si esto fuera una aplicación más o menos compleja (eg: zope) al 
final tendríamos un objeto gc.garbage gigantesco malgastando memoria, y lo 
peor es que no hay forma de liberar esta memoria de ninguna manera sin salir 
de la aplicación. En la documentación habla de hacer 'del gc.garbage[:]' pero 
no funciona, y con cualquier cosa que haya probado como 'gc.garbage=[]'.

Por eso, si no queda más remedio y hay que usar el método __del__ en una 
clase, la forma de evitarse problemas es emplear referencias débiles:

>>> import weakref
>>> class P:
...   def __del__(self):print "BORRAR"
...   def wref(self): return weakref.proxy(self)
...
>>> p=P()
>>> q=P()
>>> p.x=q.wref()
>>> q.x=p.wref()
>>>
>>> del p,q
BORRAR
BORRAR


Y mucho cuidado a partir de aquí, ya que las referencias débiles quedan 
invalidadas en el momento que se destruyan las referencias fuertes, como 
ocurrirá sin darnos cuenta cuando se trabaja con variables locales.



Lectura aconsejable:
"PythonFAQ: ¿Cómo maneja python la memoria?":
http://www.python.org/doc/faq/es/general.html#id43




Más información sobre la lista de distribución Python-es