Conversation
kernprof.py::_normalize_prof_nested_imports()
line_profiler/autoprof/ast_tree_profiler.py
line_profiler/autoprof/autoprofile.py
Updated renamed/migrated imports
line_profiler/autoprofile/_single_pass_transformer.py
New module to eventually supersede `~.ast_profile_transformer` and
`~.ast_tree_profiler`, and `~.profmod_extractor`
line_profiler/autoprofile/ast_profile_transformer.py
__all__
Added to delineate the public API
AstProfileTransformer._get_profile_imports_in()
Refactored to depend on
`~._single_pass_transformer._CompoundNodeChecker` instead of
`~.profmod_extractor._ImportFinder`
line_profiler/autoprofile/profmod_extractor.py
<General>
Updated renamed/migrated imports
__all__
Added to delineate the public API
_ImportFinder
- Now dependent on
`~._single_pass_transformer._CompoundNodeChecker`
- Updated signatures of `.__init__()` and `.find()` for simpler
use
line_profiler/autoprofile/_import_targets.py
::ImportTarget._from_ast_nodes()
Removed now-unused method
line_profiler/autoprofile/_single_pass_transformer.py
::ContextAwareVisitor
._pre_visit_hook(), ._post_visit_hook()
Renamed from the corresponding public methods
._current_loc, ._node_stack
Now self-consistent when used in node-type-specific visit
methods
.visit(), .generic_visit()
Updated to make sure that the above are self-consistent
line_profiler/autoprofile/ast_profile_transformer.py
::AstProfileTransformer
<General>
Now a `ContextAwareVisitor` subclass with simplified
implementation
._should_visit_imports
Changed typing into `Callable[[ast.AST], bool]`, using the check
as implemented in `_CompoundNodeChecker.check()` instead of
repeating it
._visit_import()
Simplified implementation
line_profiler/autoprofile/profmod_extractor.py::_ImportFinder
<General>
Now a `ContextAwareVisitor` subclass with simplified
implementations
.found_imports
Updated typing
.find()
Updated implementation
.visit(), .visit_Import(), .visit_ImportFrom()
Refactored and simplified from `.generic_visit()`
line_profiler/autoprofile/_single_pass_transformer.py
should_profile_regular_import(), should_profile_star_import()
Migrated from private functions in `~.profmod_extractor`
_DuplicateImportChecker
Migrated from `~.ast_profile_transformer._DuplicateChecker`
_ConcreteDuplicateImportChecker
New concrete implementation of the above
SinglePassTransformer
New `ast.NodeTransformer` class (WIP) which does what
`~.profmod_extractor.ProfmodExtractor` and
`~.ast_profile_transformer.AstProfileTransformer` do in one pass
line_profiler/autoprofile/ast_profile_transformer.py
::_ContextAwareDuplicateChecker
Now a `_ConcreteDuplicateImportChecker` subclass
line_profiler/autoprofile/line_profiler_utils.py
<General>
Updated import path of `should_profile_regular_import()`
__all__
Added to delineate the public API
line_profiler/autoprofile/profmod_extractor.py
::ProfmodExtractor._find_modnames_in_tree_imports()
Updated to use the new `should_profile_regular_import()` and
`should_profile_star_import()`
line_profiler/rc/line_profiler.toml
::[tool.line_profiler.autoprofile]::prof_func_defs
New switch for whether to profile locally-defined functions when
using `line_profiler.autoprofile`
TODO:
Refactor `AstProfileTransformer` to be a wrapper around
`SinglePassTransformer`
line_profiler/autoprofile/ast_profile_transformer.py
::AstProfileTransformer
<General>
Now a `SinglePassTransformer` subclass
.__init__()
Refactored to be a wrapper around `super().__init__()`
(Warning: the signatures are incompatible!)
._handle_new_import_target()
New override for the superclass method to handle
`._dropped_star_imports`
.visit_Import(), visit_ImportFrom()
Now thin wrappers around the superclass methods
line_profiler/_diagnostics.py::USE_LEGACY_AUTOPROF
New boolean switch (env var: `${LINE_PROFILER_AUTOPROFILE_CORE}`)
for whether to use the old system or the new single-pass one
line_profiler/autoprofile/_single_pass_transformer.py
_CompoundNodeChecker
Refactored internals and the `.from_toggles()` constructor
_ProfModHelper
New helper object for dealing with `kernprof`-level tasks having
to do with the `prof_mod` argument, consisting of code
refactored from
`ProfmodExtractor._get_modnames_to_profile_from_prof_mod()` etc.
SinglePassTransformer
.__init__()
- Added new argument `module` for dealing with module and
non-module rewrite modes (see
`~.autoprofile.autoprofile.run(as_module=...)`)
- Extended `config` so that it is possible to pass a
`ConfigSource` directly
- Extended `prof_imports_in` to be more flexible with the
accepted keys
- Streamlined creation of `._import_ancestry_checker`
- Added `._dropped_imports` for bookkeeping
._visit_import()
- Added `ast.copy_location()` calls on the created nodes, so
that if the calls to the profiler's on-import profiling
pseudo-methods fail, the error locations are attributed
correctly to the import statement instead of its parent
- Added bookkeeping for dropped imports
- Updated usage of `._expl_import_handlers` and
`._star_import_handlers`
.visit_ImportFrom()
Now performing normalization of relative imports, using
migrated code from `~.autoprofile.run_module`
._transform()
Refactored from `AstProfileTransformer._transform()`
._expl_import_handlers, ._star_import_handlers
Updated signatures to take `ImportTarget`s, fixing the bug
where node creation for explicit from-imports errors out
line_profiler/autoprofile/ast_profile_transformer.py
::AstProfileTransformer
._handle_new_import_target()
Simplified implementation
._get_ast_transformer()
Refactored from `._transform()`, which used to be a class method
with a big signature
line_profiler/autoprofile/ast_tree_profiler.py
__all__
Added to explicitly delineate the public API
AstTreeProfiler
._check_profile_full_script()
Refactored to use `_ProfModHelper`
._ast_create_node_from_import_target()
Migrated from
`~.ast_profile_transformer._ast_create_node_from_import_target()`
._profile_ast_tree()
Updated creation and use of `AstProfileTransformer`
line_profiler/autoprofile/autoprofile.py
__all__
Added to explicitly delineate the public API
run()
Refactored to use either of two new private functions to
construct the AST for `script_file`, one using the "legacy"
system and the other the new single-pass system
line_profiler/autoprofile/profmod_extractor.py
::ProfmodExtractor._modnames_to_profile
Refactored to use `_ProfModHelper`
line_profiler/autoprofile/run_module.py
__all__
Added to explicitly delineate the public API
ImportFromTransformer.visit_ImportFrom()
Refactored to use the same private utilities as
`SinglePassTransformer.visit_ImportFrom()`
AstTreeModuleProfiler._check_profile_full_script()
Refactored to use `_ProfModHelper`
tests/test_autoprofile.py
test_import_discovery_in_all_compound_statements()
test_nested_imports_correct_deduplication_across_scopes()
test_ast_profile_transformer_deprecated_profiled_imports()
Refactored invocations of `AstProfileTransformer._transform()`
TODO:
The new system doesn't issue warnings about dropped star-imports in
the exact same cases that the old system would; try to reach parity
before further refactoring (esp. deprecation/deletion of the old
system)
line_profiler/autoprofile/_single_pass_transformer.py
_get_conf_table()
Wrapper around `ConfigSource.get_subconfig()` taking care of
cases where `config.subtable != ['tool', 'line_profiler']`,
which may happen if e.g. a subconfig is passed
_CompoundNodeChecker._get_toggles()
SinglePassTransformer.__init__()
Now using `_get_conf_table()`
line_profiler/autoprofile/autoprofile.py::_rewrite_ast_single_pass()
Fixed the argument
`SinglePassTransformer._transform(warn_dropped_star_imports=...)` so
that warnings against non-profiled star-imports are issued in the
same cases as when using the old `line_profiler.autoprofile` core
tests/test_autoprofile.py
test_autoprofile_star_imports()
test_autoprofile_nested_imports()
test_multitarget_import_resolution()
test_nested_import_discovery()
test_import_discovery_in_all_compound_statements()
test_nested_imports_correct_deduplication_across_scopes()
Updated parametrizations and implementations to add subtests for
the new `line_profiler.autoprofile` core
This was referenced Sep 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR mainly exists for documentation/discussion purposes. See also pyutils#440, which this builds upon.
The gist is that with the previous setup (as of pyutils#440) we walk the AST in
line_profiler.autoprofile1–3 separate times:~.run_module.ImportFromTransformerwalks it once to resolve relative imports, if runningkernprof -m.~.profmod_extractor._ImportFinderdoes another walk to glean all~._import_targets.ImportTargetcovered by--prof-mod.~.ast_profile_transformer.AstProfileTransformerwalks the AST a final time to insert@profiledecorators to functions and methods, and to profile imports missed by~.profmod_extractor.ProfmodExtractor.extract_all()when using--prof-imports.This resulted in code duplication, and also in bookkeeping overhead to guard against duplicating the on-import AST nodes, because some were inserted in the body of
~.ast_tree_profiler.AstTreeProfiler._profile_ast_tree(), and some inAstProfileTransformer._transform()which the former conditionally calls.Changes
To streamline things in
line_profiler.autoprofile:~._single_pass_transformersubmodule.SinglePassTransformerclass, which handles all the aforementioned AST transformations in one pass.line_profiler.autoprofilecomponents are refactored to wrap around~._single_pass_transformercomponents, but are kept around for backward compatibility.line_profiler._diagnostics.USE_LEGACY_AUTOPROF(backed by the environment variable${LINE_PROFILER_AUTOPROFILE_CORE}) is used so that when using~.autoprofile.run(), one can switch between:SinglePassTransformer, andTests in
tests/test_autoprofile.pyhave been updated to test both cores and their resp. components where appropriate.Discussions/TODOs
Should we deprecate the old interfaces (
line_profiler.autoprofile.ast_profile_transformer,.ast_tree_profiler,.profmod_extractor, and.run_module)?Or maybe even just wait for version 6 to remove them outright? (This knocks about 1.1k lines out, putting our LoC at around the same level as ENH: handle nested and star imports pyutils/line_profiler#440. Further savings may be possible, since some of the
~._single_pass_transformerand~.autoprofilecomponents are mostly there to allow for keeping the behaviors consistent with ENH: handle nested and star imports pyutils/line_profiler#440.)The single-pass approach streamlines the workflow and reduces inter-component bookkeeping which is a pain, but so far has failed to either actually
For reference regarding the latter, I ran
pytest -k "(test_autoprofile and not star_import and not nested_import and not ast_tree_profiler and not ast_profile_transformer and not profmod_extractor and not single_pass_transformer and not multitarget) or not test_autoprofile"to deselect all the tests directly affected by the refactor, and the timings are:This may be a function of how our test suite tends to use small and shallow snippets, which makes multiple AST passes inexpensive.
SinglePassTransformeris probably also pretty unoptimized as-is, e.g. not having much in the way of checks against descending into a composite-statement node.