41

This question is asking the opposite of Inherit namedtuple from a base class in python , where the aim is to inherit a subclass from a namedtuple and not vice versa.

In normal inheritance, this works:

class Y(object):
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c


class Z(Y):
    def __init__(self, a, b, c, d):
        super(Z, self).__init__(a, b, c)
        self.d = d

[out]:

>>> Z(1,2,3,4)
<__main__.Z object at 0x10fcad950>

But if the baseclass is a namedtuple:

from collections import namedtuple

X = namedtuple('X', 'a b c')

class Z(X):
    def __init__(self, a, b, c, d):
        super(Z, self).__init__(a, b, c)
        self.d = d

[out]:

>>> Z(1,2,3,4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __new__() takes exactly 4 arguments (5 given)

The question, is it possible to inherit namedtuples as a base class in Python? If so, how?

Neuron
  • 5,141
  • 5
  • 38
  • 59
alvas
  • 115,346
  • 109
  • 446
  • 738
  • 5
    This is not exactly an answer to your question, but it might be worth checking out the new python [dataclasses](https://docs.python.org/3/library/dataclasses.html). In most cases where you would override a namedtuple, you might want to use them instead. – ethanabrooks Jun 21 '19 at 15:09

4 Answers4

48

You can, but you have to override __new__ which is called implicitly before __init__:

class Z(X):
  def __new__(cls, a, b, c, d):
    self = super(Z, cls).__new__(cls, a, b, c)
    self.d = d
    return self

>>> z = Z(1, 2, 3, 4)
>>> z
Z(a=1, b=2, c=3)
>>> z.d
4

But d will be just an independent attribute!

>>> list(z)
[1, 2, 3]
user2390182
  • 72,016
  • 6
  • 67
  • 89
  • Can I do `self.append(d)`? – alvas Feb 22 '17 at 08:27
  • @alvas No, because `namedtuple` is basically a `tuple` (immutable by design and therefore has no `append`) – MSeifert Feb 22 '17 at 08:28
  • @Mseifert Thanks! But =( – alvas Feb 22 '17 at 08:31
  • 5
    Is inheriting from `namedtuple` an anti-pattern, or is it a reasonable approach if I just need a coupe extra arguments that I'd like to keep separate from the rest? (Specifically, I want them calculated in `__init__` rather than assigned in user code.) – max May 09 '17 at 04:07
16

I think you can achieve what you want by including all of the fields in the original named tuple, then adjusting the number of arguments using __new__ as schwobaseggl suggests above. For instance, to address max's case, where some of the input values are to be calculated rather than supplied directly, the following works:

from collections import namedtuple

class A(namedtuple('A', 'a b c computed_value')):
    def __new__(cls, a, b, c):
        computed_value = (a + b + c)
        return super(A, cls).__new__(cls, a, b, c, computed_value)

>>> A(1,2,3)
A(a=1, b=2, c=3, computed_value=6)
TimH
  • 168
  • 1
  • 5
9

I came here with the exact same problem, just two years later.
I personally thought that the @property decorator would fit in here better:

from collections import namedtuple

class Base:
    @property
    def computed_value(self):
        return self.a + self.b + self.c

# inherits from Base
class A(Base, namedtuple('A', 'a b c')):
    pass

cls = A(1, 2, 3)
print(cls.computed_value)
# 6
Jan
  • 42,290
  • 8
  • 54
  • 79
  • 1
    This is mentioned in documentation now: https://docs.python.org/3/library/collections.html#collections.somenamedtuple._field_defaults – ggguser Nov 03 '20 at 08:43
0

Instead of thinking strictly in terms of inheritance, since namedtuple is a function, another approach could be to encapsulate it in a new function.

The question then becomes: "how do we construct a namedtuple that has properties a, b, c by default, and optionally some additional ones?"

def namedtuple_with_abc(name, props=[]):
     added = props if type(props) == type([]) else props.split()
     return namedtuple(name, ['a', 'b', 'c'] + added)

X = namedtuple_with_abc('X')
Z = namedtuple_with_abc('Z', 'd e')

>>> X(1, 2, 3)
X(a=1, b=2, c=3)

>>> Z(4, 5, 6, 7, 8)
Z(a=4, b=5, c=6, e=7, f=8)
Miklos Aubert
  • 4,405
  • 2
  • 24
  • 33