Skip to content

Commit 3cef26c

Browse files
add get_object_graph
1 parent 75a567d commit 3cef26c

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

pytools/debug.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@
2626
.. autofunction:: make_unique_filesystem_object
2727
.. autofunction:: open_unique_debug_file
2828
.. autofunction:: refdebug
29+
.. autofunction:: get_object_graph
2930
.. autofunction:: get_object_cycles
3031
.. autofunction:: estimate_memory_usage
3132
3233
"""
3334

3435
import sys
35-
from typing import Collection, List, Set
36+
from typing import Collection, Dict, List, Set, Tuple, Union
3637

3738
from pytools import memoize
3839

@@ -175,6 +176,48 @@ def is_excluded(o):
175176

176177
# {{{ Find circular references
177178

179+
from pytools.graph import GraphT
180+
181+
182+
def get_object_graph(objects: Collection[object],
183+
outside_objects: bool = False) -> GraphT:
184+
185+
"""Create a graph out of *objects*, with graph edges representing
186+
references between objects.
187+
188+
:arg objects: The objects to build the graph.
189+
:arg outside_objects: Include objects not in *objects* in the graph when
190+
``True``.
191+
192+
:returns: A :class:`~pytools.graph.GraphT` with the references between objects.
193+
"""
194+
195+
import gc
196+
res: Dict[object, List[object]] = {}
197+
198+
def hash_unhashable(obj: object) -> Union[object, Tuple[int, str]]:
199+
try:
200+
hash(obj)
201+
except TypeError:
202+
return (id(obj), str(obj))
203+
else:
204+
return obj
205+
206+
# Collect objects first to differentiate to outside objects later
207+
for obj in objects:
208+
res[hash_unhashable(obj)] = []
209+
210+
for obj in objects:
211+
refs = gc.get_referents(obj)
212+
obj = hash_unhashable(obj)
213+
for r in refs:
214+
r = hash_unhashable(r)
215+
if r in res or outside_objects:
216+
res.setdefault(r, []).append(obj)
217+
218+
return res
219+
220+
178221
# Based on https://code.activestate.com/recipes/523004-find-cyclical-references/
179222

180223
def get_object_cycles(objects: Collection[object]) -> List[List[object]]:

test/test_debug.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,36 @@ def test_get_object_cycles():
4747
b.append(a)
4848

4949
assert len(get_object_cycles([a, b])) == 2
50+
51+
52+
def test_get_object_graph():
53+
from pytools.debug import get_object_graph
54+
55+
assert get_object_graph([]) == {}
56+
57+
a = (1,)
58+
b = (2,)
59+
c = (a, b)
60+
assert get_object_graph([a]) == {(1,): []}
61+
assert get_object_graph([a, b]) == {(1,): [], (2,): []}
62+
assert get_object_graph([a, b, c]) == {((1,), (2,)): [], # c: []
63+
(1,): [((1,), (2,))], # a: [c]
64+
(2,): [((1,), (2,))]} # b: [c]
65+
66+
a = {}
67+
b = {"a": a}
68+
a["b"] = b
69+
70+
assert get_object_graph([a, b]) == {
71+
(id(a), "{'b': {'a': {...}}}"): [(id(b), "{'a': {'b': {...}}}")],
72+
(id(b), "{'a': {'b': {...}}}"): [(id(a), "{'b': {'a': {...}}}")]}
73+
74+
b = [42, 4]
75+
a = [1, 2, 3, 4, 5, b]
76+
b.append(a)
77+
78+
assert get_object_graph([a, b]) == {
79+
(id(a), '[1, 2, 3, 4, 5, [42, 4, [...]]]'):
80+
[(id(b), '[42, 4, [1, 2, 3, 4, 5, [...]]]')],
81+
(id(b), '[42, 4, [1, 2, 3, 4, 5, [...]]]'):
82+
[(id(a), '[1, 2, 3, 4, 5, [42, 4, [...]]]')]}

0 commit comments

Comments
 (0)