-
-
Notifications
You must be signed in to change notification settings - Fork 72
correct labels for dicts #50
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
base: master
Are you sure you want to change the base?
Conversation
94428ef to
9e2b378
Compare
|
@mgedmin can I get a review |
mgedmin
left a comment
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
| 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 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.
| _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.
graingert
left a comment
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 <marius@gedmin.as>
| 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