From e3461870435508e39dc05e13f08087070b9a43e2 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 18 Jun 2011 13:34:52 +0100 Subject: [PATCH] new python cleanups seems to work now, try an operation next --- python/finalizable.py | 105 ++++++++++++++++++++++++++++++++++++++ python/finalize.py | 41 --------------- python/test.py | 26 +++++++++- python/vipsimage.py | 115 ++++++++++++++++++++++++++++++++++++++++-- python/vipsobject.py | 46 ++++++++++++----- 5 files changed, 272 insertions(+), 61 deletions(-) create mode 100644 python/finalizable.py delete mode 100644 python/finalize.py diff --git a/python/finalizable.py b/python/finalizable.py new file mode 100644 index 00000000..f86f3141 --- /dev/null +++ b/python/finalizable.py @@ -0,0 +1,105 @@ +#!/usr/bin/python + +import weakref + +class Finalizable(object): + """ + Base class enabling the use a __finalize__ method without all the + problems associated with __del__ and reference cycles. + + If you call enable_finalizer(), it will call __finalize__ + with a single "ghost instance" argument after the object has been + deleted. Creation of this "ghost instance" does not involve calling + the __init__ method, but merely copying the attributes whose names + were given as arguments to enable_finalizer(). + + A Finalizable can be part of any reference cycle, but you must be careful + that the attributes given to enable_finalizer() don't reference back to + self, otherwise self will be immortal. + """ + + __wr_map = {} + __wr_id = None + + def bind_finalizer(self, *attrs): + """ + Enable __finalize__ on the current instance. + The __finalize__ method will be called with a "ghost instance" as + single argument. + This ghost instance is constructed from the attributes whose names + are given to bind_finalizer(), *at the time bind_finalizer() is called*. + """ + cls = type(self) + ghost = object.__new__(cls) + ghost.__dict__.update((k, getattr(self, k)) for k in attrs) + cls_wr_map = cls.__wr_map + def _finalize(ref): + try: + ghost.__finalize__() + finally: + del cls_wr_map[id_ref] + ref = weakref.ref(self, _finalize) + id_ref = id(ref) + cls_wr_map[id_ref] = ref + self.__wr_id = id_ref + + def remove_finalizer(self): + """ + Disable __finalize__, provided it has been enabled. + """ + if self.__wr_id: + cls = type(self) + cls_wr_map = cls.__wr_map + del cls_wr_map[self.__wr_id] + del self.__wr_id + + +class TransactionBase(Finalizable): + """ + A convenience base class to write transaction-like objects, + with automatic rollback() on object destruction if required. + """ + + finished = False + + def enable_auto_rollback(self): + self.bind_finalizer(*self.ghost_attributes) + + def commit(self): + assert not self.finished + self.remove_finalizer() + self.do_commit() + self.finished = True + + def rollback(self): + assert not self.finished + self.remove_finalizer() + self.do_rollback(auto=False) + self.finished = True + + def __finalize__(ghost): + ghost.do_rollback(auto=True) + + +class TransactionExample(TransactionBase): + """ + A transaction example which close()s a resource on rollback + """ + ghost_attributes = ('resource', ) + + def __init__(self, resource): + self.resource = resource + self.enable_auto_rollback() + + def __str__(self): + return "ghost-or-object %s" % object.__str__(self) + + def do_commit(self): + pass + + def do_rollback(self, auto): + if auto: + print "auto rollback", self + else: + print "manual rollback", self + self.resource.close() diff --git a/python/finalize.py b/python/finalize.py deleted file mode 100644 index 6103648d..00000000 --- a/python/finalize.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/python - -# Finalization with weakrefs -# This is designed for avoiding __del__. - - -import sys -import traceback -import logging -import weakref - -__author__ = "Benjamin Peterson " - -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 as e: - logging.debug("Exception %s running %s", repr(e), format(finalizer)) - traceback.print_exc() - -_finalize_refs = {} - -def track(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 diff --git a/python/test.py b/python/test.py index 9e8a5d93..1d35a8ad 100755 --- a/python/test.py +++ b/python/test.py @@ -1,14 +1,36 @@ #!/usr/bin/python import logging +import gc import vipsimage logging.basicConfig(level = logging.DEBUG) +# test unref +for i in range (1,10): + a = vipsimage.VipsImage('/home/john/pics/healthygirl.jpg') + # should work a = vipsimage.VipsImage('/home/john/pics/healthygirl.jpg') -a.write('x.png') +print 'width =', a.width() +print 'height =', a.height() +print 'bands =', a.bands() +print 'format =', vipsimage.VipsBandFormat.name(a.format()) +print 'coding =', vipsimage.VipsCoding.name(a.coding()) +print 'interpretation =', vipsimage.VipsInterpretation.name(a.interpretation()) +print 'xres =', a.xres() +print 'yres =', a.yres() +print 'xoffset =', a.xoffset() +print 'yoffset =', a.yoffset() # should raise an error -a = vipsimage.VipsImage('banana') +# a = vipsimage.VipsImage('banana') + +print 'starting shutdown ...' +del a +# sometimes have to do several GCs to get them all, not sure why +for i in range(10): + gc.collect () +print 'shutdown!' + diff --git a/python/vipsimage.py b/python/vipsimage.py index 804b3e30..d1b3d944 100644 --- a/python/vipsimage.py +++ b/python/vipsimage.py @@ -11,22 +11,95 @@ import ctypes import vipsobject +# image enums +class VipsDemandStyle: + SMALLTILE = 0 + FATSTRIP = 1 + THINSTRIP = 2 + ANY = 3 + + # turn 3 into 'ANY', handy for printing + # is there a clever way to define this in a base Enum class? I can't think + # of it + @staticmethod + def name(value): + return vipsobject.class_value(VipsDemandStyle, value) + +class VipsInterpretation: + MULTIBAND = 0 + B_W = 1 + HISTOGRAM = 10 + FOURIER = 24 + XYZ = 12 + LAB = 13 + CMYK = 15 + LABQ = 16 + RGB = 17 + UCS = 18 + LCH = 19 + LABS = 21 + sRGB = 22 + YXY = 23 + RGB16 = 25 + GREY16 = 26 + + @staticmethod + def name(value): + return vipsobject.class_value(VipsInterpretation, value) + +class VipsBandFormat: + NOTSET = -1 + UCHAR = 0 + CHAR = 1 + USHORT = 2 + SHORT = 3 + UINT = 4 + INT = 5 + FLOAT = 6 + COMPLEX = 7 + DOUBLE = 8, + DPCOMPLEX = 9 + LAST = 10 + + @staticmethod + def name(value): + return vipsobject.class_value(VipsBandFormat, value) + +class VipsCoding: + NONE = 0 + LABQ = 2 + RAD = 6 + + @staticmethod + def name(value): + return vipsobject.class_value(VipsCoding, value) + libvips = vipsobject.libvips vips_image_new = libvips.vips_image_new -vips_image_new.restype = vipsobject.check_pointer_return +vips_image_new.restype = ctypes.c_void_p +vips_image_new.errcheck = vipsobject.check_pointer_return vips_image_new_from_file = libvips.vips_image_new_from_file -vips_image_new_from_file.restype = vipsobject.check_pointer_return vips_image_new_from_file.argtypes = [ctypes.c_char_p] +vips_image_new_from_file.restype = ctypes.c_void_p +vips_image_new_from_file.errcheck = vipsobject.check_pointer_return vips_image_new_mode = libvips.vips_image_new_mode -vips_image_new_mode.restype = vipsobject.check_pointer_return vips_image_new_mode.argtypes = [ctypes.c_char_p, ctypes.c_char_p] +vips_image_new_mode.restype = ctypes.c_void_p +vips_image_new_mode.errcheck = vipsobject.check_pointer_return vips_image_write = libvips.vips_image_write -vips_image_write.restype = vipsobject.check_int_return vips_image_write.argtypes = [ctypes.c_void_p, ctypes.c_char_p] +vips_image_write.restype = ctypes.c_void_p +vips_image_write.errcheck = vipsobject.check_int_return + +vips_image_get_xres = libvips.vips_image_get_xres +vips_image_get_xres.restype = ctypes.c_double; + +vips_image_get_yres = libvips.vips_image_get_yres +vips_image_get_yres.restype = ctypes.c_double; class VipsImage(vipsobject.VipsObject): """Manipulate a libvips image.""" @@ -43,6 +116,40 @@ class VipsImage(vipsobject.VipsObject): else: self.vipsobject = vips_image_new_mode(filename, mode) + logging.debug('vipsimage: made %s' % hex(self.vipsobject)) + + self.enable_finalize() + + def width(self): + return libvips.vips_image_get_width(self.vipsobject) + + def height(self): + return libvips.vips_image_get_height(self.vipsobject) + + def bands(self): + return libvips.vips_image_get_bands(self.vipsobject) + + def format(self): + return libvips.vips_image_get_format(self.vipsobject) + + def coding(self): + return libvips.vips_image_get_coding(self.vipsobject) + + def interpretation(self): + return libvips.vips_image_get_interpretation(self.vipsobject) + + def xres(self): + return vips_image_get_xres(self.vipsobject) + + def yres(self): + return vips_image_get_yres(self.vipsobject) + + def xoffset(self): + return libvips.vips_image_get_xoffset(self.vipsobject) + + def yoffset(self): + return libvips.vips_image_get_yoffset(self.vipsobject) + def write(self, filename): vips_image_write(self.vipsobject, filename) diff --git a/python/vipsobject.py b/python/vipsobject.py index 480ee4fa..6a47183c 100644 --- a/python/vipsobject.py +++ b/python/vipsobject.py @@ -12,12 +12,21 @@ import logging import sys import ctypes -import finalize +import finalizable # .15 is 7.25+ with the new vips8 API libvips = ctypes.CDLL('libvips.so.15') libvips.vips_init(sys.argv[0]) +# given a class and value, search for a class member with that value +# handy for enum classes, use to turn numbers to strings +def class_value(classobject, value): + for name in dir(classobject): + if getattr(classobject, name) == value: + return classobject.__name__ + '.' + name + + return 'unknown' + class Error(Exception): """An error from libvips. @@ -31,36 +40,45 @@ class Error(Exception): self.detail = vips_error_buffer() libvips.vips_error_clear() - logging.debug('vipsobject: Error %s %s', self.message, self.detail) + logging.debug('vipsobject: Error: %s %s', self.message, self.detail) def __str__(self): - return '%s - %s' %(self.message, self.detail) + return '%s %s' %(self.message, self.detail) -def check_int_return(value): - if value != 0: - raise Error('Error calling vips function.') - return value +# handy checkers, assign to errcheck +def check_int_return(result, func, args): + if result != 0: + raise Error('Error calling vips function %s.' % func.__name__) + return result -def check_pointer_return(value): - if value == None: - raise Error('Error calling vips function.') - return value +def check_pointer_return(result, func, args): + if result == None: + raise Error('Error calling vips function %s.' % func.__name__) + return result vips_error_buffer = libvips.vips_error_buffer vips_error_buffer.restype = ctypes.c_char_p -class VipsObject: +class VipsObject(finalizable.Finalizable): """Abstract base class for libvips.""" - def unref_vips(self): + # attributes we finalize + ghost_attributes = ('vipsobject', ) + + def __finalize__(self): + logging.debug('vipsobject: __finalize__') + if self.vipsobject != None: + logging.debug('vipsobject: unref %s' % hex(self.vipsobject)) libvips.g_object_unref(self.vipsobject) self.vipsobject = None + def enable_finalize(self): + self.bind_finalizer(*self.ghost_attributes) + def __init__(self): logging.debug('vipsobject: init') self.vipsobject = None - finalize.track(self, self, self.unref_vips)