Skip to content

Commit 0bf74b2

Browse files
authored
Merge pull request #33 from xybaby/master
Add parameter 'filter' to some functions and a better representation for lambdas
2 parents 7577242 + 3647595 commit 0bf74b2

3 files changed

Lines changed: 78 additions & 9 deletions

File tree

CHANGES.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ Changes
66
3.1.3 (unreleased)
77
------------------
88

9-
- Nothing changed yet.
9+
- New ``filter`` argument for :func:`typestats`, :func:`most_common_types`,
10+
:func:`show_most_common_types`, :func:`show_growth`.
11+
12+
- Show lambda function more human-readble with change to :func:`_short_repr`
1013

1114

1215
3.1.2 (2017-11-27)

objgraph.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,18 @@ def count(typename, objects=None):
135135
del objects # clear cyclic references to frame
136136

137137

138-
def typestats(objects=None, shortnames=True):
138+
def typestats(objects=None, shortnames=True, filter=None):
139139
"""Count the number of instances for each type tracked by the GC.
140140
141141
Note that the GC does not track simple objects like int or str.
142142
143143
Note that classes with the same name but defined in different modules
144144
will be lumped together if ``shortnames`` is True.
145145
146+
If ``filter` is specified, it should be a function taking one argument and
147+
returning a boolean. Objects for which ``filter(obj)`` returns ``False``
148+
will be ignored.
149+
146150
Example:
147151
148152
>>> typestats()
@@ -158,6 +162,9 @@ def typestats(objects=None, shortnames=True):
158162
.. versionchanged:: 1.8
159163
New parameter: ``shortnames``.
160164
165+
.. versionchanged:: 3.1.3
166+
New parameter: ``filter``.
167+
161168
"""
162169
if objects is None:
163170
objects = gc.get_objects()
@@ -168,21 +175,27 @@ def typestats(objects=None, shortnames=True):
168175
typename = _long_typename
169176
stats = {}
170177
for o in objects:
178+
if filter and not filter(o):
179+
continue
171180
n = typename(o)
172181
stats[n] = stats.get(n, 0) + 1
173182
return stats
174183
finally:
175184
del objects # clear cyclic references to frame
176185

177186

178-
def most_common_types(limit=10, objects=None, shortnames=True):
187+
def most_common_types(limit=10, objects=None, shortnames=True, filter=None):
179188
"""Count the names of types with the most instances.
180189
181190
Returns a list of (type_name, count), sorted most-frequent-first.
182191
183192
Limits the return value to at most ``limit`` items. You may set ``limit``
184193
to None to avoid that.
185194
195+
If ``filter` is specified, it should be a function taking one argument and
196+
returning a boolean. Objects for which ``filter(obj)`` returns ``False``
197+
will be ignored.
198+
186199
The caveats documented in :func:`typestats` apply.
187200
188201
Example:
@@ -198,9 +211,13 @@ def most_common_types(limit=10, objects=None, shortnames=True):
198211
.. versionchanged:: 1.8
199212
New parameter: ``shortnames``.
200213
214+
.. versionchanged:: 3.1.3
215+
New parameter: ``filter``.
216+
201217
"""
202-
stats = sorted(typestats(objects, shortnames=shortnames).items(),
203-
key=operator.itemgetter(1), reverse=True)
218+
stats = sorted(
219+
typestats(objects, shortnames=shortnames, filter=filter).items(),
220+
key=operator.itemgetter(1), reverse=True)
204221
if limit:
205222
stats = stats[:limit]
206223
return stats
@@ -210,9 +227,14 @@ def show_most_common_types(
210227
limit=10,
211228
objects=None,
212229
shortnames=True,
213-
file=None):
230+
file=None,
231+
filter=None):
214232
"""Print the table of types of most common instances.
215233
234+
If ``filter` is specified, it should be a function taking one argument and
235+
returning a boolean. Objects for which ``filter(obj)`` returns ``False``
236+
will be ignored.
237+
216238
The caveats documented in :func:`typestats` apply.
217239
218240
Example:
@@ -235,16 +257,21 @@ def show_most_common_types(
235257
.. versionchanged:: 3.0
236258
New parameter: ``file``.
237259
260+
.. versionchanged:: 3.1.3
261+
New parameter: ``filter``.
262+
238263
"""
239264
if file is None:
240265
file = sys.stdout
241-
stats = most_common_types(limit, objects, shortnames=shortnames)
266+
stats = most_common_types(limit, objects, shortnames=shortnames,
267+
filter=filter)
242268
width = max(len(name) for name, count in stats)
243269
for name, count in stats:
244270
file.write('%-*s %i\n' % (width, name, count))
245271

246272

247-
def show_growth(limit=10, peak_stats={}, shortnames=True, file=None):
273+
def show_growth(limit=10, peak_stats={}, shortnames=True, file=None,
274+
filter=None):
248275
"""Show the increase in peak object counts since last call.
249276
250277
Limits the output to ``limit`` largest deltas. You may set ``limit`` to
@@ -254,6 +281,10 @@ def show_growth(limit=10, peak_stats={}, shortnames=True, file=None):
254281
seen peak object counts. Usually you don't need to pay attention to this
255282
argument.
256283
284+
If ``filter` is specified, it should be a function taking one argument and
285+
returning a boolean. Objects for which ``filter(obj)`` returns ``False``
286+
will be ignored.
287+
257288
The caveats documented in :func:`typestats` apply.
258289
259290
Example:
@@ -272,9 +303,12 @@ def show_growth(limit=10, peak_stats={}, shortnames=True, file=None):
272303
.. versionchanged:: 2.1
273304
New parameter: ``file``.
274305
306+
.. versionchanged:: 3.1.3
307+
New parameter: ``filter``.
308+
275309
"""
276310
gc.collect()
277-
stats = typestats(shortnames=shortnames)
311+
stats = typestats(shortnames=shortnames, filter=filter)
278312
deltas = {}
279313
for name, count in iteritems(stats):
280314
old_count = peak_stats.get(name, 0)
@@ -920,6 +954,9 @@ def _short_repr(obj):
920954
return name + ' (bound)'
921955
else:
922956
return name
957+
if _isinstance(obj, types.LambdaType):
958+
return 'lambda: %s:%s' % (os.path.basename(obj.__code__.co_filename),
959+
obj.__code__.co_firstlineno)
923960
if _isinstance(obj, types.FrameType):
924961
return '%s:%s' % (obj.f_code.co_filename, obj.f_lineno)
925962
if _isinstance(obj, (tuple, list, dict, set)):

tests.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,29 @@ def test_no_new_reference_cycles(self):
281281
self.assertEqual(len(gc.get_referrers(x)), 1)
282282

283283

284+
class TypestatsFilterArguTest(GarbageCollectedMixin, unittest.TestCase):
285+
"""Tests for the typestats function, especially for augument
286+
``filter`` which is added at version 3.1.3"""
287+
288+
def test_without_filter(self):
289+
MyClass = type('MyClass', (), {'__module__': 'mymodule'}) # noqa
290+
x, y = MyClass(), MyClass()
291+
x.magic_attr = True
292+
y.magic_attr = False
293+
stats = objgraph.typestats(shortnames=False)
294+
self.assertEqual(2, stats['mymodule.MyClass'])
295+
296+
def test_with_filter(self):
297+
MyClass = type('MyClass', (), {'__module__': 'mymodule'}) # noqa
298+
x, y = MyClass(), MyClass()
299+
x.magic_attr = True
300+
y.magic_attr = False
301+
stats = objgraph.typestats(
302+
shortnames=False,
303+
filter=lambda e: isinstance(e, MyClass) and e.magic_attr)
304+
self.assertEqual(1, stats['mymodule.MyClass'])
305+
306+
284307
class ByTypeTest(GarbageCollectedMixin, unittest.TestCase):
285308
"""Tests for the by_test function."""
286309

@@ -399,6 +422,12 @@ def test_edge_label_long_type_names(self):
399422
objgraph._edge_label(d, 1, shortnames=False),
400423
' [label="mymodule\.MyClass\\n<mymodule\.MyClass object at .*"]')
401424

425+
def test_short_repr_lambda(self):
426+
f = lambda x: x # noqa
427+
lambda_lineno = sys._getframe().f_lineno - 1
428+
self.assertEqual('lambda: tests.py:%s' % lambda_lineno,
429+
objgraph._short_repr(f))
430+
402431

403432
class StubSubprocess(object):
404433

0 commit comments

Comments
 (0)