Path: csiph.com!x330-a1.tempe.blueboxinc.net!usenet.pasdenom.info!aioe.org!feeder.news-service.com!newsfeed.xs4all.nl!newsfeed6.news.xs4all.nl!xs4all!post.news.xs4all.nl!not-for-mail Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.000 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'python,': 0.01; 'circular': 0.03; 'handled': 0.03; 'sys': 0.04; 'formed': 0.07; 'method,': 0.07; 'pep': 0.07; 'python)': 0.07; 'python': 0.07; "(it's": 0.09; 'called.': 0.09; 'cleaned': 0.09; 'garbage': 0.09; 'python-level': 0.09; 'referenced': 0.09; 'restriction': 0.09; 'subject:object': 0.09; 'def': 0.13; '(below).': 0.16; '(excluding': 0.16; 'callback': 0.16; 'default),': 0.16; 'destructor': 0.16; 'leaks': 0.16; 'received:96.54': 0.16; 'scope,': 0.16; 'subject:exists': 0.16; 'invalid': 0.16; 'exists': 0.19; 'object,': 0.19; 'variable': 0.21; 'code': 0.22; 'e.g.': 0.22; 'subject:code': 0.23; 'world!': 0.23; 'objects': 0.24; 'example': 0.24; 'memory': 0.24; 'skip:# 10': 0.25; 'suspect': 0.25; 'object': 0.27; "doesn't": 0.28; 'class': 0.29; "python's": 0.29; 'cycles': 0.31; 'detector': 0.31; 'enabled': 0.31; 'received:96': 0.31; "can't": 0.31; 'import': 0.32; 'called': 0.32; 'to:addr:python-list': 0.32; 'option': 0.33; 'creates': 0.33; 'reference': 0.34; 'received:192': 0.34; 'skip:" 10': 0.34; 'received:70': 0.34; 'there': 0.35; 'print': 0.35; 'point': 0.35; 'received:192.168': 0.37; 'some': 0.37; 'however': 0.37; 'self': 0.37; 'should': 0.37; 'cycle': 0.38; 'references': 0.38; 'but': 0.38; 'current': 0.38; 'signal': 0.39; 'to:addr:python.org': 0.39; 'header:Mime-Version:1': 0.39; 'how': 0.39; 'works': 0.40; 'behavior': 0.40; "it's": 0.40; 'subject': 0.61; 'order': 0.61; 'cause': 0.65; 'below,': 0.71; 'exotic': 0.84; 'off"': 0.84; 'reachable': 0.84; 'reference?': 0.84; 'tricky': 0.84; 'involved.': 0.91; 'killed': 0.91; 'subject:call': 0.91 X-Cloudmark-SP-Filtered: true X-Cloudmark-SP-Result: v=1.1 cv=l0hTUOwXBBZC3xuFbE1ochJnP84ph5EBJB8ZSEz2E7A= c=1 sm=1 a=ce0y7WToGDYA:10 a=qWYXSweIvAIA:10 a=BLceEmwcHowA:10 a=LYYm-LO_OfoA:10 a=RBKHmHSr9-IA:10 a=IkcTkHD0fZMA:10 a=b0KxtKZxmKbTALECVfpD+Q==:17 a=ix6Hxt0jKgOGdwMXI3EA:9 a=iI5xN9I5EabL2H6nA7AA:7 a=QEXdDO2ut3YA:10 a=HpAAvcLHHh0Zw7uRqdWCyQ==:117 X-IronPort-AV: E=Sophos;i="4.64,276,1301896800"; d="scan'208";a="427297414" X-reinject: true Subject: Reliably call code after object no longer exists or is "unreachable"? From: Jack Bates To: python-list@python.org Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Date: Wed, 27 Apr 2011 09:48:42 -0700 Mime-Version: 1.0 X-Mailer: Evolution 2.32.2 X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Newsgroups: comp.lang.python Message-ID: Lines: 133 NNTP-Posting-Host: 82.94.164.166 X-Trace: 1303940830 news.xs4all.nl 81473 [::ffff:82.94.164.166]:45635 X-Complaints-To: abuse@xs4all.nl Xref: x330-a1.tempe.blueboxinc.net comp.lang.python:4166 In Python, how can you reliably call code - but wait until an object no longer exists or is "unreachable"? I want to ensure that some code is called (excluding some exotic situations like when the program is killed by a signal not handled by Python) but can't call it immediately. I want to wait until there are no references to an object - or the only references to the object are from unreachable reference cycles #!/usr/bin/env python class Goodbye: def __del__(self): print 'Goodbye, world!' ref =3D Goodbye() $ ./goodbye Goodbye, world! $=20 Python's __del__ or destructor method works (above) - but only in the absence of reference cycles (below). An object, with a __del__ method, in a reference cycle, causes all objects in the cycle to be "uncollectable". This can cause memory leaks and because the object is never collected, its __del__ method is never called > Circular references which are garbage are detected when the option > cycle detector is enabled (it's on by default), but can only be > cleaned up if there are no Python-level __del__() methods involved. #!/usr/bin/env python class Goodbye: def __del__(self): print 'Goodbye, world!' class Cycle: def __init__(self, cycle): self.next =3D cycle cycle.next =3D self Cycle(Goodbye()) $ ./cycle $=20 In PEP 342 I read that an object, with a __del__ method, referenced by a cycle but not itself participating in the cycle, doesn't cause objects to be uncollectable. If the cycle is "collectable" then when it's eventually collected by the garbage collector, the __del__ method is called > If the generator object participates in a cycle, g.__del__() may not > be called. This is the behavior of CPython's current garbage > collector. The reason for the restriction is that the GC code needs to > "break" a cycle at an arbitrary point in order to collect it, and from > then on no Python code should be allowed to see the objects that > formed the cycle, as they may be in an invalid state. Objects "hanging > off" a cycle are not subject to this restriction. #!/usr/bin/env python import sys class Destruct: def __init__(self, callback): self.__del__ =3D callback class Goodbye: def __init__(self): self.destruct =3D Destruct(lambda: sys.stdout.write('Goodbye, world!\n'= )) class Cycle: def __init__(self, cycle): self.next =3D cycle cycle.next =3D self Cycle(Goodbye()) $ ./dangle Goodbye, world! $=20 However it's *extremely* tricky to ensure that the object with a __del__ method doesn't participate in a cycle, e.g. in the example below, the __del__ method is never called - I suspect because the object with a __del__ method is reachable from the global scope, and this forms a cycle with a frame's f_globals reference? "storing a generator object in a global variable creates a cycle via the generator frame's f_globals pointer" #!/usr/bin/env python import sys class Destruct: def __init__(self, callback): self.__del__ =3D callback class Goodbye: def __init__(self): self.destruct =3D Destruct(lambda: sys.stdout.write('Goodbye, world!\n'= )) class Cycle: def __init__(self, cycle): self.next =3D cycle cycle.next =3D self ref =3D Cycle(Goodbye()) $ ./global $=20 Faced with the real potential for reference cycles, how can you reliably call code - but wait until an object no longer exists or is "unreachable"?