Skip to content

Commit

Permalink
Merge pull request #184 from Theelx/main
Browse files Browse the repository at this point in the history
Fix #183
  • Loading branch information
Erotemic committed Nov 16, 2022
2 parents d5fbcfb + 70a5002 commit 623078e
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
Changes
=======
4.0.1
~~~~~
* FIX: Profiling classmethods works again. #183

4.0.0
~~~~~
Expand Down
2 changes: 1 addition & 1 deletion kernprof.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions line_profiler/_line_profiler.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 20 additions & 2 deletions line_profiler/line_profiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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.
Expand All @@ -49,14 +51,30 @@ 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)
else:
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.
Expand Down
16 changes: 16 additions & 0 deletions tests/test_line_profiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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

0 comments on commit 623078e

Please sign in to comment.