Here's a metaclass to do what you want (I think). It works by storing the methods to be inherited in a dictionary and manually inserting them into the new classes dictionary. It also stores the attribute string that gets passed to the namedtuple
constructor and merges that with the attribute string from the subclass. It then passes that to namedtuple
and returns a class that's inherited from the resulting namedtuple
with all appropriate methods in its dictionary. Because the metaclass is derived from abc.ABCMeta
, you get working type checking for free. Here's how constructing a couple of classes looks:
class Foo(object):
__metaclass__ = ImmutableMeta
_attributes_ = 'a b'
def sayhi(self):
print "Hello from {0}".format(type(self).__name__)
class Bar(Foo):
_attributes_ = 'c'
def saybye(self):
print "Goodbye from {0}".format(type(self).__name__)
Here's the metaclass:
import collections as co
import abc
class ImmutableMeta(abc.ABCMeta):
_classes = {}
def __new__(meta, clsname, bases, clsdict):
attributes = clsdict.pop('_attributes_')
if bases[0] is object:
# 'new' class
methods = clsdict
else:
# we're 'inheriting' from an existing class
base = bases[0]
attributes = meta._classes[base]['attributes'] + ' ' + attributes
base_methods = meta._classes[base]['methods'].copy()
base_methods.update(clsdict)
methods = base_methods
# construct the actual base class and create the return class
new_base = co.namedtuple(clsname + 'Base', attributes)
cls = super(ImmutableMeta, meta).__new__(meta, clsname, (new_base,),
methods)
# register the data necessary to 'inherit' from the class
# and make sure that it passes typechecking
meta._classes[cls] = {'attributes': attributes,
'methods': methods}
if bases[0] is not object:
base.register(cls)
return cls
And here's some paltry test code.
a = Foo(1, 2)
a.sayhi()
b = Bar(1, 2, 3)
b.sayhi() # 'inherited' from class Foo
b.saybye()
try:
b.c = 1 # will raise an AttributeError
except AttributeError:
print "Immutable"
print "issubclass(Bar, Foo): {0}".format(issubclass(Bar, Foo))
try:
d = {b: 1} # No problems
except TypeError:
print "Cant put it in a dict"
else:
print "Can put it in a dict"
Hope that helps. If you would prefer not to attach every method to every class that is supposed to inherit it, you could also provide a default __getattr__
that looks through the metaclasses dictionary and finds the appropriate method out of that. That would require somehow hardcoding the baseclass into method, probably using a closure.