[Python-3000] Removing __del__
Chermside, Michael
mchermside at ingdirect.com
Fri Sep 22 15:04:49 CEST 2006
I don't seem to have gotten anyone one board with the bold proposal
to just rip __del__ out and tell people to learn to use weakrefs.
But I'm hearing general agreement (at least among those contributing
to this thread) that it might be wise to change the status quo.
The two kinds of solutions I'm hearing are (1) those that are based
around making a helper object that gets stored as an attribute in
the object, or a list of weakrefs, or something like that, and (2)
the __close__ proposal (or perhaps keep the name __del__ but change
the semantics.
The difficulties with (1) that have been acknowledged so far are
that the way you code things becomes somewhat less obvious, and
that there is the possibility of accidentally creating immortal
objects through reference loops.
I would like to hear someone address the weaknesses of (2). The
first I know of is that the code in your __close__ method (or
__del__) must assume that it might have been in a reference loop
which was broken in some arbitrary place. As a result, it cannot
assume that all references it holds are still valid. To avoid
crashing the system, we'd probably have to set the broken
references to None (is that the best choice?), but can people
really write code that has to run assuming that its references
might be invalid?
A second problem I know of is, what if the code stores a reference
to self someplace? The ability for __del__ methods to resurrect
the object being finalized is one of the major sources of
complexity in the GC module, and changing the semantics to
__close__ doesn't fix this.
Does anyone defending __close__ want to address these issues?
-------- examples only below this line --------
Just in case it isn't clear enough, I wanted to put together
some examples. First, I'll do the kind of problem that __close__
handles well:
class MyClass(object):
def __init__(self, resource1_name, resource2_name):
self.resource1 = acquire_resource(resource1_name)
self.resource2 = acquire_resource(resource2_name)
def close(self):
self.resource1.release()
self.resource2.release()
def __close__(self):
self.close()
This is the simplest example I could think of for an object
which needs to call self.close() when it is freed in order to
release resources.
Now let's imagine creating a loop with such an object.
x = MyClass('db1', 'db2')
y = MyClass('db3', 'db4')
x.next = y
y.next = x
In today's world, with __del__ instead of __close__ such a
loop would be immortal (and the resources would never be
released). And it would work fine with __close__ semantics
because the __close__ method doesn't use self.next. So this
one is just fine.
The danger in __close__ is when something used (if only
indirectly) by the __close__ method participates in the loop.
We will modify the original example by adding a flush()
method which flushes the resources and calling it in close():
class MyClass2(object):
def __init__(self, resource1_name, resource2_name):
self.resource1 = acquire_resource(resource1_name)
self.resource2 = acquire_resource(resource2_name)
def flush(self):
self.resource1.flush()
self.resource2.flush()
if hasattr(self, 'next'):
self.next.flush()
def close(self):
self.resource1.release()
self.resource2.release()
def __close__(self):
self.flush()
self.close()
x = MyClass2('db1', 'db2')
y = MyClass2('db3', 'db4')
x.next = y
y.next = x
This version will encounter a problem. When the GC sees
the x <--> y loop it will break it somewhere... without
loss of generality, let us say it breaks the y -> x link
by setting y.next to None. Now y will be freed, so
__close__ will be called. __close__ will invoke self.flush()
which will then try to invoke self.next.flush(). But
self.next is None, so we'll get an exception and never
make it to invoking self.close().
------
The other problem I discussed is illustrated by the following
malicious code:
evil_list = []
class MyEvilClass(object):
def __close__(self):
evil_list.append(self)
Do the proponents of __close__ propose a way of prohibiting
this behavior? Or do we continue to include complicated
logic the GC module to support it? I don't think anyone
cares how this code behaves so long as it doesn't segfault.
-- Michael Chermside
*****************************************************************************
This email may contain confidential or privileged information. If you believe
you have received the message in error, please notify the sender and delete
the message without copying or disclosing it.
*****************************************************************************
More information about the Python-3000
mailing list