0

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?

Community
  • 1
  • 1
Jason S
  • 184,598
  • 164
  • 608
  • 970
  • Extending a `namedtuple` is a common pattern, and perfectly sensible. As for the second part, have you read e.g. http://stackoverflow.com/q/28500524/3001761? – jonrsharpe Jan 23 '17 at 23:38
  • You may find this answer interesting http://stackoverflow.com/a/28942143/4014959 – PM 2Ring Jan 24 '17 at 01:33
  • @jonrsharpe [that question](http://stackoverflow.com/questions/28500524/python-extending-a-predefined-named-tuple) looks relevant except that it's not subclassing, and I need to use the extra properties of the first subclass of `namedtuple`. – Jason S Jan 24 '17 at 02:29

0 Answers0