int.__init__ incompatible in Python 3.3

  • Thread starter Ulrich Eckhardt
  • Start date
U

Ulrich Eckhardt

Hi!

Preparing for an upgrade from 2.7 to 3, I stumbled across an
incompatibility between 2.7 and 3.2 on one hand and 3.3 on the other:

class X(int):
def __init__(self, value):
super(X, self).__init__(value)
X(42)

On 2.7 and 3.2, the above code works. On 3.3, it gives me a "TypeError:
object.__init__() takes no parameters". To some extent, this makes sense
to me, because the int subobject is not initialized in __init__ but in
__new__. As a workaround, I can simple drop the parameter from the call.
However, breaking backward compatibility is another issue, so I wonder
if that should be considered as a bug.

Bug? Feature? Other suggestions?


Uli
 
I

Ian Kelly

Hi!

Preparing for an upgrade from 2.7 to 3, I stumbled across an incompatibility
between 2.7 and 3.2 on one hand and 3.3 on the other:

class X(int):
def __init__(self, value):
super(X, self).__init__(value)
X(42)

On 2.7 and 3.2, the above code works. On 3.3, it gives me a "TypeError:
object.__init__() takes no parameters". To some extent, this makes sense to
me, because the int subobject is not initialized in __init__ but in __new__.
As a workaround, I can simple drop the parameter from the call. However,
breaking backward compatibility is another issue, so I wonder if that should
be considered as a bug.

Bug? Feature? Other suggestions?

A similar change was made to object.__init__ in 2.6, so this could
just be bringing the behavior of int into line with object. There's
nothing about it in the whatsnew document, though. I say open a bug
report and let the devs sort it out.
 
T

Terry Reedy

On Thu, Nov 8, 2012 at 8:55 AM, Ulrich Eckhardt

This is a bug. Subclasses of immutables should not define __init__.True

object.__init__(self) is a dummy placeholder function that takes no args
and does nothing.

That is a bug. It is documented that calling with the wrong number of
args is an error.

Just drop the do-nothing call.

Every bug fix breaks backward compatibility with code that depends on
the bug. Such breakage is not a bug, but, as in this case, some fixes
are not put in bugfix releases because of such breakage.

Intentional bugfix.
http://bugs.python.org/issue1683368
There was additional discussion on pydev or python-ideas lists before
the final commit. This fix was not back-ported to 2.7 or 3.2.
A similar change was made to object.__init__ in 2.6, so this could
just be bringing the behavior of int into line with object. There's
nothing about it in the whatsnew document, though.

What's New is a summary of *new* features. It does not list bug fixes.
At the top it says " For full details, see the Misc/NEWS file." The last
patch on the issue added this entry.
'''
Core and Builtins
-----------------

- Issue #1683368: object.__new__ and object.__init__ raise a TypeError
if they are passed arguments and their complementary method is not
overridden.
'''
I say open a bug report and let the devs sort it out.

Please do not. The current situation is the result of 'sorting it out'
over several years.
 
U

Ulrich Eckhardt

Am 08.11.2012 21:29, schrieb Terry Reedy:
Just drop the do-nothing call.

Wait: Which call exactly?

Do you suggest that I shouldn't override __init__? The problem is that I
need to attach additional info to the int and that I just pass this to
the class on contstruction.

Or, do you suggest I don't call super().__init__()? That would seem
unclean to me.

Just for your info, the class mimics a C enumeration, roughly it looks
like this:

class Foo(int):
def __init__(self, value, name):
super(Foo, self).__init__(value)
self.name = name

def __str__(self):
return self.name

Foo.AVALUE = Foo(1, 'AVALUE')
Foo.BVALUE = Foo(2, 'BVALUE')

Note that even though I derive from an immutable class, the resulting
class is not formally immutable. Maybe exactly that is the thing that
the developers did not want me to do? I didn't understand all the
implications in the bug ticket you quoted, to be honest.

Thank you for your time!

Uli
 
S

Steven D'Aprano

Am 08.11.2012 21:29, schrieb Terry Reedy:

Wait: Which call exactly?

Do you suggest that I shouldn't override __init__? The problem is that I
need to attach additional info to the int and that I just pass this to
the class on contstruction.

No, of course not. If you need to override __init__, you need to override
__init__.

Or, do you suggest I don't call super().__init__()? That would seem
unclean to me.

On the contrary: calling super().__init__ when the superclass does
something you don't want (i.e. raises an exception) is unclean.

Since the superclass __init__ does nothing, you don't need to call it.
Only inherit behaviour that you actually *want*.

In Python 3.3:

py> class X(int):
.... def __init__(self, *args):
.... super().__init__(*args) # does nothing, call it anyway
....
py> x = X(22)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
TypeError: object.__init__() takes no parameters


It is apparently an oversight, or a bug, that it ever worked in older
versions.

Note that even though I derive from an immutable class, the resulting
class is not formally immutable. Maybe exactly that is the thing that
the developers did not want me to do?

Nope, that's irrelevant. Attaching attributes to an otherwise immutable
object is fine.
 
U

Ulrich Eckhardt

Am 09.11.2012 12:37, schrieb Steven D'Aprano:
On the contrary: calling super().__init__ when the superclass does
something you don't want (i.e. raises an exception) is unclean.

Since the superclass __init__ does nothing, you don't need to call it.
Only inherit behaviour that you actually *want*.


That one's hard to swallow for me, but maybe this is because I don't
understand the Python object model sufficiently. The problem I have here
is that not forwarding the __init__() to the baseclass could mean that
necessary initializations are not performed, although in this very
specify case I see that there aren't any. It still seems a bit like
relying on an implementation details.

Anyhow, I'll have to do some more reading on the the construction of
objects in Python, maybe then it'll all make sense. Until then, thanks
everybody for nudging me in the right direction!

Uli
 
I

Ian Kelly

In Python 3.3:

py> class X(int):
... def __init__(self, *args):
... super().__init__(*args) # does nothing, call it anyway
...
py> x = X(22)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
TypeError: object.__init__() takes no parameters


It is apparently an oversight, or a bug, that it ever worked in older
versions.

After reading through the bug history, I think that this change to int
is incorrect, or at least incomplete. The goal of the change to
object.__init__ is to enable checking for unused arguments when doing
cooperative multiple inheritance, with the idea that each class in the
hierarchy will remove the arguments it uses and pass the rest along.
By the time object.__init__ is reached, any arguments remaining are
unused and extraneous.

In the case of int, int.__new__ takes up to two arguments. Due to the
nature of the type system, these same two arguments are also passed to
int.__init__. If each subclass removes its own arguments per the
convention, then by the time int.__init__ is reached, there are still
up to two *expected* arguments remaining. It should not be the
responsibility of the subclasses (which one? all of them?) to remove
these arguments before calling super().__init__(). The int class
should have the responsibility of accepting and removing these two
arguments *and then* checking that there is nothing left over.

In Python 3.2, int.__init__ happily accepted the int arguments, but
also incorrectly accepted anything else you might pass to it, which
was suboptimal for cooperative multiple inheritance. In Python 3.3,
it no longer accepts unused arguments, but it also rejects arguments
intended for its own class that it should accept, which as I see it
makes int.__init__ *unusable* for cooperative multiple inheritance.

I realize that the recommendation in the bug comments is to use
__new__ instead of __init__ for subclasses of immutable types. But
then why have them call __init__ in the first place? Why even fuss
over what arguments int.__init__ does or does not accept if we're not
supposed to be calling it at all? And why is that deprecation not
mentioned anywhere in the documentation, that I can find?
 
U

Ulrich Eckhardt

Am 09.11.2012 12:37, schrieb Steven D'Aprano:
In Python 3.3:

py> class X(int):
... def __init__(self, *args):
... super().__init__(*args) # does nothing, call it anyway
...
py> x = X(22)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
TypeError: object.__init__() takes no parameters


It is apparently an oversight, or a bug, that it ever worked in older
versions.


I'm not really convinced that the overall behaviour is sound:

py> x = 42
py> x.__init__()
py> x.__init__(1)
py> x.__init__(1,2)
py> x.__init__(1,2,3)
py> x.__init__(1,2,3,4)

Neither of these seem to care about the number and type of parameters.
On the other hand:

py> y = object()
py> y.__init__()
py> y.__init__(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object.__init__() takes no parameters


So, for some reason that I don't understand yet, my call to the
superclass' init function skips a class, but only when called with super().


Confused greetings!

Uli
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,534
Members
45,008
Latest member
Rahul737

Latest Threads

Top