[Python-Dev] How to suppress instance __dict__?

Alex Martelli aleax at aleax.it
Wed Mar 26 07:01:54 EST 2003


David Abrahams wrote:

> ====== DISCLAIMER ========
> 
> I'm not claiming Python is broken.  I just like to think about
> language design issues.
> 
> ====== DISCLAIMER ========

You're not alone in this vice;-).

>> I think you're dismissing the issue a tad too glibly.  In C++, I
>> often found myself needing the "two-phase initialization" pattern:
>> a costructor that did the VERY MINIMUM needed to ensure some very
>> fundamental invariants on the object -- then, a framework would
>> ensure that an ordinary virtual method "initialize" got called
    ...
> I'm not convinced of your need yet.

And yet framework authors seem to be, because so often they
provide "two-phase initialization" under some monicker or
other (MacApp calls it that; C++ FAQ 23.4 at 
http://www.parashift.com/c++-faq-lite/strange-inheritance.html
also does; I think it's also known by other names, but am
not sure which).  Creational design patterns are also often
focused on providing "further" initialization, after
construction per se, and before the fully constructed object
is returned to participate in further operations.

Providing ctor parameters to superclasses just isn't anywhere
as nice and flexible as working with perfectly normal virtual
methods for initialization as for all other tasks. All classes
in the hierarchy would have to be aware of potential initialization
needs of all possible leaf classes, badly breaking the "open-
closed principle"; and construction-related code may end up
needing to be fragmented into far too many pieces in order to
let subclasses combine the pieces with the flexibility that
would be SO much more easily obtained with virtual methods
(and, often, the precious Template DP).  Indeed one may end up
designing a "parallel hierarchy" of "initializer classes" to
get back the normal power of virtual methods (I think that's
the second solution proposed in the above-quoted FAQ 23.4, but
I haven't examined that in detail) -- do I need to waste bits
to show how undesirable and error-prone is it to have to code
parallel hierarchies of classes?!

"Two-phase initialization" does away with this clutter and
_just works_ -- that's all there is to it...!


>> I can find many more use cases of "two-phase init", but this by
>> itself should already suffice to explain that it's anything but
>> "seldom needed"... in C++.
> 
> I'm not convinced yet, and I've been around C++ programming long
> enough to feel pretty confident about what's commonly needed... but
> I'm still willing to be convinced ;-)

I'm also prety experienced in C++, having used many frameworks
and designed a few, and comparing the ease of dealing with
delicate initialization tasks in frameworks supporting "two
phase init" versus ones that don't leaves me just as confident
as you are about the issue, but on the other side of it.  So
I guess we'll just end up agreeing to disagree.


>> How this translates to Python -- I'm not sure.  The __new__
>> constructor, at first blush, would seem to be just as flexible
>> as the __init__ initializer.  However, some issues are
>> apparent.  It's easy for an __init__ to delegate to that
>> of a superclass; it's NOT easy to see how similar delegation
>> is supposed to take place for __new__ -- just as an example.
> 
> Oh.  Well that's a problem for my argument, then.  I have to admit to
> not knowing much about the mechanics of __new__.  However, it might
> also be an indicator that a simpler initialization scheme might have
> worked well for Python if __new__ were different than it is.

Please show one, because I sure can't see it.  Key issue: __new__
returns the new object (while __init__ operates on the just
constructed object).  If A inherits from B and C, how would
your "simpler initialization scheme" ensure A can delegate the
initialization tasks to both B and C (as it easily can today)?
[This depends on A not being able to inherit from two types
with *immutable* instances at the same time, of course -- but
that's a constraint I can easily live with, myself].


>> It's certainly possible to arrange for the latter, mind you:
>> __new__ does have a cls parameter so it may costruct objects
>> of different classes from the one I'm coding it in -- so if
>> everything is coded with extreme care perhaps there are no
>> real obstacle cases.  But consider MULTIPLE inheritance --
>> how would you arrange for THAT...?  I.e.:
>>
>> class Ba1(object):
>>     def __init__(self):
>>         self.a1 = 23
>>         super(Ba1, self).__init__()
>>
>> class Ba2(object):
>>     def __init__(self):
>>         self.a2 = 45
>>         super(Ba2, self).__init__()
>>
>> class Der(Ba1, Ba2):
>>     def __init__(self):
>>         super(Der, self).__init__()
>>         self.b = 67
>>
>> Using __new__ instead of __init__, how does class Der,
>> derived from two independently coded bases, ensure all
>> initialization done by the bases (here symbolically
>> represented by mere attribute setting) is properly
>> performed on the new object of class Der being created,
>> before finishing it up as it likes (here with just
>> yet another attribute setting)?
> 
> Oh, right, because __new__ essentially does the allocation, yes?

It's not an issue of it doing the allocation: it's an issue of
it *returning the new object* (rather than receiving the already
constructed object and operating on it -- that's __init__'s job).

> Maybe two orthogonal ideas that should be separate are bound together
> in __new__.  If we had one allocator and one initializer it would be a
> different (better?) world.

The one idea in __new__ is "return the new object" (establishing
the minimal invariants that couldn't be dealt with later --
essentially those connected to types with immutable instances).

In your "different world", how would you handle initializer
calls to multiple superclasses?  Please don't take away the
current flexibility of arranging such calls flexibly, with no
black magic nor ad hoc rules and constructs, just by calling.

Right now, e.g.:

class C(object):
    def __init__(self, a, b, c): "whatever is needed"

class D(int, C):
    def __new__(cls, intval, a, b, c):
        return int.__new__(cls, intval)
    def __init__(self, intval, a, b, c):
        # potential pre-C-initing stuff here
        C.__init__(self, a, b, c)
        # potential post-C-initing stuff here

just works -- D must take a modicum of care in order to
multiply inherit from a type with immutable instances AND
from an ordinary class at the same time, and arrange the
stuff it wants to do before and after calling C's own
initializer, but I find it quite flexible and simple.

So how would your dream language work instead?


Alex





More information about the Python-list mailing list