Tim Peters wrote:
Note that it's very easy to do this with __del__. The trick is for your type not to have a __del__ method itself, but to point to a simple "cleanup object" with a __del__ method. Give that "contained" object references to the resources you want to close, and you're done. Because your "real" objects don't have __del__ methods, cycles involving them can't inhibit gc. The cleanup object's only purpose in life is to close resources. Like:
class _RealTypeResourceCleaner: def __init__(self, *resources): self.resources = resources
def __del__(self): if self.resources is not None: for r in self.resources: r.close() self.resources = None # and typically no other methods are needed, or desirable, in # this helper class
class RealType: def __init__(*args): ... # and then, e.g., self.cleaner = _ResourceCleaner(resource1, resource2)
... tons of stuff, but no __del__ ....
That's the simple, general way to mix cycles and __del__ without problems.
So, stealing this trick for generators would involve a "helper" object with a close() method, a __del__ method that invoked it, and access to the generator's frame stack (rather than to the generator itself).
class _GeneratorCleaner: __slots__ = ["_gen_frame"] def __init__(self, gen_frame): self._gen_frame = gen_frame def close(self): # Do whatever gen.close() currently does to the # raise GeneratorExit in the frame stack # and catch it again def __del__(self): self.close()
The generator's close() method would then change to be:
def close(self): self._cleaner.close()
Would something like that eliminate the current cycle problem?