Repository navigation
Conversation
kernprof.py
Updated call to `line_profiler.autoprofile.autoprofile.run()` to
pass the `config` parameter
line_profiler/autoprofile/ast_tree_profiler.py::AstTreeProfiler
.__init__()
- Loosened type of parameter `prof_mod` to `Sequence[str]`
- Added parameter `config`
.profile()
Updated instantiation of
`line_profiler.autoprofile.profmod_extractor.ProfmodExtractor`
to pass the `config` parameter
line_profiler/autoprofile/autoprofile.py::run()
Updated instantiation of
`line_profiler.autoprofile.ast_tree_profiler.AstTreeProfiler` to
pass the `config` parameter
line_profiler/autoprofile/profmod_extractor.py
_ImportFinder[.find()]
New `ast.NodeVisitor` subclass (and method) for locating
imports nested inside other statements (e.g. conditionals and
definitions)
ProfmodExtractor
.__init__()
- Loosened type of parameter `prof_mod` to `Sequence[str]`
- Added parameter `config`
._get_modnames_to_profile_from_prof_mod()
Loosened type of parameter `prof_mod` to `Sequence[str]`
._ast_get_imports_from_tree()
Now an alias for `_ImportFinder.find()`
._find_modnames_in_tree_imports()
Loosened types of parameters to `Sequence[...]`
.extract_all()
Now handling nested imports in accordance to the `config`
line_profiler/autoprofile/run_module.py
::AstTreeModuleProfiler._check_profile_full_script()
Loosened type of parameter `prof_mod` to `Sequence[str]`
line_profiler/rc/line_profiler.toml
::[tool.line_profiler.prof_mod_import_discovery]
New boolean config table for controlling what kind of statements to
descend into to check for imports; keys:
[`conditionals`, 'try_except', 'contexts', 'loops', 'definitions']
tests/test_autoprofile.py::test_nested_import_discovery()
New test checking that the above toggles work
line_profiler/autoprofile/profmod_extractor.py
Updated wordings of several docstrings and some private names
tests/test_autoprofile.py
test_nested_import_discovery()
Refactored internals out to be shared with other tests
test_import_discovery_in_all_compound_statements()
New test for exhaustively testing each of the language
constructions which create compound statements containing other
statements, that our profiling of imports therein are correctly
controlled by the respective config options
line_profiler/autoprofile/profmod_extractor.py
_ImportFinder.find()
- Replaced param `collect_from_definitions` with
`collect_from_func_defs` and `collect_from_class_defs`
- The `collect_from_*` params now default to and accept `None`,
in which case the values are resolved from the default config
file
_ImportFinder.filter_node_types()
Replaced param `collect_from_definitions` with
`collect_from_func_defs` and `collect_from_class_defs`
ProfmodExtractor._config
Chaged typing (`dict[str, bool]` -> `ConfigSource`)
ProfmodExtractor._ast_get_imports_from_tree()
Now a thin wrapper around `_ImportFinder.find()` so that instead
of passing the `collect_from_*` arguments we can just pass the
`._config`
ProfmodExtractor.extract_all()
Updated call to `._ast_get_imports_from_tree()`
line_profiler/rc/line_profiler.toml
::[tool.line_profiler.prof_mod_import_discovery]
- Replaced key-value pair `definitions = false` with
`func_defs = false` and `class_defs = true`; because while class
definitions are typically once-and-done, if we were to
`add_imported_function_or_module()` on every function call it may
disproportionately impact performance
- Default for `loops` now false, again because of performance
concerns
tests/test_autoprofile.py
test_multitarget_import_transformation_executes()
Refactored internals outside so that other tests can reuse them
test_nested_import_discovery()
Updated parametrization and test-module body because of the
split of `definitions` into `func_defs` and `class_defs`
test_import_discovery_in_all_compound_statements()
Updated parametrization because of the split of `definitions`
into `func_defs` and `class_defs`
test_nested_imports_correct_deduplication_across_scopes()
New test ensuring that deduplication of the import target only
happens in the same scope; e.g. importing the same function in
the function body of different functions should result in a
`add_imported_function_or_module()` call being interpolated once
in each of the functions
line_profiler/autoprofile/ast_profile_transformer.py
<General>
Updated type annotations and docstring formatting
ast_create_star_import_node()
New function parallel to `ast_create_profile_node()` which
creates an additional AST node for profiling star-imports
AstProfileTransformer.__init__()
- Relaxed type of `profiled_imports` (`list[str] | None`
-> `Collection[str] | None`)
- Added param `profile_star_imports`
line_profiler/autoprofile/ast_tree_profiler.py::AstTreeProfiler
._profile_ast_tree()
- Added optional param `profile_star_imports` for toggling
whether to handle star-imports
- Added optional param `modnames_to_profile` to help with calls to
`ast_create_star_import_node()`
.profile()
Added optional param `profile_star_imports` for toggling whether
to handle star-imports
line_profiler/autoprofile/line_profiler_utils.py::add_star_import()
New function/pseudo-method for `LineProfiler` so that it can
retrieve the objects imported by a star-import and profile them
line_profiler/autoprofile/profmod_extractor.py::ProfmodExtractor
._find_modnames_in_tree_imports()
- Loosened typing for param `modnames_to_profile`
- Minor refactoring
.extract_all()
Added argument `filter_star_imports` for toggling whether to
return star-imports (false) or dropping them with a warning
(true)
._modnames_to_profile
New cached property for use by `AstTreeProfiler`
line_profiler/autoprofile/ast_tree_profiler.py
::AstTreeProfiler.profile()
Param `profile_star_imports` now takes `None`, where the value is
then resolved from the `config` supplied at initialization
line_profiler/autoprofile/profmod_extractor.py
_ImportFinder._get_filter_args()
Updated config loc
ProfmodExtractor.extract_all()
Param `filter_star_imports` now takes `None`, where the value
is then resolved from the `config` supplied at initialization
line_profiler/rc/line_profiler.toml::[tool.line_profiler.autoprofile]
New subtable for config options related to
`line_profiler.autoprofile`:
- `prof_star_imports`:
New boolean value for the default of:
- `AstTreeProfiler.profile(profile_star_imports=...)`
- `ProfmodExtractor.extract_all(filter_star_imports=...)`
(negated)
- `import_discovery`:
Migrated from `tool.line_profiler.prof_mod_import_discovery`
line_profiler/autoprofile/ast_profile_transformer.py
::AstProfileTransformer
.__init__()
Added optional param `profile_imports_in` and attributes
`._should_visit_imports` and `._current_loc` for controlling
whether an import statement should be profiled
._visit_import()
Added check to skip import statements nested inside deselected
compound statements
.visit()
New method wrapping around `NodeTransformer.visit()` and do
bookkeeping on the current location (i.e. what kinds of nodes
are we nested in)
._transform()
Added optional param `config` to read in the
`tool.line_profiler.autoprofile.import_dicovery` table and
decide on which import statements to profile
line_profiler/autoprofile/ast_tree_profiler.py
::AstTreeProfiler.__init__()
Param `config` now keyword-only to prevent cluttering the signature
line_profiler/autoprofile/autoprofile.py
_extend_line_profiler_for_profiling_imports()
Added missing `.add_star_import()` method to the `prof`
run()
Param `config` now keyword-only to prevent cluttering the
signature
line_profiler/autoprofile/profmod_extractor.py
_ImportFinder.__init__()
Loosened type annotation on param `node_types` (`dict[...]`
-> `Mapping[...]`)
ProfmodExtractor.__init__()
Param `config` now keyword-only to prevent cluttering the
signature
tests/test_autoprofile.py
test_import_discovery_in_all_compound_statements()
Added parametrization to test the two methods of import
discovery and profiling (`ProfmodExtractor` via
`AstTreeProfiler`, and `AstProfileTransformer`)
test_nested_imports_correct_deduplication_across_scopes()
Ditto above (FIXME: currently failing)
line_profiler/autoprofile/ast_profile_transformer.py
_DuplicateChecker
New object used by `AstProfileTransformer` to figure out whether
to insert a post-import profiling node for an import target, and
to keep track of the profiled targets
_ContextAwareDuplicateChecker
Concrete implmentation of `_DuplicateChecker` which handles
contextualization (e.g. the name being profiled on-import in a
function shouldn't cause it to not be in another)
_LegacyDuplicateChecker
Deprecated implementation of `_DuplicateChecker` that keeps the
old behavior, working only on a collection of already-profiled
names
AstProfileTransformer
.__init__()
Now also taking a mapping for `profiled_imports`, allowing
for context-aware on-import profiling deduplication
._visit_import()
- Updated signature so that the location of the
`ImportTarget` can be correctly resolved
- Updated implementation to use a `_DuplicateChecker`
instance for bookkeeping
.visit_Import(), .visit_ImportFrom()
Updated calls to `._visit_import()`
.generic_visit()
New method which does what `NodeTransformer.generic_visit()`
does but with extra bookkeeping, allowing the
`_DuplicateChecker` to check the current context
._visit_generic_child(), ._visit_generic_children()
New helper methods used by `.generic_visit()`
line_profiler/autoprofile/ast_tree_profiler.py
::AstTreeProfiler._profile_ast_tree()
- Loosened type hints on parameter `tree_imports_to_profile_dict`
(`dict[..., list[...]]` -> `Mapping[..., Sequence[...]]`)
- Updated implementation to:
- Maintain an updated copy of `tree_imports_to_profile_dict`,
taking into account the profiling nodes inserted
- Pass said copy to
`AstProfileTransformer._transform(profiled_imports=...)` so as
to use the new context-aware deduplication
tests/test_autoprofile.py
test_import_discovery_in_all_compound_statements()
Updated call to `AstProfileTransformer._transform()`
test_nested_imports_correct_deduplication_across_scopes()
Updated parametrization and implementation to allow for testing
how `ProfmodExtractor` and `AstProfileTransformer` interact
test_ast_profile_transformer_deprecated_profiled_imports()
New test that `AstProfileTransformer(profiled_imports=...)`
still supports passing a `Collection[str]`, which results in
(more or less) the old behavior and the issuance of a
`DeprecationWarning`
line_profiler/autoprofile/line_profiler_utils.py::add_star_import()
Minor refactoring of internals:
- `__all__` is assumed to be either nonexistent or a
`Sequence[str]`, since any other values would've resulted in an
error upon the preceding star-import anyway
- Calls to `add_imported_function_or_module()` now made in import
order of the names, where possible (i.e. there is an `__all__`)
tests/test_autoprofile.py
_RecordingProfiler.add_imported_function_or_module()
Now returning 1 to be consistent with
`line_profiler_utils.py::add_imported_function_or_module()`
test_drop_and_warn_against_star_imports()
Renamed from `test_handle_star_imports()`
test_add_star_import()
New unit test for `add_star_import()`, testing the retrieval and
(optionally selective) profiling of the imported names
line_profiler/autoprofile/ast_profile_transformer.py
::AstProfileTransformer
._get_profile_imports_in()
New param `profile_nested_imports` allowing for overriding the
`autoprofile.import_discovery` subtable in `config`
._transform()
- New param `profile_nested_imports` (ditto above)
- New param `profile_star_imports` for parsing the default value
thereof to pass to the initializer from `config`
line_profiler/autoprofile/ast_tree_profiler.py::AstTreeProfiler
._profile_ast_tree()
New param `profile_star_imports` allowing for overriding the
`autoprofile.prof_star_imports` value in `config`
.profile()
- New param `profile_nested_imports` (ditto above)
- Param `profile_star_imports` now keyword-only
line_profiler/autoprofile/autoprofile.py::run()
New params `profile_nested_imports` and `profile_star_imports`
(ditto above)
line_profiler/autoprofile/profmod_extractor.py
_ImportFinder._get_filter_args()
ProfmodExtractor._ast_get_imports_from_tree()
New param `find_nested_imports` allowing for overriding the
`autoprofile.import_discovery` subtable in `config`
ProfmodExtractor.extract_all()
- New param `find_nested_imports` (ditto above)
- Param `filter_star_imports` now keyword-only
tests/test_autoprofile.py
::test_import_discovery_in_all_compound_statements()
Updated parametrization and implementation to test both when the
nested-import toggles are passed via the `config` file and
the `profile_nested_imports` argument
kernprof.py
__doc__
Updated with the new options
<CLI options>
--prof-imports
Updated help text
--[no-]prof-star-imports
New flag for whether to profile star-imports, corresponding
to
`~.autoprofile.autoprofile.run(profile_star_imports=...)`
--prof-nested-imports
New flag for selecting constructs wherein to look for and
profile imports, corresponding to
`~.autoprofile.autoprofile.run(profile_nested_imports=...)`
_pre_profile()
Migrated normalization of `options.prof_mod` to
`_parse_arguments()`
_main_profile()
Now calling `line_profiler.autoprofile.autoprofile.run()` with
the appropriate values for the params `profile_star_imports` and
`profile_nested_imports`
line_profiler/rc/line_profiler.toml::[tool.line_profiler.kernprof]
Added new key-value pairs `prof-star-imports` and
`prof-nested-imports`, corresponding to the defaults for the
eponymous `kernprof` CLI options
line_profiler/autoprofile/_import_targets.py
_DROPPED_STAR_IMPORTS_MSG_TEMPLATE
New template for warning messages related to star-imports not
being profiled
ImportTarget._check_and_warn_dropped_imports()
Updated typing and default of param `category`
(`category: type[Warning] = UserWarning`
-> `category: type[Warning] | None = None`)
line_profiler/autoprofile/ast_profile_transformer.py
::AstProfileTransformer._transform()
- Updated warning message emitted (star-imports CAN be handled, we
just configured them not to be bydefault)
- Added new private argument so that we don't double-warn for the
same star-imports
line_profiler/autoprofile/ast_tree_profiler.py::AstTreeProfiler
._profile_ast_tree()
- Fixed bookkeeping of dropped imports to account for the
interpolated nodes
- Added code to issue a warning for dropped `--prof-mod` imports
(see `.profile()`)
- Now calling `AstProfileTransformer._transform()` with the
aforementioned private argument to ensure that we don't
double-warn when using both `--prof-mod` and full-module AST
rewrite with `--prof-imports`
.profile()
Now calling `ProfmodExtractor.extract_all()` with
`filter_star_imports=False` to let `._profile_ast_tree()` handle
the warnings
line_profiler/autoprofile/line_profiler_utils.py::add_star_import()
Updated because of the `_should_profile_regular_import()` rename
line_profiler/autoprofile/profmod_extractor.py
_should_profile_regular_import()
Renamed from `_should_profile()`
_should_profile_star_import()
New helper function analogous to the above but for star-imports
ProfmodExtractor._find_modnames_in_tree_imports()
Fixed behavior when a star-import is encountered: if e.g.
`'foo.bar.foobar'` is in `modnames_to_profile`, it counts as
a match against `ImportTarget.name == 'foo.bar.*'`, because
the `add_star_import()` call inserted after the
`from foo.bar import *` statement will decide whether
`foo.bar.foobar` was actually imported and proceed to profile it
if that is the case
ProfmodExtractor.extract_all()
Updated warning message emitted (star-imports CAN be handled, we
just configured them not to be bydefault)
tests/test_autoprofile.py
_propose_module_names()
Helper function refactored out from `test_add_star_import()`
test_autoprofile_star_imports()
New end-to-end test for testing `kernprof --prof-star-imports`
kernprof.py
__doc__
Updated sample help output
<Flag `--prof-nested-imports`>
- Revised help text
- Added processing for the special value 'all', which selects
all the construct types
tests/test_autoprofile.py
test_autoprofile_star_imports()
Removed redundant `MonkeyPatch.syspath_prepend()` call
test_autoprofile_nested_imports()
New end-to-end test for testing that
`kernprof --prof-nested-imports` behaves as expected
CHANGELOG.rst
Added entry
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #440 +/- ##
==========================================
+ Coverage 84.95% 85.82% +0.87%
==========================================
Files 21 21
Lines 2412 2667 +255
Branches 376 419 +43
==========================================
+ Hits 2049 2289 +240
- Misses 263 276 +13
- Partials 100 102 +2
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
|
I ran a GPT 5.6 review on this: DetailsYes. I would not merge #440 as-is, but I think the underlying feature work and much of the test suite are worth keeping. The PR is still a draft, is currently mergeable, has 12 commits and 11 changed files, and is large: +2,361/−246. There are no submitted human reviews yet. CI is green across the build/test/typecheck matrix. Codecov reports about 93% patch coverage, with 22 changed lines uncovered, concentrated mainly in the new transformer. The motivation is solid. #438 identifies real defects: selective Findings
There is also one policy decision I would settle during that refactor: should existing What I would do with the PRI would ask TTsangSC to keep the feature specification and most of the tests, but simplify the implementation around one authoritative AST transformation pass. The tests are actually one of the strongest parts of the contribution; they encode nested discovery, star-import filtering, CLI behavior, and scope-aware deduplication quite comprehensively. So my maintainer verdict would be request changes, architectural rather than incremental. I would not ask them to throw the work away. The core requirements are good, and their own PR description already points toward essentially the same refactor. After that refactor, I would add two regression tests specifically for the ancestor-index-shift case and dynamic I think its not worth maintaining backwards compat, and we should target this refactor for a line-profiler 6.0 feature, as such I also ran a GPT 5.6 review against your branch. I've read the main bullets, some details, and generally agree with the assessment: DetailsYes. PR #7 is the much better basis if we are willing to call this line_profiler 6.0 work. The author has independently arrived at essentially the same architectural objection we found in #440: multiple AST passes are forcing fragile bookkeeping, so #7 introduces a single authoritative transformer. I would not merge #7 exactly as written, though. I would use it as the prototype for a cleaner 6.0 rewrite and explicitly tell the contributor that we do not need the large backward-compatibility shell. Review findings
There is an additional simplification worth considering once we stop preserving the old implementation: What I would make the 6.0 architectureI think the target should be approximately: No The behavioral tests from #440/#7 are valuable and should survive. I would add three regressions before considering the 6.0 version finished: the dynamic So the verdict is: yes, pivot from #440 to #7, but give TTsangSC permission to go substantially farther than #7. The single-pass insight is right; the remaining problem is that #7 treats it as a backward-compatible alternate implementation. For 6.0, we should instead make it the sole implementation and use the opportunity to delete most of the machinery it was designed to supersede. |
|
Very solid points; and thanks especially for also taking a look at TTsangSC#7. Will get to that in the following days. Would you prefer that I work on dissecting #431 first, or on streamling this one first, and thus also leaving that feature for 6.0? Also interesting that you've switched to GPT for the recent reviews. Is this where the current state of the art is at? Do you find it more capable and/or cost-effective than the corresponding Anthropic offerings nowadays? |
|
I think line-profile information for multiprocessing workflows has a much higher utility than import stars (surely real developers would never use such cursed expressions), whereas multiprocessing is extremely common, and I think it would be more beneficial for users.
I have subscriptions for both. I've found the 5.6 model to be a big step over previous 5.4 and 5.5 iterations, but that is anecdotal. I'm considering looking at some of the other models like Kimi K3, Qwen3.8 Max, GLM 5.3, or DeepSeek 4.1 flash. (ad apparently muse spark 1.3 is a thing now?) And I really need to give local models another look. |
|
Fair point; on it. |
(See earlier and more discussion at: #438, TTsangSC#6)
Motivation
The current
line_profiler.autoprofileimplementation doesn't allow for handling imports nested inside code blocks, unless--prof-mod=<executed/script/or/module> --prof-importsis used:However, the use of
--prof-importswould also mean that all imported names will be indiscriminately profiled; thus we either lose the fine-grained control that--prof-mod=some.specific.targetgives us, or the ability to capture nested imports like the ones found above.Plus, star-imports are still not handled (#434 mitigated the crash, but did not add actual profiling for them yet):
Changes (brief summary)
This draft PR remedies both by:
ProfmodExtractorto do an AST pass for imports, not just scanning overast.Module.body.ast_profile_transformer.py::ast_create_star_import_node()andline_profiler_utils.py::add_star_import()to handle the post-hoc profiling of star-imported names.kernprofCLI flags for controlling both:--prof-star-imports:Whether to handle star-imports (old behavior is false)
--prof-nested-imports=...:What kind(s) of code blocks
ProfmodExtractorshould descend into to scan for importsline_profiler.tomlconfig options:[tool.line_profiler.autoprofile]::prof_star_importsand::import_discovery[tool.line_profiler.kernprof]::prof-star-importsand::prof-nested-importsThe behavior of
--prof-importshas also become more consistent with that of--prof-modsince now both are reined in by--prof-nested-imports=.... But while this is IMO a positive change, it is still a semantic change nonetheless.See TTsangSC#6 for details.
Why this is still in draft
There's quite a lot of code in here, but as I was writing it it felt increasingly apparent to me that:
ProfmodExtractorandAstProfileTransformercrawl and transform (in the case ofProfmodExtractor, indirectly viaAstTreeProfiler._profile_ast_tree()) the AST only added (1) overhead and (2) unnecessary bookkeeping for consistency and prevention of duplication.Cutting out the middleman (
ProfmodExtractor) and lettingAstProfileTransformeralone handle the profiling of imports would fix both, but it is probably not trivial to do it in a backward-compatible way. Hence I figured I'd just put the code out here first, so that we sit on it for a while, and weigh whether to incorporate the changes as-is, or to wait until the next release where we can be more haphazard with changing (esp. removing) and refactoring repo components.See TTsangSC#7 for the proposed further changes.