Skip to content

Commit ca7ec17

Browse files
1 parent bf262f4 commit ca7ec17

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

pytools/debug.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,76 @@ def is_excluded(o):
139139
# }}}
140140

141141

142+
# {{{ Find circular references
143+
144+
# https://code.activestate.com/recipes/523004-find-cyclical-references/
145+
146+
import gc
147+
from types import FrameType
148+
149+
150+
def print_cycles(objects, outstream=sys.stdout, show_progress=True):
151+
"""
152+
objects: A list of objects to find cycles in. It is often useful
153+
to pass in gc.garbage to find the cycles that are
154+
preventing some objects from being garbage collected.
155+
outstream: The stream for output.
156+
show_progress: If True, print the number of objects reached as they are
157+
found.
158+
"""
159+
def print_path(path):
160+
for i, step in enumerate(path):
161+
# next_obj "wraps around"
162+
next_obj = path[(i + 1) % len(path)]
163+
164+
outstream.write(" %s -- " % str(type(step)))
165+
if isinstance(step, dict):
166+
for key, val in step.items():
167+
if val is next_obj:
168+
outstream.write("[%s]" % repr(key))
169+
break
170+
if key is next_obj:
171+
outstream.write("[key] = %s" % repr(val))
172+
break
173+
elif isinstance(step, list):
174+
outstream.write("[%d]" % step.index(next_obj))
175+
elif isinstance(step, tuple):
176+
outstream.write("[%d]" % step.index(next_obj))
177+
else:
178+
outstream.write(repr(step))
179+
outstream.write(" ->\n")
180+
outstream.write("\n")
181+
182+
def recurse(obj, start, all_objs, current_path):
183+
if show_progress:
184+
outstream.write("%d\r" % len(all_objs))
185+
186+
all_objs.add(id(obj))
187+
188+
referents = gc.get_referents(obj)
189+
for referent in referents:
190+
# If we've found our way back to the start, this is
191+
# a cycle, so print it out
192+
if referent is start:
193+
print_path(current_path)
194+
195+
# Don't go back through the original list of objects, or
196+
# through temporary references to the object, since those
197+
# are just an artifact of the cycle detector itself.
198+
elif referent is objects or isinstance(referent, FrameType):
199+
continue
200+
201+
# We haven't seen this object before, so recurse
202+
elif id(referent) not in all_objs:
203+
recurse(referent, start, all_objs, current_path + [obj])
204+
205+
for obj in objects:
206+
outstream.write(f"Examining: {obj}\n")
207+
recurse(obj, obj, set(), [])
208+
209+
# }}}
210+
211+
142212
# {{{ interactive shell
143213

144214
def get_shell_hist_filename():

0 commit comments

Comments
 (0)