Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I subclass tuple in python3?

Let's preface this question by saying that you should use __new__ instead of __init__ for subclassing immutable objects.

With that being said, let's see the following code:

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

mytuple = MyTuple([1,2,3])

This works in python2, but in python3 I get:

Traceback (most recent call last):
  File "tmp.py", line 5, in <module>
    mytuple = MyTuple([1,2,3])
  File "tmp.py", line 3, in __init__
    super(MyTuple, self).__init__(*args)
TypeError: object.__init__() takes no parameters

Why does this happen? What changed in python3?

like image 283
loopbackbee Avatar asked Dec 30 '16 03:12

loopbackbee


2 Answers

Python 3 changed how object.__new__ and object.__init__ react to arguments when both are overridden. If a class overrides (or inherits methods that override) both object.__init__ and object.__new__, object.__init__ and object.__new__ will throw an exception if they receive any excess arguments. In Python 2, that would have given a DeprecationWarning (suppressed by default).

tuple doesn't have its own __init__. It inherits object.__init__, so you're actually passing a bunch of arguments to object.__init__ that object.__init__ doesn't take. Python 2 was giving you a (suppressed) warning, and Python 3 is making it an error.

The code has a comment that does a good job of explaining object.__init__ and object.__new__'s subtle handling of extra arguments:

/* You may wonder why object.__new__() only complains about arguments
   when object.__init__() is not overridden, and vice versa.

   Consider the use cases:

   1. When neither is overridden, we want to hear complaints about
      excess (i.e., any) arguments, since their presence could
      indicate there's a bug.

   2. When defining an Immutable type, we are likely to override only
      __new__(), since __init__() is called too late to initialize an
      Immutable object.  Since __new__() defines the signature for the
      type, it would be a pain to have to override __init__() just to
      stop it from complaining about excess arguments.

   3. When defining a Mutable type, we are likely to override only
      __init__().  So here the converse reasoning applies: we don't
      want to have to override __new__() just to stop it from
      complaining.

   4. When __init__() is overridden, and the subclass __init__() calls
      object.__init__(), the latter should complain about excess
      arguments; ditto for __new__().

   Use cases 2 and 3 make it unattractive to unconditionally check for
   excess arguments.  The best solution that addresses all four use
   cases is as follows: __init__() complains about excess arguments
   unless __new__() is overridden and __init__() is not overridden
   (IOW, if __init__() is overridden or __new__() is not overridden);
   symmetrically, __new__() complains about excess arguments unless
   __init__() is overridden and __new__() is not overridden
   (IOW, if __new__() is overridden or __init__() is not overridden).

   However, for backwards compatibility, this breaks too much code.
   Therefore, in 2.6, we'll *warn* about excess arguments when both
   methods are overridden; for all other cases we'll use the above
   rules.

*/
like image 191
user2357112 supports Monica Avatar answered Oct 03 '22 17:10

user2357112 supports Monica


I've been digging around the C code-base and I haven't found any real clues there (yet) about what changed to disallow this behavior in python3. I've tested on python2.7, python3.3, python3.5 and python3.6. The only time your code works without an exception is on python2.7. I also haven't found any references in the documentation about why this changed however, I do have some ideas...

First, let's agree that tuple.__init__ cannot do anything since tuple are immutable. By the time __init__ is called, the tuple is already frozen. So, this leads us to my guess -- since tuple.__init__ does nothing, the devs considered it misleading to allow it to accept any arguments at all. By preventing the base-class from accepting arguments, they encourage people to override __new__ (and therefore, encourage proper inheritance for immutable objects).

like image 33
mgilson Avatar answered Oct 03 '22 16:10

mgilson