Can't set attribute for subclasses of namedtuple

Solution 1:

Named tuples are immutable, so you cannot manipulate them in the __init__ initializer. Your only option is to override the __new__ method:

class C(namedtuple('C', 'x, y')):
    __slots__ = ()
    def __new__(cls, obj):
        return super(C, cls).__new__(cls, obj.x, obj.y)

Note that because __new__ is a factory method for new instances, you do need to return the newly created instance. If you do not use return in the __new__ method, the default return value is None, which gives you your error.

Demo with an object with x and y attributes:

>>> class C(namedtuple('C', 'x, y')):
...     __slots__ = ()
...     def __new__(cls, obj):
...         return super(C, cls).__new__(cls, obj.x, obj.y)
... 
>>> O.x, O.y
(10, 20)
>>> C(O)
C(x=10, y=20)

Python does not support method overloading; generally you either use optional keyword arguments or extra class methods as factory methods.

The datetime module, for example, has several such factory methods to let you create objects that do not fit the standard constructor. datetime.datetime.fromtimestamp() creates a datetime.datetime instance from a single numeric value, and so does datetime.datetime.fromordinal(); except that they interpret the number in different ways.

If you wanted to support variable arguments, do:

class C(namedtuple('C', 'x, y')):
    __slots__ = ()

    def __new__(cls, x, y=None):
        if y is None:
            # assume attributes
            x, y = x.x, x.y
        return super(C, cls).__new__(cls, x, y)

Here, y is an optional argument, defaulting to None if not supplied by the caller:

>>> C(3, 5):
C(x=3, y=5)
>>> C(O)
C(x=10, y=20)

The alternative, using a class method, would be:

class C(namedtuple('C', 'x, y')):
    @classmethod
    def from_attributes(cls, obj):
        return cls(obj.x, obj.y)

Now there are two factory methods; one default and one named:

>>> C(3, 5):
C(x=3, y=5)
>>> C.from_attributes(O)
C(x=10, y=20)

Solution 2:

I suggest you use the the _replace method

from collections import namedtuple
C = namedtuple('C', 'x, y')
c = C(x=10, y=20)
# c.x = 30 won't work
c = c._replace(x=30)