new python cleanups

seems to work now, try an operation next
This commit is contained in:
John Cupitt 2011-06-18 13:34:52 +01:00
parent 3628692799
commit e346187043
5 changed files with 272 additions and 61 deletions

105
python/finalizable.py Normal file
View File

@ -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()

View File

@ -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 <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 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

View File

@ -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!'

View File

@ -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)

View File

@ -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)