sds
sds

Reputation: 60004

Cannot create an instance of a namedtuple subclass: TypeError: __new__() takes exactly 4 arguments (3 given)

I seem to be unable to instantiate a namedtuple subclass:

from collections import namedtuple

foo = namedtuple("foo",["a","b","c"])
class Foo(foo):
    def __init__(self, a, b):
        super(Foo, self).__init__(a=a,b=b,c=a+b)

When I try to create an instance, I get:

>>> Foo(1,2)
TypeError: __new__() takes exactly 4 arguments (3 given)

I expected Foo(1,2,3).

There seems to be a workaround: using a class method instead of __init__:

class Foo(foo):
    @classmethod
    def get(cls, a, b):
        return cls(a=a, b=b, c=a+b)

Now Foo.get(1,2) indeed returns foo(a=1, b=2, c=3).

However, this looks ugly.

Is this the only way?

Upvotes: 4

Views: 839

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1121166

Named tuples are immutable, you need to use the __new__ method instead:

class Foo(foo):
    def __new__(cls, a, b):
        return super(Foo, cls).__new__(cls, a=a, b=b, c=a+b)

(Note: __new__ is implicitly made a static method, so you need to pass on the cls argument explicitly; the method returns the newly created instance).

__init__ can't be used because that is called after the instance has already been created and so would not be able to mutate the tuple anymore.

Note that you should really add a __slots__ = () line to your subclass; a named tuple has no __dict__ dictionary cluttering up your memory, but your subclass will unless you add the __slots__ line:

class Foo(foo):
    __slots__ = ()
    def __new__(cls, a, b):
        return super(Foo, cls).__new__(cls, a=a, b=b, c=a+b)

That way you get to keep the memory footprint of your named tuples low. See the __slots__ documentation:

The action of a __slots__ declaration is limited to the class where it is defined. As a result, subclasses will have a __dict__ unless they also define __slots__ (which must only contain names of any additional slots).

Upvotes: 7

Related Questions