Welcome, guest | Sign In | My Account | Store | Cart

This recipe is meant for Python classes wrapping ctypes bindings where a C-level finalizer must be invoked when the wrapper is destroyed. It uses weakref callbacks to avoid problems with __del__.

Python, 44 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
"""Finalization with weakrefs

This is designed for avoiding __del__.
"""

import sys
import traceback
import weakref


__author__ = "Benjamin Peterson <benjamin@python.org>"


class OwnerRef(weakref.ref):
    """A simple weakref.ref subclass, so attributes can be added."""
    pass


def _run_finalizer(ref):
    """Internal weakref callback to run finalizers"""
    del _finalize_refs[id(ref)]
    finalizer = ref.finalizer
    item = ref.item
    try:
        finalizer(item)
    except Exception:
        print("Exception running {}:".format(finalizer), file=sys.stderr)
        traceback.print_exc()


_finalize_refs = {}


def track_for_finalization(owner, item, finalizer):
    """Register an object for finalization.

    ``owner`` is the the object which is responsible for ``item``.
    ``finalizer`` will be called with ``item`` as its only argument when
    ``owner`` is destroyed by the garbage collector.
    """
    ref = OwnerRef(owner, _run_finalizer)
    ref.item = item
    ref.finalizer = finalizer
    _finalize_refs[id(ref)] = ref

C libraries often require that a function be explicitly called to close opened resources and free memory. When writing Python bindings with ctypes, its hard to render this pythonically because Python's garbage collector will not call the C finalizers for you. The usual solution is to write a __del__ on the wrapper class which closes the C resources. However, __del__ has quite a list of problems and caveats. This recipe avoids the problems of __del__ by using weakref callbacks.

To use this recipe, call track_for_finalization with owner of the C-level object, the C-level object itself, and the finalizer to call. When the owner is destroyed, the finalizer will be invoked with the C-level object as its sole argument.

The syntax is Python 3, but it should be a simple matter of using the print statement instead of the print function to use this on Python 2.

An example of a simple malloc wrapper:

import ctypes
import ctypes.util
import gc

import finalize


libc = ctypes.CDLL(ctypes.util.find_library("c"))
libc.malloc.argtypes = (ctypes.c_size_t,)
libc.malloc.restype = ctypes.c_void_p
libc.free.argtypes = (ctypes.c_void_p,)
libc.free.restype = None

def _free_mem(mem):
    print("Freeing memory at {}".format(mem))
    libc.free(mem)


class MemoryChunk:
    """A wrapper for a C level chunk of memory"""

    def __init__(self, size):
        self.memory = libc.malloc(size)
        finalize.track_for_finalization(self, self.memory, _free_mem)


def example():
    mem = MemoryChunk(64)
    del mem
    gc.collect()
example()

1 comment

AJ. Mayorga 13 years, 11 months ago  # | flag

This is great, definately going to help me with releasing handles in wininet calls THANKS! : )