Tim Peters tim_one at
Mon Jun 21 00:02:43 EDT 1999

[Hisao Suzuki, still likes his anlaogy between explicitly delete'ing
 in C++, and explicitly removing all references in CPython]

You say my position "sounds too particular about superficial differences",
and I say yours reaches too far from superficial similarities.  We're never
going to agree on this one, so I'll leave it there.

> ...
> All in all, each idea is a valid model for the current Python.
> The definitive difference lies in the semantics of language it
> relies on.  Regarding the favor of destruction predictability
> such as seen in this newsgroup, the current actual semantics is
> perhaps as important as the official one.

Trying to nudge this in a useful direction <wink>, the "current actual
semantics" don't suggest anything about what "should be" done in the
presence of cycles with destructors in a GC world, except for the obvious
"reclaim a cycle the instant it becomes unreachable".  That is doable but--
I think --not cheaply enough to be practical.  So what then?  BTW, I'm told
that most of the C++ committee disagrees with Stroustrup on this one -- the
notion that an object can be destroyed without its destructor getting
invoked is distasteful at first bite, and leaves a bad taste even after
three helpings of creative rationalization <wink>.

If there's a good argument to made for it, it should rest where Guido left
it:  the behavior falls out of the simplest implementation, and that's
really all there is to it:  you *put up* with the semantics that fall out,
hoping they're not too bad in practice.  Lots of things work like that
(floating-point arithmetic is a particularly miserable example <0.3 wink>);
sometimes it's just the best you can get.  Let's not pretend it's a good
thing for "deep reasons", though!

>| Further the C++ 3rd Ed. says:
>|    "It is possible to design a garbage collector to invoke the
>|     destructors for objects that have been specifically
>|     registered' with the collector.  However, there is no
>|     standard way of `registering' objects.  Note that it is
>|     always important to destroy objects in an order that ensures
>|     that the destructor for one object doesn't refer to an
>|     object that has been previously destroyed.  Such ordering
>|     isn't easily achieved by a garbage collector without help
>|     from the programmer."

[Tim sez Java solved that one]

> Even if the storage of such objects are not reclaimed yet, their
> finalizer()s may have been already invoked.  This may be a real
> problem for aggregation objects (if you once rely on finalizers
> in Java).  Note that Stroustrup's "destroy" implies "call the
> destructor".

We don't have an argument about Java (at least none that I've been able to
detect <wink>), and Stroustrup wasn't writing about Java.  His "destroy"--
being about C++ --*also* implies "nuke the memory", which isn't the case in
Java.  Java does guarantee that the memory of every object reachable from a
finalizer is wholly intact, so-- again --Java addresses the point Stroustrup
is making (which was in support of his view that GC not invoke destructors
at all).  GC simply doesn't need any help from the programmer to guarantee
that much; although Stroustrup is right (Graham notwithstanding <wink>) that
it's not easily achieved.

[quotes from the JLS, and a Java aggregation example where the order
 finalizers run in is important]
> ...
> You see, to implement such a class needs _help_ from the
> programmer.

Certainly.  It's a different issue, though:  GC doesn't need help to avoid
referencing bogus memory, it needs help to implement the intent of the
algorithm.  If Stroustrup were concerned about the latter, he would not have
said "such ordering isn't easily achieved ... without help", he would have
said such ordering is *impossible* to achieve without programmer
intervention.  He's a careful writer.

Even in the absence of cycles, CPython today doesn't (and can't!) guarantee
to run finalizers in the order your code may need; e.g., if you run this
today under 1.5.2:

class Parent:
    def __init__(self): = []
    def add_child(self, name):
    def __del__(self):
        global estate
        estate = 1000000
        print "parent died, leaving", estate, "dollars"
        for k in
            print, "wants a share of the cash!"

class Child:
    def __init__(self, name): = name
    def __del__(self):
        global estate
        me = estate / 2
        estate = estate - me
        print, "died of grief with", me, "of those bucks"

p = Parent()
del p

It prints:

parent died, leaving 1000000 dollars
Nancy wants a share of the cash!
Tim wants a share of the cash!
Nancy died of grief with 500000 of those bucks
Tim died of grief with 250000 of those bucks

But if you run it under the current CVS Python snapshot, Tim gets the half
million and poor Nancy gets 250000 (lists get destroyed in reverse order in
the development version).

The order of finalizer invocation certainly matters a lot here (well, to me
and my sister, or at least to the charities in our wills <wink>), and it's
impossible for Python-- or any other language --to guess the intent.

All that can be said is that refcount rules *usually* avoid such surprises;
in the end, though, it's still up to you to force ordering when you need it.

BTW, that does suggest one "natural" extension of CPython semantics to
cycles:  since CPython today doesn't guarantee anything about the order in
which finalizers are called when more than one object loses its last
reference at the same time, it would be consistent to run finalizers for
members of GC'ed cycles in an arbitrary order too; and if your program
relies on a particular order, tough.

One bad aspect is that it's a rule the compiler+runtime can't enforce, in
the sense that violations can't be detected.  Python tries (much harder than
C++) to avoid rules like that.

it's-a-good-game-that-can't-be-won<wink>-ly y'rs  - tim

More information about the Python-list mailing list