-
-
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?
Conversation
94428ef
to
9e2b378
Compare
@mgedmin can I get a review |
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.
Thank you, this is excellent!
I have only a couple of minor comments that I'll leave to your discretion. Also, would you like to add a changelog entry for this fix?
(Apologies for a late reply, I was on vacation and was pretending computers didn't exist.)
edges = edge_func(target) | ||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
So AFAIU neighbours
is edges
without duplicates, and counts
is the count of duplicates. This took me a while to understand, maybe a comment would be helpful?
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.
no need for a comment if we can add more_itertools
:
https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.unique_everseen
neighbours = more_itertools.unique_everseen(edges, key=id)
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'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.
objgraph.py
Outdated
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 comment
The 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 yield
here for easier handling of multiple return values.
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 tried using yield
but it breaks the control flow around return
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.
oh I see yes that might be much better
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.
It would require replacing all return
statements with yield
, and then adding return
with no arguments for short-circuiting the control flow (or rewriting the series of top-level 'if/if/if' into 'if/elif/elif').
Not worth it. When I first suggested yield
, I thought there would be more places with possible duplicate labels, but it looks like just this one place.
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.
lol I already did it
objgraph.py
Outdated
' [label="%s",weight=10]' % _quote(k) | ||
for k in dir(source) | ||
if target is getattr(source, k) | ||
] |
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.
👍
tests.py
Outdated
@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 comment
The 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
The rest of the test will continue to look beautiful, and the sorted(lines[5:-3])
will inspire awe in the unwary reader. ;)
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.
Python 3.5 reached End Of Life recently, so the point is moot.
fillvalue='', | ||
): | ||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of lines down from here there's a del neighbours
, and I think you might want to add del counts
in there.
It probably doesn't matter, counts
holds only ints, and I doubt anyone expects specific refcounts for those, and also they won't be causing problems with memory usage.
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.
yeah I figured it wasn't worth it, could wrap this whole thing in a def
so everything is automatically deleted
objgraph.py
Outdated
zip_longest = itertools.izip_longest | ||
except AttributeError: # pragma: PY3 | ||
# Python 3.x compatibility | ||
zip_longest = itertools.zip_longest |
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.
should detect cases where frame.f_locals is frame.f_globals
if _isinstance(k, basestring) and _is_identifier(k): | ||
yield ' [label="%s",weight=2]' % _quote(k) | ||
else: | ||
yield ' [label="%s"]' % _quote(tn(k) + "\n" + _safe_repr(k)) |
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.
+1 for adding max-line-length = 80
to setup.cfg's flake8 section to avoid this line too long error.
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.
3.6 support is dropped
Co-authored-by: Marius Gedminas <[email protected]>
elabel = _edge_label(srcnode, tgtnode, shortnames) | ||
f.write(' %s -> %s%s;\n' % (_obj_node_id(srcnode), | ||
_obj_node_id(tgtnode), elabel)) | ||
for elabel, _ in itertools.zip_longest( |
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.
This doesn't work on Python 2.7.
I'd have to stop and think whether I want to drop Python 2.7 compatibility at this point.
Fixes #49