Is it valid to make an extension of a collections.

2019-03-06 06:29发布

问题:

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?