Skip to content

Commit 97ec0c0

Browse files
committed
Refactoring: default to eager imports
CHANGERLOG.rst - Reworded previous entry (pyutils#338) - Reworded entry kernprof.py __doc__ Updated _normalize_profiling_targets.__doc__ _restore_list.__doc__ pre_parse_single_arg_directive.__doc__ Reformatted to be more `sphinx`-friendly main() - Removed the `-e`/`--eager-preimports` flag - Made eager pre-imports the default for `--prof-mod` - Added new flag `--no-preimports` for restoring the old behavior
1 parent c215b71 commit 97ec0c0

File tree

2 files changed

+46
-55
lines changed

2 files changed

+46
-55
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ Changes
1111
* 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
1212
* FIX: Fixed namespace bug when running ``kernprof -m`` on certain modules (e.g. ``calendar`` on Python 3.12+).
1313
* FIX: Fixed ``@contextlib.contextmanager`` bug where the cleanup code (e.g. restoration of ``sys`` attributes) is not run if exceptions occurred inside the context
14-
* ENH: Added CLI arguments ``-c`` to ``kernprof`` for (auto-)profiling module/package/inline-script execution instead of that of script files; passing ``'-'`` as the script-file name now also reads from and profiles ``stdin``
15-
* ENH: Added CLI argument ``-e``/``--eager-preimports`` to profile target entities even when they aren't directly imported in the run script/module; made on-import profiling more aggressive so that it doesn't miss entities like class methods and properties
14+
* ENH: Added CLI arguments ``-c`` to ``kernprof`` for (auto-)profiling inline-script execution instead of that of script files; passing ``'-'`` as the script-file name now also reads from and profiles ``stdin``
15+
* ENH: ``kernprof --prof-mod`` target entities are now imported and profiled regardless of whether they are directly imported in the run script/module/code (old behavior recoed by passing ``--no-preimports``); made on-import profiling more aggressive so that it doesn't miss entities like class methods and properties
1616

1717
4.2.0
1818
~~~~~

kernprof.py

Lines changed: 44 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def main():
4141
4242
NOTE:
4343
44-
New in 4.3.0: more code execution options are added:
44+
New in 4.3.0: More code execution options are added:
4545
4646
* ``kernprof <options> -m some.module <args to module>`` parallels
4747
``python -m`` and runs the provided module as ``__main__``.
@@ -88,15 +88,22 @@ def main():
8888
-i, --output-interval [OUTPUT_INTERVAL]
8989
Enables outputting of cumulative profiling results to file every n seconds. Uses the threading module. Minimum value is 1 (second). Defaults to
9090
disabled.
91-
-p, --prof-mod PROF_MOD
92-
List of modules, functions and/or classes to profile specified by their name or path, if they are imported in the profiled script/module. These
93-
profiling targets can be supplied both as comma-separated items, or separately with multiple copies of this flag. Adding the current script/module
94-
profiles the entirety of it. Only works with line_profiler -l, --line-by-line.
95-
-e, --eager-preimports
96-
List of modules, functions, and/or classes, to be imported and marked for profiling before running the script/module, regardless of whether they are
97-
directly imported in the script/module. Follows the same semantics as `--prof-mod`. If supplied without an argument, indicates that all `--prof-mod`
98-
targets are to be so profiled. Only works with line_profiler -l, --line-by-line.
91+
-p, --prof-mod {path/to/script | object.dotted.path}[,...]
92+
List of modules, functions and/or classes to profile specified by their name or path. These profiling targets can be supplied both as comma-separated
93+
items, or separately with multiple copies of this flag. Adding the current script/module profiles the entirety of it. Only works with line_profiler -l,
94+
--no-preimports Instead of eagerly importing all profiling targets specified via -p and profiling them, only profile those that are directly imported in the profiled
95+
code. Only works with line_profiler -l, --line-by-line.
9996
--prof-imports If specified, modules specified to `--prof-mod` will also autoprofile modules that they import. Only works with line_profiler -l, --line-by-line
97+
98+
NOTE:
99+
100+
New in 4.3.0: For more intuitive profiling behavior, profiling
101+
targets in ``--prof-mod`` (except the profiled script/code) are now
102+
eagerly pre-imported to be profiled
103+
(see :py:mod:`line_profiler.autoprofile.eager_preimports`),
104+
regardless of whether those imports directly occur in the profiled
105+
script/module/code.
106+
To restore the old behavior, pass the ``--no-preimports`` flag.
100107
"""
101108
import builtins
102109
import functools
@@ -249,12 +256,13 @@ def _python_command():
249256

250257
def _normalize_profiling_targets(targets):
251258
"""
252-
Normalize the parsed `--prof-mod` and `--eager-preimports` by:
253-
- Normalizing file paths with `find_script()`, and subsequently
254-
to absolute paths.
255-
- Splitting non-file paths at commas into (presumably) file paths
259+
Normalize the parsed ``--prof-mod`` by:
260+
261+
* Normalizing file paths with :py:func:`find_script()`, and
262+
subsequently to absolute paths.
263+
* Splitting non-file paths at commas into (presumably) file paths
256264
and/or dotted paths.
257-
- Removing duplicates.
265+
* Removing duplicates.
258266
"""
259267
def find(path):
260268
try:
@@ -277,8 +285,8 @@ def find(path):
277285

278286
class _restore_list:
279287
"""
280-
Restore a list like `sys.path` after running code which potentially
281-
modifies it.
288+
Restore a list like ``sys.path`` after running code which
289+
potentially modifies it.
282290
283291
Example
284292
-------
@@ -320,8 +328,8 @@ def wrapper(*args, **kwargs):
320328

321329
def pre_parse_single_arg_directive(args, flag, sep='--'):
322330
"""
323-
Pre-parse high-priority single-argument directives like `-m module`
324-
to emulate the behavior of `python [...]`.
331+
Pre-parse high-priority single-argument directives like
332+
``-m module`` to emulate the behavior of ``python [...]``.
325333
326334
Examples
327335
--------
@@ -455,28 +463,19 @@ def positive_float(value):
455463
metavar=("{path/to/script | object.dotted.path}"
456464
"[,...]"),
457465
help="List of modules, functions and/or classes "
458-
"to profile specified by their name or path, "
459-
"if they are imported in the profiled "
460-
"script/module. "
466+
"to profile specified by their name or path. "
461467
"These profiling targets can be supplied both as "
462468
"comma-separated items, or separately with "
463469
"multiple copies of this flag. "
464470
"Adding the current script/module profiles the "
465471
"entirety of it. "
466472
"Only works with line_profiler -l, --line-by-line.")
467-
parser.add_argument('-e', '--eager-preimports',
468-
action='append',
469-
const=True,
470-
metavar=("{path/to/script | object.dotted.path}"
471-
"[,...]"),
472-
nargs='?',
473-
help="List of modules, functions, and/or classes, "
474-
"to be imported and marked for profiling before "
475-
"running the script/module, regardless of whether "
476-
"they are directly imported in the script/module. "
477-
"Follows the same semantics as `--prof-mod`. "
478-
"If supplied without an argument, indicates that "
479-
"all `--prof-mod` targets are to be so profiled. "
473+
parser.add_argument('--no-preimports',
474+
action='store_true',
475+
help="Instead of eagerly importing all profiling "
476+
"targets specified via -p and profiling them, "
477+
"only profile those that are directly imported in "
478+
"the profiled code. "
480479
"Only works with line_profiler -l, --line-by-line.")
481480
parser.add_argument('--prof-imports', action='store_true',
482481
help="If specified, modules specified to `--prof-mod` will also autoprofile modules that they import. "
@@ -619,20 +618,14 @@ def _main(options, module=False):
619618
# commas), so check against existing filenames before splitting
620619
# them
621620
options.prof_mod = _normalize_profiling_targets(options.prof_mod)
622-
if options.eager_preimports:
623-
if options.eager_preimports == [True]:
624-
# Eager-import all of `--prof-mod`
625-
options.eager_preimports = list(options.prof_mod or [])
626-
else: # Only eager-import the specified targets
627-
options.eager_preimports = _normalize_profiling_targets([
628-
target for target in options.eager_preimports
629-
if target not in (True,)])
630-
if options.line_by_line and options.eager_preimports:
631-
# We assume most items in `.eager_preimports` to be import-able
632-
# without significant side effects, but the same cannot be said
633-
# if it contains the script file to be run. E.g. the script may
634-
# not even have a `if __name__ == '__main__': ...` guard. So
635-
# don't eager-import it.
621+
if not options.prof_mod:
622+
options.no_preimports = True
623+
if options.line_by_line and not options.no_preimports:
624+
# We assume most items in `.prof_mod` to be import-able without
625+
# significant side effects, but the same cannot be said if it
626+
# contains the script file to be run. E.g. the script may not
627+
# even have a `if __name__ == '__main__': ...` guard. So don't
628+
# eager-import it.
636629
from line_profiler.autoprofile.eager_preimports import (
637630
is_dotted_path, propose_names, write_eager_import_module)
638631
from line_profiler.autoprofile.util_static import modpath_to_modname
@@ -641,7 +634,7 @@ def _main(options, module=False):
641634

642635
filtered_targets = []
643636
invalid_targets = []
644-
for target in options.eager_preimports:
637+
for target in options.prof_mod:
645638
if is_dotted_path(target):
646639
filtered_targets.append(target)
647640
continue
@@ -652,10 +645,8 @@ def _main(options, module=False):
652645
continue
653646
if not module and os.path.samefile(target, script_file):
654647
# Ignore the script to be run in eager importing
655-
# (but make sure that it is handled by `--prof-mod`)
656-
if options.prof_mod is None:
657-
options.prof_mod = []
658-
options.prof_mod.append(script_file)
648+
# (`line_profiler.autoprofile.autoprofile.run()` will
649+
# handle it)
659650
continue
660651
modname = modpath_to_modname(target)
661652
if modname is None: # Not import-able

0 commit comments

Comments
 (0)