int.__init__ incompatible in Python 3.3

Discussion in 'Python' started by Ulrich Eckhardt, Nov 8, 2012.

  1. 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
     
    Ulrich Eckhardt, Nov 8, 2012
    #1
    1. Advertising

  2. Ulrich Eckhardt

    Ian Kelly Guest

    On Thu, Nov 8, 2012 at 8:55 AM, Ulrich Eckhardt
    <> wrote:
    > 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.
     
    Ian Kelly, Nov 8, 2012
    #2
    1. Advertising

  3. Ulrich Eckhardt

    Terry Reedy Guest

    On 11/8/2012 12:13 PM, Ian Kelly wrote:
    > On Thu, Nov 8, 2012 at 8:55 AM, Ulrich Eckhardt
    > <> wrote:


    >> 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)


    This is a bug. Subclasses of immutables should not define __init__.
    >>> int.__init__ is object.__init__

    True

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

    >> X(42)
    >>
    >> On 2.7 and 3.2, the above code works.


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

    >> 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.


    Just drop the do-nothing call.

    >> breaking backward compatibility is another issue, so I wonder if that should
    >> be considered as a bug.


    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.

    >> Bug? Feature? Other suggestions?


    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.


    --
    Terry Jan Reedy
     
    Terry Reedy, Nov 8, 2012
    #3
  4. Am 08.11.2012 21:29, schrieb Terry Reedy:
    > On Thu, Nov 8, 2012 at 8:55 AM, Ulrich Eckhardt
    > <> wrote:
    >>> 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.

    >
    > 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
     
    Ulrich Eckhardt, Nov 9, 2012
    #4
  5. On Fri, 09 Nov 2012 08:56:22 +0100, Ulrich Eckhardt wrote:

    > Am 08.11.2012 21:29, schrieb Terry Reedy:
    >> On Thu, Nov 8, 2012 at 8:55 AM, Ulrich Eckhardt
    >> <> wrote:
    >>>> 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.

    >>
    >> 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.


    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.



    --
    Steven
     
    Steven D'Aprano, Nov 9, 2012
    #5
  6. Am 09.11.2012 12:37, schrieb Steven D'Aprano:
    > On Fri, 09 Nov 2012 08:56:22 +0100, Ulrich Eckhardt wrote:
    >> 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*.



    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
     
    Ulrich Eckhardt, Nov 9, 2012
    #6
  7. Ulrich Eckhardt

    Ian Kelly Guest

    On Fri, Nov 9, 2012 at 4:37 AM, Steven D'Aprano
    <> wrote:
    > 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?
     
    Ian Kelly, Nov 9, 2012
    #7
  8. 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
     
    Ulrich Eckhardt, Nov 12, 2012
    #8
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Schnoffos
    Replies:
    2
    Views:
    1,220
    Martien Verbruggen
    Jun 27, 2003
  2. Hal Styli
    Replies:
    14
    Views:
    1,646
    Old Wolf
    Jan 20, 2004
  3. Steven Bethard
    Replies:
    2
    Views:
    458
    Steven Bethard
    Feb 16, 2005
  4. Kent Johnson
    Replies:
    7
    Views:
    915
    Jan Niklas Fingerle
    Feb 12, 2006
  5. Ramchandra Apte
    Replies:
    17
    Views:
    342
    Manuel Pégourié-Gonnard
    Sep 30, 2012
Loading...

Share This Page