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 @@ -4,6 +4,7 @@ Changes
3.5.0
~~~~~
* FIX: Fixes max of an empty sequence error #118
* Make IPython optional
* FIX: #100 Exception raise ZeroDivisionError

3.4.0
Expand Down
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ Releases of `line_profiler` can be installed using pip::

$ pip install line_profiler

Installation while ensuring a compatible IPython version can also be installed using pip::

$ pip install line_profiler[ipython]

Source releases and any binaries can be downloaded from the PyPI link.

http://pypi.python.org/pypi/line_profiler
Expand Down
5 changes: 3 additions & 2 deletions line_profiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
__submodules__ = [
'line_profiler',
'ipython_extension',
]

__autogen__ = """
Expand All @@ -13,10 +14,10 @@

from .line_profiler import __version__

from .line_profiler import (LineProfiler, LineProfilerMagics,
from .line_profiler import (LineProfiler,
load_ipython_extension, load_stats, main,
show_func, show_text,)

__all__ = ['LineProfiler', 'LineProfilerMagics', 'line_profiler',
__all__ = ['LineProfiler', 'line_profiler',
'load_ipython_extension', 'load_stats', 'main', 'show_func',
'show_text', '__version__']
143 changes: 143 additions & 0 deletions line_profiler/ipython_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
from io import StringIO

from IPython.core.magic import Magics, magics_class, line_magic
from IPython.core.page import page
from IPython.utils.ipstruct import Struct
from IPython.core.error import UsageError

from .line_profiler import LineProfiler


@magics_class
class LineProfilerMagics(Magics):
@line_magic
def lprun(self, parameter_s=""):
""" Execute a statement under the line-by-line profiler from the
line_profiler module.

Usage:
%lprun -f func1 -f func2 <statement>

The given statement (which doesn't require quote marks) is run via the
LineProfiler. Profiling is enabled for the functions specified by the -f
options. The statistics will be shown side-by-side with the code through the
pager once the statement has completed.

Options:

-f <function>: LineProfiler only profiles functions and methods it is told
to profile. This option tells the profiler about these functions. Multiple
-f options may be used. The argument may be any expression that gives
a Python function or method object. However, one must be careful to avoid
spaces that may confuse the option parser.

-m <module>: Get all the functions/methods in a module

One or more -f or -m options are required to get any useful results.

-D <filename>: dump the raw statistics out to a pickle file on disk. The
usual extension for this is ".lprof". These statistics may be viewed later
by running line_profiler.py as a script.

-T <filename>: dump the text-formatted statistics with the code side-by-side
out to a text file.

-r: return the LineProfiler object after it has completed profiling.

-s: strip out all entries from the print-out that have zeros.

-u: specify time unit for the print-out in seconds.
"""

# Escape quote markers.
opts_def = Struct(D=[""], T=[""], f=[], m=[], u=None)
parameter_s = parameter_s.replace('"', r"\"").replace("'", r"\'")
opts, arg_str = self.parse_options(parameter_s, "rsf:m:D:T:u:", list_all=True)
opts.merge(opts_def)

global_ns = self.shell.user_global_ns
local_ns = self.shell.user_ns

# Get the requested functions.
funcs = []
for name in opts.f:
try:
funcs.append(eval(name, global_ns, local_ns))
except Exception as e:
raise UsageError(
f"Could not find module {name}.\n{e.__class__.__name__}: {e}"
)

profile = LineProfiler(*funcs)

# Get the modules, too
for modname in opts.m:
try:
mod = __import__(modname, fromlist=[""])
profile.add_module(mod)
except Exception as e:
raise UsageError(
f"Could not find module {modname}.\n{e.__class__.__name__}: {e}"
)

if opts.u is not None:
try:
output_unit = float(opts.u[0])
except Exception:
raise TypeError("Timer unit setting must be a float.")
else:
output_unit = None

# Add the profiler to the builtins for @profile.
import builtins

if "profile" in builtins.__dict__:
had_profile = True
old_profile = builtins.__dict__["profile"]
else:
had_profile = False
old_profile = None
builtins.__dict__["profile"] = profile

try:
try:
profile.runctx(arg_str, global_ns, local_ns)
message = ""
except SystemExit:
message = """*** SystemExit exception caught in code being profiled."""
except KeyboardInterrupt:
message = (
"*** KeyboardInterrupt exception caught in code being " "profiled."
)
finally:
if had_profile:
builtins.__dict__["profile"] = old_profile

# Trap text output.
stdout_trap = StringIO()
profile.print_stats(
stdout_trap, output_unit=output_unit, stripzeros="s" in opts
)
output = stdout_trap.getvalue()
output = output.rstrip()

page(output)
print(message, end="")

dump_file = opts.D[0]
if dump_file:
profile.dump_stats(dump_file)
print(f"\n*** Profile stats pickled to file {dump_file!r}. {message}")

text_file = opts.T[0]
if text_file:
pfile = open(text_file, "w")
pfile.write(output)
pfile.close()
print(f"\n*** Profile printout saved to text file {text_file!r}. {message}")

return_value = None
if "r" in opts:
return_value = profile

return return_value
151 changes: 9 additions & 142 deletions line_profiler/line_profiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,8 @@
import tempfile
import os
import sys
from io import StringIO
from argparse import ArgumentError, ArgumentParser

from IPython.core.magic import (Magics, magics_class, line_magic)
from IPython.core.page import page
from IPython.utils.ipstruct import Struct
from IPython.core.error import UsageError

try:
from ._line_profiler import LineProfiler as CLineProfiler
except ImportError as ex:
Expand All @@ -25,6 +19,13 @@
__version__ = '3.5.0'


def load_ipython_extension(ip):
""" API for IPython to recognize this module as an IPython extension.
"""
from .ipython_extension import LineProfilerMagics

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can either be imported from this function, or it can be added with the other imports with a sys.modules guard for IPython.
In the second case there would be a circular import but it shouldn't be a problem.

ip.register_magics(LineProfilerMagics)


def is_coroutine(f):
return False

Expand Down Expand Up @@ -155,6 +156,8 @@ def add_module(self, mod):
return nfuncsadded


# This could be in the ipython_extension submodule,
# but it doesn't depend on the IPython module so it's easier to just let it stay here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, this is just handling the case where we are printing something in a notebook. It doesn't have to do with the plugin directly, so it's fine to stay here.

def is_ipython_kernel_cell(filename):
""" Return True if a filename corresponds to a Jupyter Notebook cell
"""
Expand Down Expand Up @@ -248,142 +251,6 @@ def show_text(stats, unit, output_unit=None, stream=None, stripzeros=False):
output_unit=output_unit, stream=stream,
stripzeros=stripzeros)


@magics_class
class LineProfilerMagics(Magics):

@line_magic
def lprun(self, parameter_s=''):
""" Execute a statement under the line-by-line profiler from the
line_profiler module.

Usage:
%lprun -f func1 -f func2 <statement>

The given statement (which doesn't require quote marks) is run via the
LineProfiler. Profiling is enabled for the functions specified by the -f
options. The statistics will be shown side-by-side with the code through the
pager once the statement has completed.

Options:

-f <function>: LineProfiler only profiles functions and methods it is told
to profile. This option tells the profiler about these functions. Multiple
-f options may be used. The argument may be any expression that gives
a Python function or method object. However, one must be careful to avoid
spaces that may confuse the option parser.

-m <module>: Get all the functions/methods in a module

One or more -f or -m options are required to get any useful results.

-D <filename>: dump the raw statistics out to a pickle file on disk. The
usual extension for this is ".lprof". These statistics may be viewed later
by running line_profiler.py as a script.

-T <filename>: dump the text-formatted statistics with the code side-by-side
out to a text file.

-r: return the LineProfiler object after it has completed profiling.

-s: strip out all entries from the print-out that have zeros.

-u: specify time unit for the print-out in seconds.
"""

# Escape quote markers.
opts_def = Struct(D=[''], T=[''], f=[], m=[], u=None)
parameter_s = parameter_s.replace('"', r'\"').replace("'", r"\'")
opts, arg_str = self.parse_options(parameter_s, 'rsf:m:D:T:u:', list_all=True)
opts.merge(opts_def)

global_ns = self.shell.user_global_ns
local_ns = self.shell.user_ns

# Get the requested functions.
funcs = []
for name in opts.f:
try:
funcs.append(eval(name, global_ns, local_ns))
except Exception as e:
raise UsageError(f'Could not find module {name}.\n{e.__class__.__name__}: {e}')

profile = LineProfiler(*funcs)

# Get the modules, too
for modname in opts.m:
try:
mod = __import__(modname, fromlist=[''])
profile.add_module(mod)
except Exception as e:
raise UsageError(f'Could not find module {modname}.\n{e.__class__.__name__}: {e}')

if opts.u is not None:
try:
output_unit = float(opts.u[0])
except Exception:
raise TypeError('Timer unit setting must be a float.')
else:
output_unit = None

# Add the profiler to the builtins for @profile.
import builtins

if 'profile' in builtins.__dict__:
had_profile = True
old_profile = builtins.__dict__['profile']
else:
had_profile = False
old_profile = None
builtins.__dict__['profile'] = profile

try:
try:
profile.runctx(arg_str, global_ns, local_ns)
message = ''
except SystemExit:
message = """*** SystemExit exception caught in code being profiled."""
except KeyboardInterrupt:
message = ('*** KeyboardInterrupt exception caught in code being '
'profiled.')
finally:
if had_profile:
builtins.__dict__['profile'] = old_profile

# Trap text output.
stdout_trap = StringIO()
profile.print_stats(stdout_trap, output_unit=output_unit, stripzeros='s' in opts)
output = stdout_trap.getvalue()
output = output.rstrip()

page(output)
print(message, end='')

dump_file = opts.D[0]
if dump_file:
profile.dump_stats(dump_file)
print(f'\n*** Profile stats pickled to file {dump_file!r}. {message}')

text_file = opts.T[0]
if text_file:
pfile = open(text_file, 'w')
pfile.write(output)
pfile.close()
print(f'\n*** Profile printout saved to text file {text_file!r}. {message}')

return_value = None
if 'r' in opts:
return_value = profile

return return_value


def load_ipython_extension(ip):
""" API for IPython to recognize this module as an IPython extension.
"""
ip.register_magics(LineProfilerMagics)


def load_stats(filename):
""" Utility function to load a pickled LineStats object from a given
filename.
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
-r requirements/runtime.txt
-r requirements/ipython.txt
-r requirements/build.txt
-r requirements/tests.txt
2 changes: 2 additions & 0 deletions requirements/ipython.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
IPython >=0.13 ; python_version >= '3.7'
IPython >=0.13, <7.17.0 ; python_version <= '3.6'
2 changes: 0 additions & 2 deletions requirements/runtime.txt
Original file line number Diff line number Diff line change
@@ -1,2 +0,0 @@
IPython >=0.13 ; python_version >= '3.7'
IPython >=0.13, <7.17.0 ; python_version <= '3.6'
2 changes: 2 additions & 0 deletions requirements/tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ pytest >= 4.6.11
pytest-cov >= 2.10.1
coverage[toml] >= 5.3
ubelt >= 1.0.1
IPython >=0.13 ; python_version >= '3.7'
IPython >=0.13, <7.17.0 ; python_version <= '3.6'
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ def native_mb_python_tag(plat_impl=None, version_info=None):
install_requires=parse_requirements('requirements/runtime.txt'),
extras_require={
'all': parse_requirements('requirements.txt'),
'ipython': parse_requirements('requirements/ipython.txt'),
'tests': parse_requirements('requirements/tests.txt'),
'build': parse_requirements('requirements/build.txt'),
},
Expand Down
Loading