from types import FunctionType import os def automethod(func): """Return func unchanged, wrapped in a staticmethod or in a classmethod, depending on what it seems to be (from parsing argument names)""" if not func.func_code.co_argcount: return staticmethod(func) if func.func_code.co_varnames[0] == 'cls': return classmethod(func) if func.func_code.co_varnames[0] == 'self': return func return staticmethod(func) class cachingprop(object): __slots__ = ["_name", "_fget"] """ A read-only property() that caches the value on the instance Only supports the 'getter' function, not setter/delete and __doc__, and doesn't work on objects without a __dict__. """ def __init__(self, name, fget): self._name = name self._fget = fget def __get__(self, inst, type=None): if inst is None: return self v = self._fget(inst) inst.__dict__[self._name] = v return v class conveniencyTypeType(type): """ Conveniency metaclass: save typing by automatically creating classmethods and properties, triggered by the naming scheme. For each method (Python function) in the newly created class: - if it has no arguments, convert it to a staticmethod - if the first argument is named 'cls', convert it to a classmethod - otherwise, if the first argument is not 'self', it's a staticmethod - if the name matches _cacheget_*, * is the name of a 'cachingproperty', and the result of the _cacheget_ function is automatically placed in the instance's __dict__. - if the name matches _get_*, _set_*, _del_* or _doc_*, * is the name of a regular property. Inheritance is obeyed for these attributes; you can meaningfully 'extend' an existing property by overriding some of these functions. Also, create an 'unbound super object' as class.__super. """ def __init__(self, name, bases, attrs): super(conveniencyTypeType, self).__init__(name, bases, attrs) # Create an 'unbound super object' supername = "_%s__super" % name if hasattr(self, supername): raise TypeError, "Conflicting classname " + supername setattr(self, supername, super(self)) props = {} # Process all attributes for attr, value in attrs.items(): if attr.startswith("__") and attr.endswith("__"): continue if isinstance(value, FunctionType): # Auto-convert methods setattr(self, attr, automethod(value)) # Find and wrap "caching properties" if attr.startswith("_cacheget_"): if not hasattr(self, "__dict__"): raise TypeError, "Can't use _cacheget_ on " \ "objects without __dict__" propname = attr[10:] setattr(self, propname, cachingprop(propname, value)) if attr[:10] in ("_cacheset_", "_cachedelete_", "_cachedoc_"): raise TypeError, "_cacheset_, _cachedelete_ and _cachedoc_" \ " are not supported" # Collect normal properties... if attr[:5] in ("_get_", "_set_", "_del_", "_doc_"): props[attr[5:]] = 1 # ... and fetch them using getattr() (for inheritance) for name in props.keys(): fget = getattr(self, "_get_" + name, None) fset = getattr(self, "_set_" + name, None) fdel = getattr(self, "_del_" + name, None) fdoc = getattr(self, "_doc_" + name, None) setattr(self, name, property(fget, fset, fdel, fdoc)) class conveniencyType(object): __slots__ = [] __metaclass__ = conveniencyTypeType class automethodTest(conveniencyType): def spam(self, *args): return self, args def ham(cls, *args): return cls, args def eggs(*args): return args class Bag(conveniencyType): def __init__(self, dirname): self._dirname = dirname def _cacheget_spam(self): return file(os.path.join(self._dirname, "spam.txt")).read() def _cacheget_ham(self): print "Reaching in bag for ham" return file(os.path.join(self._dirname, "ham.txt")).read() class mydict(dict): __metaclass__ = conveniencyTypeType def __setitem__(self, item, value): print "Setting item", "to", value return self.__super.__setitem__(item, value) class tristate(int): # Specify an (empty) __slots__ so instances don't get a __dict__ # -- just like regular ints, they won't have arbitrary attributes. __slots__ = [] __metaclass__ = conveniencyTypeType nstates = 3 # __new__ is the method to override for immutable values. # It is a static method. The first argument is the class that is being # created (which may be a subclass), and there is no actual instance # yet. def __new__(cls, state=0): # We want to limit the actual value of the type to [0, nstates > state %= cls.nstates # To actually create the new instance, we call the parent's __new__. # Since __new__ is a static method, we have to pass the first # argument explicitly. # To call the right parent method, we use super(). What super() # does, is call the method that _would have been called_ if this # method wasn't present. In single-inheritance situations this just # means 'int.__new__' in our case, but if a subclass of our class # actually inherits from more than just us, it may end up being # another method altogether. Using super() in all places assures # consistent method-call order. # super() can be used in several ways, and the actual return value # is a magic object, a proxy object that does the right thing with # bound and unbound instance methods, and class methods. # Since we only have a class, we return super(tristate, cls).__new__(cls, state) def __add__(self, other): return tristate(self.__super.__add__(other)) def __sub__(self, other): return self.__add__(-other) class mylist(list): __metaclass__ = conveniencyTypeType def __getitem__(self, i): try: return self.__super.__getitem__(i) except IndexError: return None