
[Phillip J. Eby]
Throwing an exception also provides ample opportunity for creating cycles, since the frame hold a reference to the most recent traceback. Ironically, throwing an exception with a traceback into a generator is likely to cause a cycle because the traceback likely references the throwing frame, which certainly has a reference to the generator...
*head exploding* Double ouch.
Wait a minute... those cycles don't include the generator, do they? Let me think. Frame A has a reference to the generator iterator, and invokes throw() (directly or indirectly) on it. Frame B, the generator frame, gets its f_back set to point to Frame A, but presumably that link is cleared on exit? (If it isn't, it probably should be).
Anyway, frame B throws an exception, and the traceback is created. The traceback has a reference to frame B. We return to frame A, and add it to the traceback as well, and a reference to the traceback goes into the frame too. Hm. Still no cycle passing through the *generator iterator*, unless the generator's frame's f_back is still pointing to the frame that it was last called from. This holds even if the generator's frame holds the traceback that was current at the time of the error, because that traceback only includes the generator's frame, not the caller's frame.
So, as long as the generator guarantees its frame's f_back is empty while the generator is not actually executing, the cycle should not include the generator, so it will still be GC'able. (Note, by the way, that this property is probably another good reason for using the yield expression as the source location for a throw()!)
Hm. The way I see it, as soon as a generator raises an exception, its frame is part of a cycle: the frame's f_exc_traceback points to the traceback object, and the traceback object's tb_frame points back to the frame. So that's a cycle right there. -- --Guido van Rossum (home page: http://www.python.org/~guido/)