From fa666bbc07ab7d2afa900f6c7034168858849a24 Mon Sep 17 00:00:00 2001 From: xybaby Date: Sun, 17 Dec 2017 20:54:07 +0800 Subject: [PATCH 1/5] MOD:add parameter 'filter' to some method and show lambda more friendly --- objgraph.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/objgraph.py b/objgraph.py index c313813..e13daa4 100755 --- a/objgraph.py +++ b/objgraph.py @@ -135,7 +135,7 @@ def count(typename, objects=None): del objects # clear cyclic references to frame -def typestats(objects=None, shortnames=True): +def typestats(objects=None, shortnames=True, filter=None): """Count the number of instances for each type tracked by the GC. Note that the GC does not track simple objects like int or str. @@ -168,6 +168,8 @@ def typestats(objects=None, shortnames=True): typename = _long_typename stats = {} for o in objects: + if filter and not filter(o): + continue n = typename(o) stats[n] = stats.get(n, 0) + 1 return stats @@ -175,7 +177,7 @@ def typestats(objects=None, shortnames=True): del objects # clear cyclic references to frame -def most_common_types(limit=10, objects=None, shortnames=True): +def most_common_types(limit=10, objects=None, shortnames=True, filter=None): """Count the names of types with the most instances. Returns a list of (type_name, count), sorted most-frequent-first. @@ -199,7 +201,7 @@ def most_common_types(limit=10, objects=None, shortnames=True): New parameter: ``shortnames``. """ - stats = sorted(typestats(objects, shortnames=shortnames).items(), + stats = sorted(typestats(objects, shortnames=shortnames, filter=filter).items(), key=operator.itemgetter(1), reverse=True) if limit: stats = stats[:limit] @@ -210,7 +212,8 @@ def show_most_common_types( limit=10, objects=None, shortnames=True, - file=None): + file=None, + filter=None): """Print the table of types of most common instances. The caveats documented in :func:`typestats` apply. @@ -238,13 +241,13 @@ def show_most_common_types( """ if file is None: file = sys.stdout - stats = most_common_types(limit, objects, shortnames=shortnames) + stats = most_common_types(limit, objects, shortnames=shortnames, filter=filter) width = max(len(name) for name, count in stats) for name, count in stats: file.write('%-*s %i\n' % (width, name, count)) -def show_growth(limit=10, peak_stats={}, shortnames=True, file=None): +def show_growth(limit=10, peak_stats={}, shortnames=True, file=None, filter=None): """Show the increase in peak object counts since last call. Limits the output to ``limit`` largest deltas. You may set ``limit`` to @@ -274,7 +277,7 @@ def show_growth(limit=10, peak_stats={}, shortnames=True, file=None): """ gc.collect() - stats = typestats(shortnames=shortnames) + stats = typestats(shortnames=shortnames, filter=filter) deltas = {} for name, count in iteritems(stats): old_count = peak_stats.get(name, 0) @@ -920,6 +923,8 @@ def _short_repr(obj): return name + ' (bound)' else: return name + if _isinstance(obj, types.LambdaType): + return 'lambda: %s:%s' % (os.path.basename(obj.func_code.co_filename), obj.func_code.co_firstlineno) if _isinstance(obj, types.FrameType): return '%s:%s' % (obj.f_code.co_filename, obj.f_lineno) if _isinstance(obj, (tuple, list, dict, set)): From e5f8848ea87d9fd20fb83e911dfa97f0d8d13d35 Mon Sep 17 00:00:00 2001 From: xybaby Date: Tue, 19 Dec 2017 10:26:52 +0800 Subject: [PATCH 2/5] MOD: add change log, unittest case, docstring --- CHANGES.rst | 9 +++++++++ objgraph.py | 34 +++++++++++++++++++++++++++++----- tests.py | 26 ++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 5e66cb1..36362ed 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,15 @@ Changes ======= +- New ``filter`` argument for :func:`typestats`, :func:`most_common_types`, + :func:`show_most_common_types`, :func:`show_growth`. + +- Show lambda function more human-readble with change to :func:`_short_repr` + + +3.1.3 (unreleased)(2017-12-19) +------------------ + .. currentmodule:: objgraph 3.1.3 (unreleased) diff --git a/objgraph.py b/objgraph.py index e13daa4..e31c08a 100755 --- a/objgraph.py +++ b/objgraph.py @@ -143,6 +143,8 @@ def typestats(objects=None, shortnames=True, filter=None): Note that classes with the same name but defined in different modules will be lumped together if ``shortnames`` is True. + Use ``filter`` (a predicate) to remove undesired objects. + Example: >>> typestats() @@ -158,6 +160,9 @@ def typestats(objects=None, shortnames=True, filter=None): .. versionchanged:: 1.8 New parameter: ``shortnames``. + .. versionchanged:: 3.1.3 + New parameter: ``filter``. + """ if objects is None: objects = gc.get_objects() @@ -185,6 +190,8 @@ def most_common_types(limit=10, objects=None, shortnames=True, filter=None): Limits the return value to at most ``limit`` items. You may set ``limit`` to None to avoid that. + Use ``filter`` (a predicate) to remove undesired objects. + The caveats documented in :func:`typestats` apply. Example: @@ -200,9 +207,13 @@ def most_common_types(limit=10, objects=None, shortnames=True, filter=None): .. versionchanged:: 1.8 New parameter: ``shortnames``. + .. versionchanged:: 3.1.3 + New parameter: ``filter``. + """ - stats = sorted(typestats(objects, shortnames=shortnames, filter=filter).items(), - key=operator.itemgetter(1), reverse=True) + stats = sorted( + typestats(objects, shortnames=shortnames, filter=filter).items(), + key=operator.itemgetter(1), reverse=True) if limit: stats = stats[:limit] return stats @@ -216,6 +227,8 @@ def show_most_common_types( filter=None): """Print the table of types of most common instances. + Use ``filter`` (a predicate) to remove undesired objects. + The caveats documented in :func:`typestats` apply. Example: @@ -238,16 +251,21 @@ def show_most_common_types( .. versionchanged:: 3.0 New parameter: ``file``. + .. versionchanged:: 3.1.3 + New parameter: ``filter``. + """ if file is None: file = sys.stdout - stats = most_common_types(limit, objects, shortnames=shortnames, filter=filter) + stats = most_common_types(limit, objects, shortnames=shortnames, + filter=filter) width = max(len(name) for name, count in stats) for name, count in stats: file.write('%-*s %i\n' % (width, name, count)) -def show_growth(limit=10, peak_stats={}, shortnames=True, file=None, filter=None): +def show_growth(limit=10, peak_stats={}, shortnames=True, file=None, + filter=None): """Show the increase in peak object counts since last call. Limits the output to ``limit`` largest deltas. You may set ``limit`` to @@ -257,6 +275,8 @@ def show_growth(limit=10, peak_stats={}, shortnames=True, file=None, filter=None seen peak object counts. Usually you don't need to pay attention to this argument. + Use ``filter`` (a predicate) to remove undesired objects from counting. + The caveats documented in :func:`typestats` apply. Example: @@ -275,6 +295,9 @@ def show_growth(limit=10, peak_stats={}, shortnames=True, file=None, filter=None .. versionchanged:: 2.1 New parameter: ``file``. + .. versionchanged:: 3.1.3 + New parameter: ``filter``. + """ gc.collect() stats = typestats(shortnames=shortnames, filter=filter) @@ -924,7 +947,8 @@ def _short_repr(obj): else: return name if _isinstance(obj, types.LambdaType): - return 'lambda: %s:%s' % (os.path.basename(obj.func_code.co_filename), obj.func_code.co_firstlineno) + return 'lambda: %s:%s' % (os.path.basename(obj.func_code.co_filename), + obj.func_code.co_firstlineno) if _isinstance(obj, types.FrameType): return '%s:%s' % (obj.f_code.co_filename, obj.f_lineno) if _isinstance(obj, (tuple, list, dict, set)): diff --git a/tests.py b/tests.py index 8c40f5c..145bd6c 100755 --- a/tests.py +++ b/tests.py @@ -280,6 +280,26 @@ def test_no_new_reference_cycles(self): objgraph.typestats() self.assertEqual(len(gc.get_referrers(x)), 1) +class TypestatsFilterArguTest(GarbageCollectedMixin, unittest.TestCase): + """Tests for the typestats function, especially for augument + ``fiter`` which is added at version 3.1.3""" + + def test_without_filter(self): + MyClass = type('MyClass', (), {'__module__': 'mymodule'}) # noqa + x, y = MyClass(), MyClass() + x.magic_attr = True + y.magic_attr = False + stats = objgraph.typestats(shortnames=False) + self.assertEqual(2, stats['mymodule.MyClass']) + + def test_with_filter(self): + MyClass = type('MyClass', (), {'__module__': 'mymodule'}) # noqa + x, y = MyClass(), MyClass() + x.magic_attr = True + y.magic_attr = False + stats = objgraph.typestats(shortnames=False, + filter=lambda e: hasattr(e, 'magic_attr') and e.magic_attr) + self.assertEqual(1, stats['mymodule.MyClass']) class ByTypeTest(GarbageCollectedMixin, unittest.TestCase): """Tests for the by_test function.""" @@ -399,6 +419,12 @@ def test_edge_label_long_type_names(self): objgraph._edge_label(d, 1, shortnames=False), ' [label="mymodule\.MyClass\\n Date: Tue, 19 Dec 2017 10:48:33 +0800 Subject: [PATCH 3/5] MOD: Compatibility with python3.x --- objgraph.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/objgraph.py b/objgraph.py index e31c08a..df5cc94 100755 --- a/objgraph.py +++ b/objgraph.py @@ -947,8 +947,8 @@ def _short_repr(obj): else: return name if _isinstance(obj, types.LambdaType): - return 'lambda: %s:%s' % (os.path.basename(obj.func_code.co_filename), - obj.func_code.co_firstlineno) + return 'lambda: %s:%s' % (os.path.basename(obj.__code__.co_filename), + obj.__code__.co_firstlineno) if _isinstance(obj, types.FrameType): return '%s:%s' % (obj.f_code.co_filename, obj.f_lineno) if _isinstance(obj, (tuple, list, dict, set)): From 77ec7609b6dfc15dfaabcafaa3464e8e4316ab6a Mon Sep 17 00:00:00 2001 From: xybaby Date: Tue, 19 Dec 2017 13:28:28 +0800 Subject: [PATCH 4/5] MOD: Compatibility with pep8 --- objgraph.py | 12 ++++++------ tests.py | 11 +++++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/objgraph.py b/objgraph.py index df5cc94..c7c95b1 100755 --- a/objgraph.py +++ b/objgraph.py @@ -161,7 +161,7 @@ def typestats(objects=None, shortnames=True, filter=None): New parameter: ``shortnames``. .. versionchanged:: 3.1.3 - New parameter: ``filter``. + New parameter: ``filter``. """ if objects is None: @@ -208,12 +208,12 @@ def most_common_types(limit=10, objects=None, shortnames=True, filter=None): New parameter: ``shortnames``. .. versionchanged:: 3.1.3 - New parameter: ``filter``. + New parameter: ``filter``. """ stats = sorted( - typestats(objects, shortnames=shortnames, filter=filter).items(), - key=operator.itemgetter(1), reverse=True) + typestats(objects, shortnames=shortnames, filter=filter).items(), + key=operator.itemgetter(1), reverse=True) if limit: stats = stats[:limit] return stats @@ -258,7 +258,7 @@ def show_most_common_types( if file is None: file = sys.stdout stats = most_common_types(limit, objects, shortnames=shortnames, - filter=filter) + filter=filter) width = max(len(name) for name, count in stats) for name, count in stats: file.write('%-*s %i\n' % (width, name, count)) @@ -948,7 +948,7 @@ def _short_repr(obj): return name if _isinstance(obj, types.LambdaType): return 'lambda: %s:%s' % (os.path.basename(obj.__code__.co_filename), - obj.__code__.co_firstlineno) + obj.__code__.co_firstlineno) if _isinstance(obj, types.FrameType): return '%s:%s' % (obj.f_code.co_filename, obj.f_lineno) if _isinstance(obj, (tuple, list, dict, set)): diff --git a/tests.py b/tests.py index 145bd6c..eff33c7 100755 --- a/tests.py +++ b/tests.py @@ -280,8 +280,9 @@ def test_no_new_reference_cycles(self): objgraph.typestats() self.assertEqual(len(gc.get_referrers(x)), 1) + class TypestatsFilterArguTest(GarbageCollectedMixin, unittest.TestCase): - """Tests for the typestats function, especially for augument + """Tests for the typestats function, especially for augument ``fiter`` which is added at version 3.1.3""" def test_without_filter(self): @@ -297,10 +298,12 @@ def test_with_filter(self): x, y = MyClass(), MyClass() x.magic_attr = True y.magic_attr = False - stats = objgraph.typestats(shortnames=False, + stats = objgraph.typestats( + shortnames=False, filter=lambda e: hasattr(e, 'magic_attr') and e.magic_attr) self.assertEqual(1, stats['mymodule.MyClass']) + class ByTypeTest(GarbageCollectedMixin, unittest.TestCase): """Tests for the by_test function.""" @@ -420,10 +423,10 @@ def test_edge_label_long_type_names(self): ' [label="mymodule\.MyClass\\n Date: Wed, 20 Dec 2017 09:48:39 +0800 Subject: [PATCH 5/5] MOD:fix fail on python3.4 windows,and more clearly docstring --- CHANGES.rst | 14 ++++---------- objgraph.py | 16 ++++++++++++---- tests.py | 4 ++-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 36362ed..d4c95f9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,21 +1,15 @@ Changes ======= -- New ``filter`` argument for :func:`typestats`, :func:`most_common_types`, - :func:`show_most_common_types`, :func:`show_growth`. - -- Show lambda function more human-readble with change to :func:`_short_repr` - - -3.1.3 (unreleased)(2017-12-19) ------------------- - .. currentmodule:: objgraph 3.1.3 (unreleased) ------------------ -- Nothing changed yet. +- New ``filter`` argument for :func:`typestats`, :func:`most_common_types`, + :func:`show_most_common_types`, :func:`show_growth`. + +- Show lambda function more human-readble with change to :func:`_short_repr` 3.1.2 (2017-11-27) diff --git a/objgraph.py b/objgraph.py index c7c95b1..7d062ef 100755 --- a/objgraph.py +++ b/objgraph.py @@ -143,7 +143,9 @@ def typestats(objects=None, shortnames=True, filter=None): Note that classes with the same name but defined in different modules will be lumped together if ``shortnames`` is True. - Use ``filter`` (a predicate) to remove undesired objects. + If ``filter` is specified, it should be a function taking one argument and + returning a boolean. Objects for which ``filter(obj)`` returns ``False`` + will be ignored. Example: @@ -190,7 +192,9 @@ def most_common_types(limit=10, objects=None, shortnames=True, filter=None): Limits the return value to at most ``limit`` items. You may set ``limit`` to None to avoid that. - Use ``filter`` (a predicate) to remove undesired objects. + If ``filter` is specified, it should be a function taking one argument and + returning a boolean. Objects for which ``filter(obj)`` returns ``False`` + will be ignored. The caveats documented in :func:`typestats` apply. @@ -227,7 +231,9 @@ def show_most_common_types( filter=None): """Print the table of types of most common instances. - Use ``filter`` (a predicate) to remove undesired objects. + If ``filter` is specified, it should be a function taking one argument and + returning a boolean. Objects for which ``filter(obj)`` returns ``False`` + will be ignored. The caveats documented in :func:`typestats` apply. @@ -275,7 +281,9 @@ def show_growth(limit=10, peak_stats={}, shortnames=True, file=None, seen peak object counts. Usually you don't need to pay attention to this argument. - Use ``filter`` (a predicate) to remove undesired objects from counting. + If ``filter` is specified, it should be a function taking one argument and + returning a boolean. Objects for which ``filter(obj)`` returns ``False`` + will be ignored. The caveats documented in :func:`typestats` apply. diff --git a/tests.py b/tests.py index eff33c7..149a17b 100755 --- a/tests.py +++ b/tests.py @@ -283,7 +283,7 @@ def test_no_new_reference_cycles(self): class TypestatsFilterArguTest(GarbageCollectedMixin, unittest.TestCase): """Tests for the typestats function, especially for augument - ``fiter`` which is added at version 3.1.3""" + ``filter`` which is added at version 3.1.3""" def test_without_filter(self): MyClass = type('MyClass', (), {'__module__': 'mymodule'}) # noqa @@ -300,7 +300,7 @@ def test_with_filter(self): y.magic_attr = False stats = objgraph.typestats( shortnames=False, - filter=lambda e: hasattr(e, 'magic_attr') and e.magic_attr) + filter=lambda e: isinstance(e, MyClass) and e.magic_attr) self.assertEqual(1, stats['mymodule.MyClass'])