Pregunta

Tengo algunos enlaces de ctypes, y para cada cuerpo. Ahora debería llamar a body.Free. La biblioteca que estoy vinculando no tiene rutinas de asignación aisladas del resto del código (se puede llamar en cualquier lugar allí), y para usar un par de características útiles necesito hacer referencias cíclicas.

Creo que resolvería si encontrara una manera confiable de enganchar el destructor a un objeto. (débiles ayudarían si me devolvieran la llamada justo antes de que se eliminen los datos.

Entonces, obviamente, este código falla cuando pongo velocity_func:

class Body(object):
    def __init__(self, mass, inertia):
        self._body = body.New(mass, inertia)

    def __del__(self):
        print '__del__ %r' % self
        if body:
            body.Free(self._body)

    ...        

    def set_velocity_func(self, func):
        self._body.contents.velocity_func = ctypes_wrapping(func)

También intenté resolverlo a través de puntos débiles, con los cuales las cosas parecen empeorar, solo que en gran medida más impredecibles.

Incluso si no pongo velocity_func, aparecerán ciclos al menos cuando haga esto:

class Toy(object):
    def __init__(self, body):
        self.body.owner = self

...

def collision(a, b, contacts):
    whatever(a.body.owner)

Entonces, ¿cómo asegurarse de que las estructuras se recolectarán, incluso si la biblioteca compartida las asigna / libera?

Hay repositorio si está interesado en obtener más información: http://bitbucket.org/cheery/ ctypes-chipmunk /

¿Fue útil?

Solución 3

Si los puntos débiles no están rotos, supongo que esto puede funcionar:

from weakref import ref

pointers = set()

class Pointer(object):
    def __init__(self, cfun, ptr):
        pointers.add(self)
        self.ref = ref(ptr, self.cleanup)
        self.data = cast(ptr, c_void_p).value # python cast it so smart, but it can't be smarter than this.
        self.cfun = cfun

    def cleanup(self, obj):
        print 'cleanup 0x%x' % self.data
        self.cfun(self.data)
        pointers.remove(self)

def cleanup(cfun, ptr):
    Pointer(cfun, ptr)

Todavía lo intento. La pieza importante es que el puntero no tiene referencias fuertes al puntero extraño, excepto un número entero. Esto debería funcionar si ctypes no libera memoria que debería liberar con los enlaces. Sí, es básicamente un truco, pero creo que puede funcionar mejor que las cosas anteriores que he estado intentando.

Editar: Probé, y parece funcionar después de un pequeño ajuste fino de mi código. Una cosa sorprendente es que incluso si obtuve del de todas mis estructuras, parece que todavía falla. Interesante pero frustrante.

Ninguno de los dos funciona, por alguna extraña posibilidad, he podido eliminar referencias cíclicas en algunos lugares, pero las cosas siguen en quiebra.

Editar: Bueno ... ¡los débiles se rompieron después de todo! por lo tanto, es probable que no haya una solución para una limpieza confiable en python, excepto forzarla a ser explícita.

Otros consejos

Lo que quieres hacer, es crear un objeto que asigne cosas y luego se desasigne automáticamente cuando el objeto ya no esté en uso, es casi imposible en Python, desafortunadamente. No se garantiza que se invoque la declaración del , por lo que no puede confiar en eso.

La forma estándar en Python es simplemente:

try:
    allocate()
    dostuff()
finally:
    cleanup()

O desde 2.5 también puede crear administradores de contexto y usar la instrucción with, que es una forma más ordenada de hacerlo.

Pero ambos son principalmente para cuando asigna / bloquea al comienzo de un fragmento de código. Si desea tener cosas asignadas para toda la ejecución del programa, debe asignar el recurso al inicio, antes de que se ejecute el código principal del programa, y ??luego desasignarlo. Hay una situación que no se trata aquí, y es cuando desea asignar y desasignar muchos recursos dinámicamente y usarlos en muchos lugares del código. Por ejemplo, si desea un grupo de búferes de memoria o similar. Pero la mayoría de esos casos son para la memoria, que Python se encargará de usted, por lo que no tiene que preocuparse por eso. Por supuesto, hay casos en los que desea tener una asignación dinámica de grupo de cosas que NO son memoria, y luego desea el tipo de desasignación que intenta en su ejemplo, y que es difícil de hacer con Python .

En CPython, __del__ es un destructor confiable de un objeto, porque siempre se llamará cuando el recuento de referencia llegue a cero (nota: puede haber casos como referencias circulares de elementos con el método __del__ definido, donde el recuento de referencias nunca llegará a cero, pero ese es otro problema).

Actualizar Según los comentarios, entiendo que el problema está relacionado con el orden de destrucción de los objetos: cuerpo es un objeto global, y se está destruyendo antes que todos los demás objetos, por lo que ya no está disponible para ellos.
En realidad, usar objetos globales no es bueno; no solo por problemas como este, sino también por mantenimiento.

Luego cambiaría tu clase con algo como esto

class Body(object):
    def __init__(self, mass, inertia):
        self._bodyref = body
        self._body = body.New(mass, inertia)

    def __del__(self):
        print '__del__ %r' % self
        if body:
            body.Free(self._body)

...        

def set_velocity_func(self, func):
    self._body.contents.velocity_func = ctypes_wrapping(func)

Un par de notas:

  1. El cambio solo agrega una referencia al objeto body global, que por lo tanto vivirá al menos tanto como todos los objetos derivados de esa clase.
  2. Aún así, el uso de un objeto global no es bueno debido a las pruebas unitarias y el mantenimiento; mejor sería tener una fábrica para el objeto, que establecerá el "cuerpo" correcto a la clase, y en caso de prueba unitaria pondremos fácilmente un objeto simulado. Pero eso depende de ti y de cuánto esfuerzo creas que tiene sentido en este proyecto.
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top