Making C Extensions More Pythonic by Andrew Dalke Listing One # Wrapper object to garbage collect the toolkit handle when no longer needed. import dayswig_python class smart_ptr: def __init__(self, handle): self.handle = handle def __del__(self, dt_dealloc = dayswig_python.dt_dealloc): dt_dealloc(self.handle) def __int__(self): return self.handle Listing Two # Getting and setting an atom's charge using (a) toolkit function calls and # (b) attributes. (c) shows how attributes are converted to function calls. # Part (a) print "The charge is", dt_charge(atom) dt_setcharge(atom, 1) # Part (b) print "The charge is", atom.charge atom.charge = 1 # Part (c) class Atom: def __init__(self, handle): self.handle = handle def __int__(self): return int(self.handle) def __getattr__(self, name): if name == "charge": return dt_charge(self.handle) elif name == "symbol": return dt_symbol(self.handle) raise AttributeError, name def __setattr__(self, name, val): if name == "charge": dt_setcharge(self.handle, val) elif name == "symbol": raise TypeError, "readonly attribute" else: self.__dict__[name] = val Listing Three # (a) Part of the dispatch table used in PyDaylight's base class. # (b) Derived class which adds atom-specific attributes. # Part (a) dayobject_properties = { "type": (dt_type, None), "typename": (dt_typename, None), "stringvalue": (dt_stringvalue, dt_setstringvalue), } class dayobject: __members__ = dayobject_properties.keys() _properties = dayobject_properties def __init__(self, handle): self.handle = handle return int(self.handle) def __getattr__(self, name): get_set = self._properties.get(name, None) if get_set is None: raise AttributeError, name return get_set[0](self.handle) def __setattr__(self, name, val): get_set = self._properties.get(name, None) if get_set is None: self.__dict__[name] = val else: set = get_set[1] if set is None: raise TypeError, "readonly attribute" set(self.handle, val) # Part (b) atom_properties = dayobject_properties.copy() atom_properties.update( { "charge": (dt_charge, dt_setcharge), "symbol": (dt_symbol, None), "weight": (dt_weight, dt_setweight), }) class Atom(dayobject): __members__ = atom_properties.keys() _properties = atom_properties Listing Four # Enforcing a toolkit dependency by deleting dependent objects first. class Path(dayobject): def __init__(self, path, mol): self.handle = path self.mol = mol # .. more initialization code .. def __del__(self): del self.handle del self.mol Listing Five # Converting from toolkit streams and sequences to a Python list. def toList(seq, converter = None): if not seq: return [] dt_reset(seq) result = [] while 1: element = dt_next(seq) if not element: return result if converter: result.append(converter(element)) else: result.append(element) Listing Six # A list-like class for forward iteration through toolkit streams. class Iterator: def __init__(self, handle, converter = None): self.handle = handle self._i = 0 self.converter = converter def __len__(self): return dt_count(self.handle, TYP_ANY) __nonzero__ = __len__ def __getitem__(self, i): if i != self._i: raise IndexError, "forward iteration only" element = dt_next(self.handle) if not element: raise IndexError, "list index out of range" self._i = i + 1 if self.converter: return self.converter(element) return element def next(self): try: return self.__getitem__(self._i) except IndexError: return None 3