Skip to content

Instantly share code, notes, and snippets.

@fdb
Created April 5, 2010 14:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fdb/356404 to your computer and use it in GitHub Desktop.
Save fdb/356404 to your computer and use it in GitHub Desktop.
import gc
import weakref
_destroyed = False
_superDestroyed = False
class MyClass(object):
'''A simple class that sets a global variable when it is destroyed.
Optionally it can hold a cyclic reference when passing cyclic=True to the constructor.'''
def __init__(self, cyclic=False):
if cyclic:
self.cycle = self
def __del__(self):
global _destroyed
_destroyed = True
class BadSuperDestructor(MyClass):
'''Overrides MyClass and provides a custom destructor.
This implementation does not call the super destructor.'''
def __del__(self):
global _superDestroyed
_superDestroyed = True
class GoodSuperDestructor(MyClass):
'''Overrides MyClass and provides a custom destructor.
This implementation calls the super destructor in the finally clause.'''
def __del__(self):
try:
global _superDestroyed
_superDestroyed = True
finally:
super(GoodSuperDestructor, self).__del__()
class FaultyDestructor(object):
'''A class that raises an exception in its destructor.'''
def __del__(self):
global destroyed
raise RuntimeError("Destructor has an error.")
_destroyed = True
import unittest
class GarbageCollectionTest(unittest.TestCase):
'''Test the behavior of the CPython garbage collector.
The GC is very implementation-specific. All of these tests fail on Jython.'''
def setUp(self):
global _destroyed, _superDestroyed
_destroyed = False
_superDestroyed = False
gc.enable()
def testObject(self):
'''Test if the object is immediately destroyed after the last reference to it is removed.'''
mc = MyClass()
self.assertFalse(_destroyed)
del mc
self.assertTrue(_destroyed)
def testTwoReferences(self):
'''Test what happens if two references point to the same object.
Objects are destroyed as soon as all the references dissapear. '''
ref1 = ref2 = MyClass()
self.assertFalse(_destroyed)
del ref1
self.assertFalse(_destroyed)
ref2 = None
self.assertTrue(_destroyed)
def testWeakReferences(self):
'''Test if the weak reference affects garbage collection.
A weak reference holds a reference to an object, but this reference does not
increase the reference count. As soon as all the strong references are removed, the object
is garbage collected. Retrieving the referent (by calling the reference object) returns None.'''
strongRef = MyClass()
weakRef = weakref.ref(strongRef)
self.assertTrue(strongRef == weakRef())
self.assertFalse(_destroyed)
del strongRef
self.assertTrue(_destroyed)
self.assertTrue(weakRef() is None)
def testCycle(self):
'''Test if an object is removed if it holds a cyclic reference.
Cyclic references are removed by the garbage collector, not the reference-counting implementation.
Note that if objects with a __del__ method contain a cycle, they're not automatically garbage-collected.
Instead, they're put in the gc.garbage list. See http://docs.python.org/library/gc.html#gc.garbage
'''
mc = MyClass(cyclic=True)
self.assertFalse(_destroyed)
mc = None
self.assertFalse(_destroyed)
self.assertEquals(0, len(gc.garbage))
gc.collect()
self.assertFalse(_destroyed)
self.assertEquals(1, len(gc.garbage))
self.assertEquals(MyClass, type(gc.garbage[0]))
def testExplicitGarbageCollection(self):
'''Test if objects are still destroyed even with the GC disabled.
CPython has actually two "garbage collectors". A simple refence counting implementation cleans up
objects without cyclic dependencies. The real garbage collector cleans up object cycles that go
out of scope.
Since MyClass is not cyclic, it is immediately destroyed.'''
gc.disable()
ref = MyClass()
self.assertFalse(_destroyed)
del ref
self.assertTrue(_destroyed)
def testFaultyDestructor(self):
'''Test what happens if the destructor raises an exception.
The exception is ignored, but reported to sys.stderr.'''
ref = FaultyDestructor()
del ref
# The exception is ignored, but the rest of the destructor is not executed.
self.assertFalse(_destroyed)
def testBadSuperDestructor(self):
'''Test that the destructor of a superclass does not automatically call the subclass destructor.'''
ref = BadSuperDestructor()
del ref
self.assertTrue(_superDestroyed)
self.assertFalse(_destroyed)
def testGoodSuperDestructor(self):
'''Test the correct implementation for a superclass destructor, using try/finally.'''
ref = GoodSuperDestructor()
del ref
self.assertTrue(_superDestroyed)
self.assertTrue(_destroyed)
if __name__=='__main__':
unittest.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment