gc exposes the underlying memory management mechanism of Python, the automatic garbage collector. The module includes functions for controlling how the collector operates and to examine the objects known to the system, either pending collection or stuck in reference cycles and unable to be freed.
In [1]:
import gc
import pprint
class Graph:
def __init__(self, name):
self.name = name
self.next = None
def set_next(self, next):
print('Linking nodes {}.next = {}'.format(self, next))
self.next = next
def __repr__(self):
return '{}({})'.format(
self.__class__.__name__, self.name)
# Construct a graph cycle
one = Graph('one')
two = Graph('two')
three = Graph('three')
one.set_next(two)
two.set_next(three)
three.set_next(one)
print()
print('three refers to:')
for r in gc.get_referents(three):
pprint.pprint(r)
In [2]:
import gc
import pprint
import queue
class Graph:
def __init__(self, name):
self.name = name
self.next = None
def set_next(self, next):
print('Linking nodes {}.next = {}'.format(self, next))
self.next = next
def __repr__(self):
return '{}({})'.format(
self.__class__.__name__, self.name)
# Construct a graph cycle
one = Graph('one')
two = Graph('two')
three = Graph('three')
one.set_next(two)
two.set_next(three)
three.set_next(one)
print()
seen = set()
to_process = queue.Queue()
# Start with an empty object chain and Graph three.
to_process.put(([], three))
# Look for cycles, building the object chain for each object
# found in the queue so the full cycle can be printed at the
# end.
while not to_process.empty():
chain, next = to_process.get()
chain = chain[:]
chain.append(next)
print('Examining:', repr(next))
seen.add(id(next))
for r in gc.get_referents(next):
if isinstance(r, str) or isinstance(r, type):
# Ignore strings and classes
pass
elif id(r) in seen:
print()
print('Found a cycle to {}:'.format(r))
for i, link in enumerate(chain):
print(' {}: '.format(i), end=' ')
pprint.pprint(link)
else:
to_process.put((chain, r))
In [3]:
import gc
import pprint
class Graph:
def __init__(self, name):
self.name = name
self.next = None
def set_next(self, next):
print('Linking nodes {}.next = {}'.format(self, next))
self.next = next
def __repr__(self):
return '{}({})'.format(
self.__class__.__name__, self.name)
# Construct a graph cycle
one = Graph('one')
two = Graph('two')
three = Graph('three')
one.set_next(two)
two.set_next(three)
three.set_next(one)
# Remove references to the graph nodes in this module's namespace
one = two = three = None
# Show the effect of garbage collection
for i in range(2):
print('\nCollecting {} ...'.format(i))
n = gc.collect()
print('Unreachable objects:', n)
print('Remaining Garbage:', end=' ')
pprint.pprint(gc.garbage)
In [4]:
import gc
print(gc.get_threshold())
In [5]:
import gc
gc.set_debug(gc.DEBUG_STATS)
gc.collect()
print('Exiting')
In [6]:
import gc
flags = (gc.DEBUG_COLLECTABLE |
gc.DEBUG_UNCOLLECTABLE |
gc.DEBUG_SAVEALL
)
gc.set_debug(flags)
class Graph:
def __init__(self, name):
self.name = name
self.next = None
def set_next(self, next):
self.next = next
def __repr__(self):
return '{}({})'.format(
self.__class__.__name__, self.name)
class CleanupGraph(Graph):
def __del__(self):
print('{}.__del__()'.format(self))
# Construct a graph cycle
one = Graph('one')
two = Graph('two')
one.set_next(two)
two.set_next(one)
# Construct another node that stands on its own
three = CleanupGraph('three')
# Construct a graph cycle with a finalizer
four = CleanupGraph('four')
five = CleanupGraph('five')
four.set_next(five)
five.set_next(four)
# Remove references to the graph nodes in this module's namespace
one = two = three = four = five = None
# Force a sweep
print('Collecting')
gc.collect()
print('Done')
# Report on what was left
for o in gc.garbage:
if isinstance(o, Graph):
print('Retained: {} 0x{:x}'.format(o, id(o)))
# Reset the debug flags before exiting to avoid dumping a lot
# of extra information and making the example output more
# confusing.
gc.set_debug(0)