-
-
Notifications
You must be signed in to change notification settings - Fork 73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
correct labels for dicts #50
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,6 +72,12 @@ | |
# Python 3.x compatibility | ||
iteritems = dict.items | ||
|
||
try: | ||
zip_longest = itertools.izip_longest | ||
except AttributeError: # pragma: PY3 | ||
# Python 3.x compatibility | ||
zip_longest = itertools.zip_longest | ||
|
||
graingert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
IS_INTERACTIVE = False | ||
try: # pragma: nocover | ||
import graphviz | ||
|
@@ -1000,7 +1006,10 @@ def _show_graph(objs, edge_func, swap_source_target, | |
continue | ||
if cull_func is not None and cull_func(target): | ||
continue | ||
neighbours = edge_func(target) | ||
edges = edge_func(target) | ||
graingert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
counts = collections.Counter(id(v) for v in edges) | ||
neighbours = list({id(v): v for v in edges}.values()) | ||
del edges | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So AFAIU There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need for a comment if we can add https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.unique_everseen
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather not; I like that objgraph is a single file library with no external dependencies I can copy over into any random location on my python path and start using immediately. |
||
ignore.add(id(neighbours)) | ||
n = 0 | ||
skipped = 0 | ||
|
@@ -1016,9 +1025,13 @@ def _show_graph(objs, edge_func, swap_source_target, | |
srcnode, tgtnode = target, source | ||
else: | ||
srcnode, tgtnode = source, target | ||
elabel = _edge_label(srcnode, tgtnode, shortnames) | ||
f.write(' %s -> %s%s;\n' % (_obj_node_id(srcnode), | ||
_obj_node_id(tgtnode), elabel)) | ||
for elabel, _ in zip_longest( | ||
graingert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_edge_labels(srcnode, tgtnode, shortnames), | ||
range(counts[id(source)]), | ||
fillvalue='', | ||
): | ||
graingert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
f.write(' %s -> %s%s;\n' % (_obj_node_id(srcnode), | ||
_obj_node_id(tgtnode), elabel)) | ||
if id(source) not in depth: | ||
depth[id(source)] = tdepth + 1 | ||
queue.append(source) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A couple of lines down from here there's a It probably doesn't matter, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah I figured it wasn't worth it, could wrap this whole thing in a |
||
|
@@ -1208,43 +1221,51 @@ def _gradient(start_color, end_color, depth, max_depth): | |
return h, s, v | ||
|
||
|
||
def _edge_label(source, target, shortnames=True): | ||
def _edge_labels(source, target, shortnames=True): | ||
if (_isinstance(target, dict) | ||
and target is getattr(source, '__dict__', None)): | ||
return ' [label="__dict__",weight=10]' | ||
return [' [label="__dict__",weight=10]'] | ||
if _isinstance(source, types.FrameType): | ||
if target is source.f_locals: | ||
return ' [label="f_locals",weight=10]' | ||
return [' [label="f_locals",weight=10]'] | ||
if target is source.f_globals: | ||
return ' [label="f_globals",weight=10]' | ||
return [' [label="f_globals",weight=10]'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if there are any frames where frame.f_locals is frame.f_globals and we'd have the same problem with two arrows having the same label? I wonder if it wouldn't be a good idea to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh I see yes that might be much better There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would require replacing all Not worth it. When I first suggested There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lol I already did it |
||
if _isinstance(source, types.MethodType): | ||
try: | ||
if target is source.__self__: | ||
return ' [label="__self__",weight=10]' | ||
return [' [label="__self__",weight=10]'] | ||
if target is source.__func__: | ||
return ' [label="__func__",weight=10]' | ||
return [' [label="__func__",weight=10]'] | ||
except AttributeError: # pragma: nocover | ||
# Python < 2.6 compatibility | ||
if target is source.im_self: | ||
return ' [label="im_self",weight=10]' | ||
return [' [label="im_self",weight=10]'] | ||
if target is source.im_func: | ||
return ' [label="im_func",weight=10]' | ||
return [' [label="im_func",weight=10]'] | ||
if _isinstance(source, types.FunctionType): | ||
for k in dir(source): | ||
if target is getattr(source, k): | ||
return ' [label="%s",weight=10]' % _quote(k) | ||
return [ | ||
' [label="%s",weight=10]' % _quote(k) | ||
for k in dir(source) | ||
if target is getattr(source, k) | ||
graingert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
if _isinstance(source, dict): | ||
for k, v in iteritems(source): | ||
if v is target: | ||
if _isinstance(k, basestring) and _is_identifier(k): | ||
return ' [label="%s",weight=2]' % _quote(k) | ||
else: | ||
if shortnames: | ||
tn = _short_typename(k) | ||
else: | ||
tn = _long_typename(k) | ||
return ' [label="%s"]' % _quote(tn + "\n" + _safe_repr(k)) | ||
return '' | ||
tn = _short_typename if shortnames else _long_typename | ||
return [ | ||
( | ||
' [label="%s",weight=2]' % _quote(k) | ||
if _isinstance(k, basestring) and _is_identifier(k) | ||
else ( | ||
' [label="%s"]' % _quote(tn(k) + "\n" + _safe_repr(k)) | ||
) | ||
) | ||
for k, v in iteritems(source) | ||
if v is target | ||
] | ||
return [] | ||
graingert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
def _edge_label(*args, **kwargs): | ||
return next(iter(_edge_labels(*args, **kwargs)), '') | ||
graingert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
_is_identifier = re.compile('[a-zA-Z_][a-zA-Z_0-9]*$').match | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -247,6 +247,40 @@ def test_cull_func(self): | |
label_a=label_a, | ||
label_b=label_b)) | ||
|
||
@skipIf( | ||
sys.version_info < (3, 6), | ||
"Python < 3.6 dicts have random iteration order", | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm tempted to do a quick hack with lines = output.readlines()
self.assertEqual(
''.join(lines[:5] + sorted(lines[5:-3]) + lines[-3:]),
textwrap.dedent(...)
) so this test can run on all Python versions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but the test looks so beautiful without it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The rest of the test will continue to look beautiful, and the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Python 3.5 reached End Of Life recently, so the point is moot.
graingert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def test_dict(self): | ||
d = dict.fromkeys("abcdefg") | ||
output = StringIO() | ||
objgraph.show_refs(d, output=output) | ||
self.assertEqual( | ||
output.getvalue(), | ||
textwrap.dedent( | ||
"""\ | ||
digraph ObjectGraph {{ | ||
node[shape=box, style=filled, fillcolor=white]; | ||
{d_id}[fontcolor=red]; | ||
{d_id}[label="dict\\n7 items"]; | ||
{d_id}[fillcolor="0,0,1"]; | ||
{d_id} -> {none_id} [label="a",weight=2]; | ||
{d_id} -> {none_id} [label="b",weight=2]; | ||
{d_id} -> {none_id} [label="c",weight=2]; | ||
{d_id} -> {none_id} [label="d",weight=2]; | ||
{d_id} -> {none_id} [label="e",weight=2]; | ||
{d_id} -> {none_id} [label="f",weight=2]; | ||
{d_id} -> {none_id} [label="g",weight=2]; | ||
{none_id}[label="NoneType\\nNone"]; | ||
{none_id}[fillcolor="0,0,0.766667"]; | ||
}} | ||
""" | ||
).format( | ||
d_id=objgraph._obj_node_id(d), | ||
none_id=objgraph._obj_node_id(None), | ||
), | ||
) | ||
|
||
@mock.patch('objgraph.IS_INTERACTIVE', True) | ||
@mock.patch('objgraph.graphviz', create=True) | ||
def test_ipython(self, mock_graphviz): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we use six.moves.zip_longest instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to introduce non-optional dependencies outside Python's stdlib yet.