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?
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.
*/
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With