[Python-Dev] Breaking calls to object.__init__/__new__

Adam Olsen rhamph at gmail.com
Thu Mar 22 05:21:18 CET 2007

On 3/21/07, Jean-Paul Calderone <exarkun at divmod.com> wrote:
> On Wed, 21 Mar 2007 15:45:16 -0700, Guido van Rossum <guido at python.org> wrote:
> >See python.org/sf/1683368. I'd like to invite opinions on whether it's
> >worth breaking an unknown amount of user code in 2.6 for the sake of
> >stricter argument checking for object.__init__ and object.__new__. I
> >think it probably isn't; but the strict version could be added to 3.0
> >and a warning issued in 2.6 in -Wpy3k mode. Alternatively, we could
> >introduce the stricter code in 2.6, fix the stdlib modules that it
> >breaks, and hope for the best. Opinions?
> >
> Perhaps I misunderstand the patch, but it would appear to break not just
> some inadvisable uses of super(), but an actual core feature of super().
> Maybe someone can set me right.  Is this correct?
>   class Base(object):
>       def __init__(self, important):
>           # Don't upcall with `important` because object is the base
>           # class and its __init__ doesn't care (or won't accept) it
>           super(Base, self).__init__()
>           self.a = important
> If so, what are the implications for this?
>   class Other(object):
>       def __init__(self, important):
>           # Don't upcall with `important` because object is the base
>           # class and its __init__ doesn't care (or won't accept) it
>           super(Other, self).__init__()
>           self.b = important
>   class Derived(Base, Other):
>       pass
> (A similar example could be given where Base and Other take differently
> named arguments with nothing to do with each other.  The end result is
> the same either way, I think.)

The common name is actually critical.  Your argument names are
essentially a shared namespace, just like that on the object itself,
and they're both modifying it on the assumption of being the only
thing that does so.

There's two ways to fix your example.  First, adding a common base
class which is the "owner" of that name:

class Owner(object):
    def __init__(self, important, **kwargs):
        super(Owner, self).__init__(**kwargs)  # important is skipped

class Left(Owner):
    def __init__(self, important, **kwargs):
        super(Left, self).__init__(important=important, **kwargs)

class Right(Owner):
    def __init__(self, important, **kwargs):
        super(Right, self).__init__(important=important, **kwargs)

class Derived(Left, Right):

>>> Derived("hi")

The other is to rename the argument, removing the namespace conflict:

class Left(object):
    def __init__(self, oranges, **kwargs):
        super(Left, self).__init__(oranges=oranges, **kwargs)

class Right(object):
    def __init__(self, apples, **kwargs):
        super(Right, self).__init__(apples=apples, **kwargs)

class Derived(Left, Right):

>>> Derived(apples=3, oranges=8)

In this second version you could clean up Derived's interface by
adding either "def __init__(self, apples, oranges, **kwargs)" and
passing them both explicitly, or by adding "def __init__(self, *,
**kwargs)" and requiring they by given to you by name.  Either way
you're completely safe.

> I think I understand the desire to pull keyword arguments out at each
> step of the upcalling process, but I don't see how it can work, since
> "up" calling isn't always what's going on - given a diamond, there's
> arbitrary side-calling, so for cooperation to work every method has to
> pass on every argument, so object.__init__ has to take arbitrary args,
> since no one knows when their "up" call will actually hit object.
> Since without diamonds, naive "by-name" upcalling works, I assume that
> super() is actually intended to be used with diamonds, so this seems
> relevant.
> I hope I've just overlooked something.  Writing this email feels very
> strange.

super() has always felt strange to me.  Now, with PEP 3102 and the
strict __init__, not so much.

Adam Olsen, aka Rhamphoryncus

More information about the Python-Dev mailing list