Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[Python-Dev] How to suppress instance __dict__?

108 views
Skip to first unread message

Delaney, Timothy C (Timothy)

unread,
Mar 23, 2003, 7:09:25 PM3/23/03
to
> From: David Abrahams [mailto:da...@boost-consulting.com]
>
> Guido van Rossum <gu...@python.org> writes:
>
> > So I don't think __new__ is preferred over __init__, unless
> you need a
> > feature that only __new__ offers (like initializing an
> immutable base
> > class or returning an existing object or an object of a different
> > class).
>
> In other words, TIMTOWTDI? <0.3 wink>

[Moved to python-list as I think it's gone well beyond python-dev].

I wouldn't say that. Use __init__ unless you *must* use __new__. And when you use __new__, still use __init__ for initialisation.

And if you are going to use __new__, you are expected to be able to explain *why*.

Pretty straightforward to me.

Tim Delaney

David Abrahams

unread,
Mar 23, 2003, 10:01:14 PM3/23/03
to

Why should that be the rule? Why not the opposite?

FWIW, if everyone used the opposite policy (__new__ unless you must
use __init__), the problem of __del__ operating on uninitialized
objects could be substantially relieved.

However, tradition has us working the other way.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

Delaney, Timothy C (Timothy)

unread,
Mar 23, 2003, 11:02:43 PM3/23/03
to
> From: David Abrahams [mailto:da...@boost-consulting.com]
> >
> > I wouldn't say that. Use __init__ unless you *must* use __new__. And
> > when you use __new__, still use __init__ for initialisation.
> >
> > And if you are going to use __new__, you are expected to be able to
> > explain *why*.
> >
> > Pretty straightforward to me.
>
> Why should that be the rule? Why not the opposite?
>
> FWIW, if everyone used the opposite policy (__new__ unless you must
> use __init__), the problem of __del__ operating on uninitialized
> objects could be substantially relieved.

Because most of the time we want to work with fully-constructed objects. You can't use overridden methods in a constructor - only from an initialiser.

Tim Delaney

Michael Hudson

unread,
Mar 24, 2003, 4:33:08 AM3/24/03
to
"Delaney, Timothy C (Timothy)" <tdel...@avaya.com> writes:

[__init__ vs. __new__]

> Because most of the time we want to work with fully-constructed
> objects. You can't use overridden methods in a constructor - only
> from an initialiser.

What makes you say that?

/>> class C(object):
|.. def __new__(cls):
|.. i = object.__new__(cls)
|.. i.meth()
|.. return i
|.. def meth(self):
|.. print "C"
\__
/>> class D(C):
|.. def meth(self):
|.. print "D"
\__
->> D()
D
<__main__.D object at 0x820f684>

Doing too much C++ lately?

Cheers,
M.

--
31. Simplicity does not precede complexity, but follows it.
-- Alan Perlis, http://www.cs.yale.edu/homes/perlis-alan/quotes.html

Delaney, Timothy C (Timothy)

unread,
Mar 24, 2003, 6:29:28 PM3/24/03
to
> From: Michael Hudson [mailto:m...@python.net]

>
> "Delaney, Timothy C (Timothy)" <tdel...@avaya.com> writes:
>
> [__init__ vs. __new__]
>
> > Because most of the time we want to work with fully-constructed
> > objects. You can't use overridden methods in a constructor - only
> > from an initialiser.
>
> What makes you say that?
>
> Doing too much C++ lately?

Unfortunately, yes :(

How about this then ... using __init__ is simpler in most cases ;)

Tim Delaney

David Abrahams

unread,
Mar 24, 2003, 9:48:57 PM3/24/03
to
"Delaney, Timothy C (Timothy)" <tdel...@avaya.com> writes:

> Because most of the time we want to work with fully-constructed
> objects. You can't use overridden methods in a constructor - only
> from an initialiser.

Even if that were true, it's seldom needed (only comes up rarely in
C++ where you can't do that) and often incorrect in any case because
the derived class isn't constructed yet.

David Abrahams

unread,
Mar 24, 2003, 9:49:28 PM3/24/03
to
"Delaney, Timothy C (Timothy)" <tdel...@avaya.com> writes:

> How about this then ... using __init__ is simpler in most cases ;)

What's simpler about it?

Alex Martelli

unread,
Mar 25, 2003, 5:52:29 AM3/25/03
to
<posted & mailed>

David Abrahams wrote:

> "Delaney, Timothy C (Timothy)" <tdel...@avaya.com> writes:
>
>> Because most of the time we want to work with fully-constructed
>> objects. You can't use overridden methods in a constructor - only
>> from an initialiser.
>
> Even if that were true, it's seldom needed (only comes up rarely in
> C++ where you can't do that) and often incorrect in any case because
> the derived class isn't constructed yet.

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
to give the object a chance to do substantial initialization work
before the object got totally hooked up to the framework and
started being used in earnest. The key issue was often that, for
a constructor, you could never be sure if the object WAS fully
constructed (or if you were in a base class) -- no such problems
with 'initialize', where you could rest serene that you WERE
executing in the "leaf class", with a fully constructed object,
period.

For example say that the purpose of my class is modeling a
window object in some underlying windowing system. The actual
window object must be costructed only once, and it must be
constructed with detailed parameters as determined/overridden
by the leaf class -- so no constructor of a class that might
possibly be used as a BASE class dare invoke the underlying
window system's "create window" operation, because the ctor
of more-derived classes haven't been run yet so it's uncertain
which parameters should in fact be used for that operation.
Whence, the need for two-phase init -- the underlying window
is constructed in the 'initialize' (second phase) so the leaf
class has full power to control window creation parameters.

Classes modeling connections to external entities of many
kinds often have similar issues -- i.e. from this POV there
is no necessary difference between "creating the underlying
window object" and "opening the connection to the database",
say -- in both cases there are creation/opening parameters
and the potential need to override them, etc, etc.

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++. ctor's just labor
under too many constraints to make them fully satisfactory
for all cases of initialization needs.


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


Alex


David Abrahams

unread,
Mar 25, 2003, 4:23:20 PM3/25/03
to al...@aleax.it

====== DISCLAIMER ========

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

====== DISCLAIMER ========

Alex Martelli <al...@aleax.it> writes:

> <posted & mailed>
>
> David Abrahams wrote:
>
>> "Delaney, Timothy C (Timothy)" <tdel...@avaya.com> writes:
>>
>>> Because most of the time we want to work with fully-constructed
>>> objects. You can't use overridden methods in a constructor - only
>>> from an initialiser.
>>
>> Even if that were true, it's seldom needed (only comes up rarely in
>> C++ where you can't do that) and often incorrect in any case
>> because the derived class isn't constructed yet.
>
> 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
> to give the object a chance to do substantial initialization work
> before the object got totally hooked up to the framework and
> started being used in earnest. The key issue was often that, for
> a constructor, you could never be sure if the object WAS fully
> constructed (or if you were in a base class) -- no such problems
> with 'initialize', where you could rest serene that you WERE
> executing in the "leaf class", with a fully constructed object,
> period.

I'm not convinced of your need yet.

> For example say that the purpose of my class is modeling a
> window object in some underlying windowing system. The actual
> window object must be costructed only once, and it must be
> constructed with detailed parameters as determined/overridden
> by the leaf class -- so no constructor of a class that might
> possibly be used as a BASE class dare invoke the underlying
> window system's "create window" operation, because the ctor
> of more-derived classes haven't been run yet so it's uncertain
> which parameters should in fact be used for that operation.
> Whence, the need for two-phase init -- the underlying window
> is constructed in the 'initialize' (second phase) so the leaf
> class has full power to control window creation parameters.

The usual answer to that is: "don't rely on a virtual function call to
get those parameters; instead have the derived class pass them to the
base class constructor". TOOWTDI.

> Classes modeling connections to external entities of many
> kinds often have similar issues -- i.e. from this POV there
> is no necessary difference between "creating the underlying
> window object" and "opening the connection to the database",
> say -- in both cases there are creation/opening parameters
> and the potential need to override them, etc, etc.

Ditto. Those are constructor parameters.

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

Furthermore, being disciplined about fully-initializing objects in the
constructor has positive effects on the complexity of invariants and
on exception-safety. Anytime I find myself thinking about virtual
calls from constructors I return to that discipline and am reminded of
how well it works.

> ctor's just labor under too many constraints to make them fully
> satisfactory for all cases of initialization needs.

I didn't say "all"; just the great majority.

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

Oh, right, because __new__ essentially does the allocation, yes?
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.

Alex Martelli

unread,
Mar 26, 2003, 7:01:54 AM3/26/03
to
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 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

0 new messages