Generator inside a class prevent __del__ ??

E

Emmanuel

Hi,

I run across this problem, and couldn't find any solution (python 2.2.2)
:

Code :
===========
from __future__ import generators
def __init__(self):
print "init"
def __del__(self):
print "del"
def Gen(self):
yield 1
del
==============
Here, everything is normal...
But creating a generator :

Code :
===========
def __init__(self):
print "init"
self.Coroutine = self.Gen()
def __del__(self):
print "del"
def Gen(self):
yield 1
<--- Nothing there !!!
==============

I can't understand why the destructor is not called when a generator is
created, and what I should do to have a "correct" behavior.
(perhaps I missed something obvious, but I can't find it )
Thank you for any help,

Emmanuel
 
T

Terry Reedy

Emmanuel said:
I run across this problem, and couldn't find any solution (python 2.2.2) ....
Here, everything is normal...
But creating a generator :

You both defined generator function and called it to create generator
iterator.
Code :
===========

def __init__(self):
print "init"
self.Coroutine = self.Gen()

This creates a reference loop. Delete this (and correct typo below) and
'problem' will disappear.
def __del__(self):
print "del"
def Gen(self):

If you do not really use self in the resulting iterator, define this
outside of the class without self as a parameter, and problem will
disappear.

did you mean 'c = toto()'?
init
<--- Nothing there !!!
==============

I can't understand why the destructor is not called when a generator is
created, and what I should do to have a "correct" behavior.

Either do not create reference loop or break it with del c.Coroutine.

Terry J. Reedy
 
M

Mark Day

def __init__(self):
print "init"
self.Coroutine = self.Gen()
def __del__(self):
print "del"
def Gen(self):
yield 1
a = toto() init
c = []
<--- Nothing there !!![/QUOTE]

First of all, "a" is still referencing your toto object. I think you
meant "a = []" here. But even if you did "a = []", the destructor
still isn't called. There must still be a reference to the object. My
guess is that the generator (directly or indirectly) is referencing the
object, creating a self referential loop.

Consider the following modification that merely references a function,
and does not create a generator:
.... def __init__(self):
.... print "init"
.... self.Coroutine = self.Gen
.... def __del__(self):
.... print "del"
.... def Gen(self):
.... pass
....

Here's how to break that loop:
b=tata() init
b.Coroutine=None
b=[] del

-Mark
 
R

Rob Nikander

Mark said:
still isn't called. There must still be a reference to the object. My
guess is that the generator (directly or indirectly) is referencing the
object, creating a self referential loop.

Python has a garbage collector that will try to find these objects with
cyclic references.

from test import *
>>> a = toto() init
>>> a = None
>>> import gc
>>> gc.garbage []
>>> gc.collect() 4
>>> gc.garbage
[ said:

I checked out the documentation for that gc.garbage list and it says
that the collector can't free objects in cycles if the cyles have
objects that have __del__ methods. So it puts them in this list.

I wonder what other garbage collectors do in this situation? Anyone
know? Java?

Rob
 
D

Duncan Booth

I checked out the documentation for that gc.garbage list and it says
that the collector can't free objects in cycles if the cyles have
objects that have __del__ methods. So it puts them in this list.

I wonder what other garbage collectors do in this situation? Anyone
know? Java?

Most garbage collectors will do peculiar things if you have destructors or
finalizers in the objects. The problem is that if two objects with
finalizers reference each other there is no correct order to release the
objects that will guarantee that the other object still exists, so the
system either has to choose an arbitrary order, or refuse to call the
finalizers.

The .Net garbage collector is typical. Objects may have finalizers, and
these finalizers are called as part of the garbage collection. The system
guarantees that any finalizer is called exactly 0 or more times --- usually
it is called once when the object is garbage collected, but if the object
is never collected it may not be called at all, and if the object
resurrects itself (e.g. during the finalizer it assigns itself to a global
variable) the finalizer could be called more than once.

A separate thread pool is used for finalizers, so your finalizer could be
called while a user thread is executing a method on the object, and two
objects which refer to each other could have their finalizers called in any
order, or even simultaneously on separate threads. Effectively, this makes
finalizers useless in all but the most obscure situations.

When resources need to be released you should try to do it explicitly. In
..Net this is handled by the Dispose() method, and the finalizer can then
either try calling Dispose() if it has not yet been called, or could try
logging an error although even that may be problematic from a finalizer.
 
E

Emmanuel

Terry Reedy a écrit :
You both defined generator function and called it to create generator
iterator.

Yes, I don't have all the generators vocabulary yet...
This creates a reference loop. Delete this (and correct typo below) and
'problem' will disappear.

If you do not really use self in the resulting iterator, define this
outside of the class without self as a parameter, and problem will
disappear.

I didn't use self in order to provide a simple example. In my real class, self
is used...
did you mean 'c = toto()'?

Yes, sorry for that...
init
<--- Nothing there !!!
==============

I can't understand why the destructor is not called when a generator is
created, and what I should do to have a "correct" behavior.

Either do not create reference loop or break it with del c.Coroutine.

Terry J. Reedy

Thank you very much for your answer, but I'm still not sure I understand it.
If I understand your words right, creating self.Coroutine as an iterator on
the generator function will create a reference on self, so if I want to use a
generator in a class ( and I really want to ), I must delete explicitly the
iterator before I destroy the object.

Trouble is, I _would_ like not to care about the lifetime of the object, and I
don't know where it will be destroyed.
Should I encapsulate this object in another one, like this :

import toto

class TotoCapsule:
def __init__( self ):
self.toto = toto.toto()
def __del__(self):
del self.toto.Coroutine
self.toto = None

And use TotoCapsule ?
But it means I have to write a lot of more code to access toto's method.
Is there a pattern I missed to dea l with that ?

Thanks a lot,

Emmanuel
 
T

Terry Reedy

Emmanuel said:
Terry Reedy a écrit :

To amplify: the usual idiom for an instance-associated generator is to name
the generator function (method) __iter__ (with one param, self) and to
create and get a reference to the generator via iter() or let the for loop
mechanism do so for you.

c = C(*args)
cgen =iter(c)

Then there is no reference loop. And you can pass around the cgen object
just like any other. If you only need the instance after initialization to
get the generator and you only need one generator for the instance, then
combine the two lines into

cgen = iter(C(*args))

and the *only* reference to the instance is the one in the generator, which
will disappear at the end of a for loop or with an explicit 'del cgen'.

There is also the question whether you actually *need* to get rid of the
object while the program is still running instead of just letting the
program finish and clean up.

Terry J. Reedy
 
A

Andrew Bennetts

Trouble is, I _would_ like not to care about the lifetime of the object, and I
don't know where it will be destroyed.

Then don't use __del__. Python can and will automatically collect cycles
when the objects *don't* define __del__ methods.

Out of curiousity, why are you defining __del__ anyway?

-Andrew.
 
E

Emmanuel

Andrew Bennetts a écrit :
Then don't use __del__. Python can and will automatically collect cycles
when the objects *don't* define __del__ methods.

Out of curiousity, why are you defining __del__ anyway?

-Andrew.



I don't want to use __del__, but I suspected I had an issue with the destruction of
my objects, and used a log in __del__ to monitor the destruction.

But defining __del__ has also a lot of valuable utilisation, or so I think...

Emmanuel
 
J

Joe Mason

Thank you very much for your answer, but I'm still not sure I understand it.
If I understand your words right, creating self.Coroutine as an iterator on
the generator function will create a reference on self, so if I want to use a
generator in a class ( and I really want to ), I must delete explicitly the
iterator before I destroy the object.

Trouble is, I _would_ like not to care about the lifetime of the object, and I
don't know where it will be destroyed.

Try looking up "weakref". (I've never used them myself, so I don't know
the exact syntax.)

Joe
 
A

Andrew Bennetts

Andrew Bennetts a écrit :


I don't want to use __del__, but I suspected I had an issue with the destruction of
my objects, and used a log in __del__ to monitor the destruction.

Except that __del__ affects how they are destructed :)

Weakrefs are probably a better choice for this, as they don't interfere with
the lifecycle of the object you're interested in, unlike __del__.
But defining __del__ has also a lot of valuable utilisation, or so I think...

It's only very very rarely useful, in my experience. Again, weakrefs are
probably more useful for what you have in mind.

-Andrew.
 
E

Emmanuel

Andrew Bennetts a écrit :
Except that __del__ affects how they are destructed :)

Weakrefs are probably a better choice for this, as they don't interfere with
the lifecycle of the object you're interested in, unlike __del__.


It's only very very rarely useful, in my experience. Again, weakrefs are
probably more useful for what you have in mind.

-Andrew.

Ok, I think I don't understand anything anymore...

I thought __del__ was the destructor of the object, like the object::~object in C++ ( my
experience in programming is mainly from C++ ), and so __del__ shouldn't affect when they
are destructed.
And I thought weakref is a way to control the lifetime, ie when the ref count is
decremented, and when to call __del__.

From what you ( and others ) are saying, I'm proven wrong...

Do you know where I can find more information, beside python doc ?

Thanks,

Emmanuel
 
E

Emmanuel

Terry Reedy a écrit :
To amplify: the usual idiom for an instance-associated generator is to name
the generator function (method) __iter__ (with one param, self) and to
create and get a reference to the generator via iter() or let the for loop
mechanism do so for you.

c = C(*args)
cgen =iter(c)

Then there is no reference loop. And you can pass around the cgen object
just like any other. If you only need the instance after initialization to
get the generator and you only need one generator for the instance, then
combine the two lines into

cgen = iter(C(*args))

and the *only* reference to the instance is the one in the generator, which
will disappear at the end of a for loop or with an explicit 'del cgen'.

But I obviously need other references to my object in my code, my object isn't
modified by the generator only.
I want to resume my generator from time to time during the execution of my app,
and to modify the members of the objects somewhere else ( interaction between
my objects ).
Doing this result in my nicer programmation style than without generators.

There is also the question whether you actually *need* to get rid of the
object while the program is still running instead of just letting the
program finish and clean up.

I have a _lot_ of objects created whenever they want, and I don't know where
they will finish their job.
Additionnaly, I'm not developping only on PC, but also on platforms where there
is not so much memory avalaible.

By the way, it seems I still have a lot to understand on this subject.
Do you know any link, example, or whatever, that I could have a look at ?

Thank you very much for your answers,

Emmanuel
 
A

Andrew Bennetts

Ok, I think I don't understand anything anymore...

I thought __del__ was the destructor of the object, like the object::~object in C++ ( my
experience in programming is mainly from C++ ), and so __del__ shouldn't affect when they
are destructed.

__del__ unfortunately *does* impact the lifetime of the object, at least in
CPython:
http://docs.python.org/lib/module-gc.html#l2h-403
http://docs.python.org/ref/customization.html#l2h-175

It's main use used to be to break reference cycles, because before Python
2.0 (or perhaps 1.6?), it couldn't automatically collect reference cycles
because it used a purely ref-count based approach. Now that cycles are
automatically collected, there's not much point in defining __del__ (and it
can actually have unexpected results).
And I thought weakref is a way to control the lifetime, ie when the ref count is
decremented, and when to call __del__.

No -- weakref doesn't affect the lifetime, that's it's point. It's a way to
have a reference to an object that doesn't keep the object alive if nothing
else is. As the documentation at
http://docs.python.org/lib/module-weakref.html says:

A weak reference to an object is not enough to keep the object alive:
when the only remaining references to a referent are weak references,
garbage collection is free to destroy the referent and reuse its memory
for something else.
Do you know where I can find more information, beside python doc ?

Try googling for tutorials and things, there's probably stuff out there.
The Python docs are pretty good, though... the weakref module has pretty
comprehensive documentation, and the description of __del__ in the language
reference has big note that mentions that garbage-collection of cycles
doesn't work when __del__ methods are involved.

I've also found books such as Python in a Nutshell and the Python Essential
Reference to be quite good at pointing this sort of thing out, when I've
looked. I usually rely on the official Python docs, though.

-Andrew.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top