From cf9d6ee08718a6717988d3c00f859e4e519be69c Mon Sep 17 00:00:00 2001 From: Theelx <43764914+Theelx@users.noreply.github.com> Date: Tue, 15 Nov 2022 15:36:51 -0500 Subject: [PATCH 1/2] Fix profiling classmethods --- CHANGELOG.rst | 3 +++ line_profiler/_line_profiler.pyx | 2 ++ line_profiler/line_profiler.py | 20 +++++++++++++++++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8e057151..1454b704 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,8 @@ Changes ======= +4.0.1 +~~~~~ +* FIX: Profiling classmethods works again. #183 4.0.0 ~~~~~ diff --git a/line_profiler/_line_profiler.pyx b/line_profiler/_line_profiler.pyx index a1b0fbc7..637cff15 100644 --- a/line_profiler/_line_profiler.pyx +++ b/line_profiler/_line_profiler.pyx @@ -204,6 +204,8 @@ cdef class LineProfiler: "instead." % (func.__name__,) ) try: + if isinstance(func, classmethod): + func = func.__func__ code = func.__code__ except AttributeError: import warnings diff --git a/line_profiler/line_profiler.py b/line_profiler/line_profiler.py index 5417406a..b77aceea 100755 --- a/line_profiler/line_profiler.py +++ b/line_profiler/line_profiler.py @@ -39,6 +39,8 @@ def is_generator(f): isgen = (f.__code__.co_flags & CO_GENERATOR) != 0 return isgen +def is_classmethod(f): + return isinstance(f, classmethod) class LineProfiler(CLineProfiler): """ A profiler that records the execution times of individual lines. @@ -49,7 +51,9 @@ def __call__(self, func): it on function exit. """ self.add_function(func) - if is_coroutine(func): + if is_classmethod(func): + wrapper = self.wrap_classmethod(func) + elif is_coroutine(func): wrapper = self.wrap_coroutine(func) elif is_generator(func): wrapper = self.wrap_generator(func) @@ -57,6 +61,20 @@ def __call__(self, func): wrapper = self.wrap_function(func) return wrapper + def wrap_classmethod(self, func): + """ + Wrap a classmethod to profile it. + """ + @functools.wraps(func) + def wrapper(*args, **kwds): + self.enable_by_count() + try: + result = func.__func__(func.__class__, *args, **kwds) + finally: + self.disable_by_count() + return result + return wrapper + def wrap_coroutine(self, func): """ Wrap a Python 3.5 coroutine to profile it. From 70a500247bb68f158bad3f316ad453c39aad800d Mon Sep 17 00:00:00 2001 From: Theelx <43764914+Theelx@users.noreply.github.com> Date: Tue, 15 Nov 2022 16:40:18 -0500 Subject: [PATCH 2/2] Add test and adjust version --- kernprof.py | 2 +- line_profiler/line_profiler.py | 2 +- tests/test_line_profiler.py | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/kernprof.py b/kernprof.py index 0d89a5f8..62e939a7 100755 --- a/kernprof.py +++ b/kernprof.py @@ -13,7 +13,7 @@ # NOTE: This version needs to be manually maintained with the line_profiler # __version__ for now. -__version__ = '4.0.0' +__version__ = '4.0.1' # Guard the import of cProfile such that 3.x people # without lsprof can still use this script. diff --git a/line_profiler/line_profiler.py b/line_profiler/line_profiler.py index b77aceea..10369a1f 100755 --- a/line_profiler/line_profiler.py +++ b/line_profiler/line_profiler.py @@ -16,7 +16,7 @@ f'Has it been compiled? Underlying error is ex={ex!r}' ) -__version__ = '4.0.0' +__version__ = '4.0.1' def load_ipython_extension(ip): diff --git a/tests/test_line_profiler.py b/tests/test_line_profiler.py index f9a33b39..3d5dae55 100644 --- a/tests/test_line_profiler.py +++ b/tests/test_line_profiler.py @@ -12,6 +12,12 @@ def g(x): y = yield x + 10 yield y + 20 +class C: + @classmethod + def c(self, value): + print(value) + return 0 + def test_init(): lp = LineProfiler() @@ -86,3 +92,13 @@ def test_gen_decorator(): with pytest.raises(StopIteration): next(i) assert profile.enable_count == 0 + +def test_classmethod_decorator(): + profile = LineProfiler() + c_wrapped = profile(C.c) + assert c_wrapped.__name__ == 'c' + assert profile.enable_count == 0 + val = c_wrapped('test') + assert profile.enable_count == 0 + assert val == C.c('test') + assert profile.enable_count == 0