Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Changes
* FIX: Fixed issue when calling ``kernprof`` with neither the ``-l`` nor ``-b`` flag; also refactored common methods to ``LineProfiler`` and ``ContextualProfile``
* FIX: Fixed auto-profiling of async function definitions #330
* ENH: Added CLI argument ``-m`` to ``kernprof`` for running a library module as a script; also made it possible for profiling targets to be supplied across multiple ``-p`` flags
* FIX: Fixed explicit profiling of class methods; added handling for profiling static, bound, and partial methods, ``functools.partial`` objects, (cached) properties, and async generator functions

4.2.0
~~~~~
Expand Down
72 changes: 65 additions & 7 deletions line_profiler/line_profiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@
'The line_profiler._line_profiler c-extension is not importable. '
f'Has it been compiled? Underlying error is ex={ex!r}'
)
from .profiler_mixin import ByCountProfilerMixin
from .profiler_mixin import (ByCountProfilerMixin,
is_property, is_cached_property,
is_boundmethod, is_classmethod, is_staticmethod,
is_partial, is_partialmethod)


# NOTE: This needs to be in sync with ../kernprof.py and __init__.py
__version__ = '4.3.0'

is_function = inspect.isfunction


def load_ipython_extension(ip):
""" API for IPython to recognize this module as an IPython extension.
Expand All @@ -32,6 +38,34 @@ def load_ipython_extension(ip):
ip.register_magics(LineProfilerMagics)


def _get_underlying_functions(func):
"""
Get the underlying function objects of a callable or an adjacent
object.

Returns:
funcs (list[Callable])
"""
if any(check(func)
for check in (is_boundmethod, is_classmethod, is_staticmethod)):
return _get_underlying_functions(func.__func__)
if any(check(func)
for check in (is_partial, is_partialmethod, is_cached_property)):
return _get_underlying_functions(func.func)
if is_property(func):
result = []
for impl in func.fget, func.fset, func.fdel:
if impl is not None:
result.extend(_get_underlying_functions(impl))
return result
if not callable(func):
raise TypeError(f'func = {func!r}: '
f'cannot get functions from {type(func)} objects')
if is_function(func):
return [func]
return [type(func).__call__]


class LineProfiler(CLineProfiler, ByCountProfilerMixin):
"""
A profiler that records the execution times of individual lines.
Expand All @@ -51,12 +85,36 @@ class LineProfiler(CLineProfiler, ByCountProfilerMixin):
"""

def __call__(self, func):
""" Decorate a function to start the profiler on function entry and stop
it on function exit.
"""
self.add_function(func)
Decorate a function, method, property, partial object etc. to
start the profiler on function entry and stop it on function
exit.
"""
# Note: if `func` is a `types.FunctionType` which is already
# decorated by the profiler, the same object is returned;
# otherwise, wrapper objects are always returned.
self.add_callable(func)
return self.wrap_callable(func)

def add_callable(self, func):
"""
Register a function, method, property, partial object, etc. with
the underlying Cython profiler.

Returns:
1 if any function is added to the profiler, 0 otherwise
"""
guard = self._already_wrapped

nadded = 0
for impl in _get_underlying_functions(func):
if guard(impl):
continue
self.add_function(impl)
nadded += 1

return 1 if nadded else 0

def dump_stats(self, filename):
""" Dump a representation of the data to a file as a pickled LineStats
object from `get_stats()`.
Expand All @@ -77,16 +135,16 @@ def print_stats(self, stream=None, output_unit=None, stripzeros=False,
def add_module(self, mod):
""" Add all the functions in a module and its classes.
"""
from inspect import isclass, isfunction
from inspect import isclass

nfuncsadded = 0
for item in mod.__dict__.values():
if isclass(item):
for k, v in item.__dict__.items():
if isfunction(v):
if is_function(v):
self.add_function(v)
nfuncsadded += 1
elif isfunction(item):
elif is_function(item):
self.add_function(item)
nfuncsadded += 1

Expand Down
3 changes: 3 additions & 0 deletions line_profiler/line_profiler.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ def load_ipython_extension(ip) -> None:

class LineProfiler(CLineProfiler, ByCountProfilerMixin):

def add_callable(self, func) -> None:
...

def dump_stats(self, filename) -> None:
...

Expand Down
Loading