Difficulty extending tuples in Python

Question:

I’m using Python 3.9.6. Why do the classes A and B work with the *args as a parameter, but C does not? Python throws this error (before printing args):

TypeError: tuple expected at most 1 argument, got 3.

Note the only difference between the classes is if it is extending a class, and what class it’s extending. I’m asking because I’m trying to make an object that behaves exactly like a tuple but would be distinguishable from it using isinstance.

class A:
    def __init__(self, *args):
        print(args)

class B(list):
    def __init__(self, *args):
        print(args)
        super().__init__(args)

class C(tuple):
    def __init__(self, *args):
        print(args)
        super().__init__(args)

A(1,2,3)
B(4,5,6)
C(7,8,9)
Asked By: TimH

||

Answers:

Note that in your example class C, the value (7, 8, 9) is not printed before the error message is shown. That means your __init__ method is not being called at all.

In fact, the error is raised by the __new__ method, not the __init__ method. It looks like the difference between B and C is caused by the fact that list.__new__ accepts any number of arguments (and ignores them), whereas tuple.__new__ does not:

>>> list.__new__(B, 4, 5, 6)
[]
>>> tuple.__new__(C, 7, 8, 9)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: tuple expected at most 1 argument, got 3

Note that list.__new__ just creates the list object, it doesn’t populate it (list.__init__ does that). I can’t say for certain why one accepts extra arguments while the other doesn’t, but that’s the reason for this discrepancy, anyway.

To fix it, your class C needs to override __new__ in order to pass the correct number of arguments to tuple.__new__:

class C(tuple):
    def __new__(cls, *args):
        print(args)
        return super().__new__(cls, args)

In this case there is no need to also override __init__, though you still can if you want to; however, note that tuple doesn’t override __init__, so your super().__init__() will actually invoke object.__init__, which accepts no arguments and does nothing, so you must invoke it with no arguments.

Answered By: kaya3
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.