Author jaraco
Recipients KayEss, Rhamphoryncus, benjamin.peterson, blakeross, eric.snow, georg.brandl, gregory.p.smith, gvanrossum, jaraco, jcea, jonash, rhettinger, terry.reedy
Date 2014-05-28.21:15:56
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1401311759.8.0.871714258408.issue1683368@psf.upfronthosting.co.za>
In-reply-to
Content
Maybe I should have focused on a more trivial example to demonstrate the place where my expectation was violated. The use of a real-world example is distracting from my intended point. Consider instead this abstract example:

class SomeClass(SomeParentClass):
    def __new__(cls, *args, **kwargs):
        return super(SomeClass, cls).__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        super(SomeClass, self).__init__(*args, **kwargs)

Ignoring for a moment the incongruity of the invocation of __new__ with 'cls' due to __new__ being a staticmethod, the naive programmer expects the above SomeClass to work exactly like SomeParentClass because both overrides are implemented as a trivial pass-through.

And indeed that technique will work just fine if the parent class implements both __init__ and __new__, but if the parent class (or one of its parents) does not implement either of those methods, the technique will fail, because the fall through to 'object' class.

I believe this incongruity stems from the fact that __new__ and __init__ are special-cased not to be called if they aren't implemented on the class.

Therefore, to write SomeClass without knowledge of the SomeParentClass implementation, one could write this instead:

class SomeClass(SomeParentClass):
    def __new__(cls, *args, **kwargs):
        super_new = super(SomeClass, cls).__new__
        if super_new is object.__new__:
            return super_new(cls)
        return super_new(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        super_init = super(SomeClass, self).__init__
        if super_init.__objclass__ is object:
            return
        super_init(*args, **kwargs)

Now that implementation is somewhat ugly and perhaps a bit brittle (particularly around use of __objclass__). Ignoring that for now, it does have the property that regardless of the class from which it derives, it will work, including:

SomeParentClass = datetime.datetime # implements only __new__
SomeParentClass = zipfile.ZipFile # implements only __init__
class SomeParentClass: pass # implements neither __init__ nor __new__

While I would prefer a language construct that didn't require this dance for special casing (or similarly require the programmer to hard-code the dance to a specific implementation of a specific parent class as Guido recommends), at the very least I would suggest that the documentation better reflect this somewhat surprising behavior.

Currently, the documentation states [https://docs.python.org/2/reference/datamodel.html#object.__new__] effectively "Typical implementations of __new__ invoke the superclass’ __new__() method with appropriate arguments." It's left as an exercise to the reader to ascertain what 'appropriate arguments' are, and doesn't communicate that the introduction or omission of __new__ or __init__ to a class hierarchy affects the process by which a class is constructed/initialized.

Greg Smith's blog demonstrates some even more dangerous cases. I don't understand why his concerns weren't addressed, because they seem legitimate, and I agree with his conclusion that the older behavior is more desirable, despite the concerns raised by the OP.
History
Date User Action Args
2014-05-28 21:16:00jaracosetrecipients: + jaraco, gvanrossum, georg.brandl, rhettinger, terry.reedy, gregory.p.smith, jcea, Rhamphoryncus, blakeross, benjamin.peterson, KayEss, jonash, eric.snow
2014-05-28 21:15:59jaracosetmessageid: <1401311759.8.0.871714258408.issue1683368@psf.upfronthosting.co.za>
2014-05-28 21:15:59jaracolinkissue1683368 messages
2014-05-28 21:15:56jaracocreate