Jason R. Coombs added the comment:
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.
----------
_______________________________________
Python tracker <[email protected]>
<http://bugs.python.org/issue1683368>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com