I want to use something like collections.namedtuple
, which nicely enforces immutability and facilitates a simple value class, but it doesn't allow subclassing; for example I'd like to do something like the following to add extra read-only properties:
from collections import namedtuple
class Foo(namedtuple('Foo','foo')):
@property
def antifoo(self):
return -self.foo
class Bar(Foo):
""" ARGH: somehow we add a 'bar' field """
@property
def inversebar(self):
return 1.0/bar
The only problem is there's no mechanism to add additional properties. So I looked at the result of collections.namedtuple('Test','foo bar baz', verbose=True)
and tweaked it for my own purposes:
from collections import OrderedDict
from operator import itemgetter as _itemgetter
class Foo(tuple):
__slots__ = ()
_fields = ('foo',)
_nargs = 1
_repr_format = '(foo=%r)'
foo = property(_itemgetter(0), doc='Alias for field number 0')
def __new__(_cls, foo):
return tuple.__new__(_cls, (foo,))
@classmethod
def _make(cls, iterable, new=tuple.__new__, len=len):
'Make a new Foo object from a sequence or iterable'
result = new(cls, iterable)
if len(result) != cls._nargs:
raise TypeError('Expected 1 argument, got %d' % len(result))
return result
def _replace(self, **kwds):
'Return a new {typename} object replacing specified fields with new values'
result = self._make(map(kwds.pop, self._fields, self))
if kwds:
raise ValueError('Got unexpected field names: %r' % kwds.keys())
return result
def __repr__(self):
'Return a nicely formatted representation string'
return self.__class__.__name__ + self._repr_format % self
def _asdict(self):
'Return a new OrderedDict which maps field names to their values.'
return OrderedDict(zip(self._fields, self))
__dict__ = property(_asdict)
def __getstate__(self):
'Exclude the OrderedDict from pickling'
pass
@property
def antifoo(self):
return -self.foo
class Bar(Foo):
_fields = ('foo','bar')
_nargs = 2
_repr_format = '(foo=%r, bar=%r)'
bar = property(_itemgetter(1), doc='Alias for field number 1')
def __new__(_cls, foo, bar):
return tuple.__new__(_cls, (foo, bar))
@property
def inversebar(self):
return 1.0/self.bar
This seems to work properly, but is it OK, or am I making a grave mistake?