lista - No entiendo este comportamiento python__del__
del variable python (7)
¿Alguien puede explicar por qué el siguiente código se comporta de la manera que lo hace?
import types
class Dummy():
def __init__(self, name):
self.name = name
def __del__(self):
print "delete",self.name
d1 = Dummy("d1")
del d1
d1 = None
print "after d1"
d2 = Dummy("d2")
def func(self):
print "func called"
d2.func = types.MethodType(func, d2)
d2.func()
del d2
d2 = None
print "after d2"
d3 = Dummy("d3")
def func(self):
print "func called"
d3.func = types.MethodType(func, d3)
d3.func()
d3.func = None
del d3
d3 = None
print "after d3"
La salida (tenga en cuenta que el destructor para d2 nunca se llama) es esto (python 2.7)
delete d1
after d1
func called
after d2
func called
delete d3
after d3
¿Hay alguna manera de "arreglar" el código para que se llame al destructor sin eliminar el método? Quiero decir, ¡el mejor lugar para poner el d2.func = None estaría en el destructor!
Gracias
[edit] Basándome en las primeras respuestas, me gustaría aclarar que no estoy preguntando sobre los méritos (o la falta de ellos) de usar __del__
. Traté de crear la función más corta que demostrara lo que considero un comportamiento no intuitivo. Supongo que se ha creado una referencia circular, pero no estoy seguro de por qué. Si es posible, me gustaría saber cómo evitar la referencia circular ...
En lugar de del , puede usar el operador with
.
http://effbot.org/zone/python-with-statement.htm
al igual que con los objetos de tipo de archivo, podrías hacer algo como
with Dummy(''d1'') as d:
#stuff
#d is guaranteed to be out of scope
Me parece que el verdadero corazón del asunto está aquí:
agregando las funciones es dinámico (en tiempo de ejecución) y no se conoce de antemano
Siento que lo que realmente buscas es una forma flexible de vincular diferentes funcionalidades a un objeto que representa el estado del programa. También conocido como polimorfismo. Python lo hace bastante bien, no mediante métodos de unión / separación, sino creando instancias de diferentes clases. Sugiero que vuelvas a mirar tu organización de clase. Quizás necesite separar un objeto de datos persistente y central de los objetos de estado transitorio. Utilice el paradigma has-a en lugar de is-a: cada vez que cambia el estado, envuelve los datos centrales en un objeto de estado o asigna el nuevo objeto de estado a un atributo del núcleo.
Si está seguro de que no puede usar ese tipo de POO pythonic, podría solucionar su problema de otra manera, definiendo todas sus funciones en la clase para comenzar y luego vinculándolas a atributos de instancia adicionales (a menos que esté compilando estas funciones sobre la marcha de la entrada del usuario):
class LongRunning(object):
def bark_loudly(self):
print("WOOF WOOF")
def bark_softly(self):
pring("woof woof")
while True:
d = LongRunning()
d.bark = d.bark_loudly
d.bark()
d.bark = d.bark_softly
d.bark()
No puede suponer que __del__
alguna vez se llamará, no es un lugar en el que esperar que los recursos se desasignaran automágicamente. Si desea asegurarse de que se libera un recurso (que no es de memoria), debe hacer una release()
o un método similar y luego llamarlo explícitamente (o usarlo en un administrador de contexto como lo señala Thanatos en los comentarios a continuación).
Por lo menos, deberías leer la documentation __del__
muy de cerca, y entonces probablemente no deberías tratar de usar __del__
. (También consulte la documentation gc.garbage
para conocer otras cosas malas sobre __del__
)
Proporciono mi propia respuesta porque, si bien agradezco los consejos para evitar __del__
, mi pregunta era cómo hacer que funcione correctamente para la muestra de código proporcionada.
Versión corta: el siguiente código usa weakref
para evitar la referencia circular. Pensé que había intentado esto antes de publicar la pregunta, pero creo que debo haber hecho algo mal.
import types, weakref
class Dummy():
def __init__(self, name):
self.name = name
def __del__(self):
print "delete",self.name
d2 = Dummy("d2")
def func(self):
print "func called"
d2.func = types.MethodType(func, weakref.ref(d2)) #This works
#d2.func = func.__get__(weakref.ref(d2), Dummy) #This works too
d2.func()
del d2
d2 = None
print "after d2"
Versión más larga : cuando publiqué la pregunta, busqué preguntas similares. Sé que puedes usarlo with
lugar, y que el sentimiento predominante es que __del__
es MALO .
Usar with
tiene sentido, pero solo en ciertas situaciones. Abrir un archivo, leerlo y cerrarlo es un buen ejemplo donde with
es una solución perfectamente buena. Has encontrado un bloque de código específico donde se necesita el objeto y quieres limpiar el objeto y el final del bloque.
Parece que a menudo se usa una conexión de base de datos como ejemplo que no funciona bien con with
, ya que generalmente se debe abandonar la sección de código que crea la conexión y se cierra la conexión de forma más orientada a eventos (en lugar de secuencial) periodo de tiempo.
Si with
no es la solución correcta, veo dos alternativas:
- Asegúrate de que
__del__
funcione (mira este blog para una mejor descripción del uso de weakref) - Utiliza el módulo
atexit
para ejecutar una devolución de llamada cuando se cierra el programa. Vea este tema por ejemplo.
Aunque traté de proporcionar un código simplificado, mi problema real está más orientado a los eventos, por lo que no es una solución adecuada ( with
está bien para el código simplificado). También quería evitar atexit
, ya que mi programa puede ser de larga duración y quiero realizar la limpieza lo antes posible.
Entonces, en este caso específico, considero que es la mejor solución para usar weakref
y evitar referencias circulares que impedirían que __del__
funcione.
Esto puede ser una excepción a la regla, pero hay casos de uso donde usar weakref
y __del__
es la implementación correcta, en mi humilde opinión.
Un ejemplo completo de un administrador de contexto.
class Dummy(object):
def __init__(self, name):
self.name = name
def __enter__(self):
return self
def __exit__(self, exct_type, exce_value, traceback):
print ''cleanup:'', d
def __repr__(self):
return ''Dummy(%r)'' % (self.name,)
with Dummy("foo") as d:
print ''using:'', d
print ''later:'', d
Una solución alternativa al uso de weakref
es vincular dinámicamente la función a la instancia solo cuando se la llama anulando __getattr__
o __getattribute__
en la clase para devolver func.__get__(self, type(self))
lugar de solo func
para funciones vinculadas al ejemplo. Así es como se comportan las funciones definidas en la clase. Desafortunadamente (para algunos casos de uso), Python no realiza la misma lógica para las funciones asociadas a la instancia en sí, pero puede modificarlo para hacer esto. He tenido problemas similares con descriptores vinculados a instancias. Es probable que el rendimiento aquí no sea tan bueno como usar weakref
, pero es una opción que funcionará de forma transparente para cualquier función asignada dinámicamente con el uso de solo builtins de python.
Si te encuentras haciendo esto a menudo, es posible que desees una metaclase personalizada que realice el enlace dinámico de las funciones de nivel de instancia.
Otra alternativa es agregar la función directamente a la clase, que luego realizará correctamente el enlace cuando se llame. Para muchos casos de uso, esto tendría algunos dolores de cabeza involucrados: a saber, el espacio de nombres correcto para que no colisionen. Sin embargo, la ID de instancia se puede usar para esto, ya que la identificación en cPython no se garantiza de manera única durante la vida útil del programa, necesitará reflexionar un poco sobre esto para asegurarse de que funcione para su caso de uso ... en en particular, es probable que deba asegurarse de eliminar la función de clase cuando un objeto se sale del alcance y, por lo tanto, su dirección de id / memoria vuelve a estar disponible. __del__
es perfecto para esto :). De forma alternativa, puede borrar todos los métodos nombrados a la instancia en la creación del objeto (en __init__
o __new__
).
Otra alternativa (en lugar de jugar con los métodos de magia de pitón) es agregar explícitamente un método para llamar a sus funciones vinculadas dinámicamente. Esto tiene la desventaja de que los usuarios no pueden llamar a su función utilizando la sintaxis normal de Python:
class MyClass(object):
def dynamic_func(self, func_name):
return getattr(self, func_name).__get__(self, type(self))
def call_dynamic_func(self, func_name, *args, **kwargs):
return getattr(self, func_name).__get__(self, type(self))(*args, **kwargs)
"""
Alternate without using descriptor functionality:
def call_dynamic_func(self, func_name, *args, **kwargs):
return getattr(self, func_name)(self, *args, **kwargs)
"""
Solo para completar esta publicación, mostraré tu opción de weakref
también:
import weakref
inst = MyClass()
def func(self):
print ''My func''
# You could also use the types modules, but the descriptor method is cleaner IMO
inst.func = func.__get__(weakref.ref(inst), type(inst))
del
no llama __del__
del
modo en que está utilizando elimina una variable local. __del__
se __del__
cuando el objeto se destruye. Python como lenguaje no garantiza cuándo destruirá un objeto.
CPython como la implementación más común de Python, utiliza el recuento de referencias. Como resultado, del funcionará a menudo como usted espera. Sin embargo, no funcionará en el caso de que tenga un ciclo de referencia.
d3 -> d3.func -> d3
Python no detecta esto y no lo limpiará de inmediato. Y no son solo los ciclos de referencia. Si se lanza una excepción, es probable que desee llamar a su destructor. Sin embargo, Python normalmente se aferrará a las variables locales como parte de su rastreo.
La solución es no depender del método __del__
. Más bien, use un administrador de contexto.
class Dummy:
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
print "Destroying", self
with Dummy() as dummy:
# Do whatever you want with dummy in here
# __exit__ will be called before you get here
Esto está garantizado para funcionar, e incluso puede verificar los parámetros para ver si está manejando una excepción y hacer algo diferente en ese caso.