Python中如何捕获包括from...import在内的所有导入语句?
from...import) The inspect module falls short here because it only looks at the runtime state of your code—if you use from os.path import basename, the os.path module might not be present in the global namespace (only basename is), so inspect can't pick it up. To capture every type of static import, you need to parse your code's Abstract Syntax Tree (AST) directly, which lets you analyze the source code structure before it's executed.
Here's a complete solution that captures:
- Regular imports (e.g.,
import os) - Aliased imports (e.g.,
import numpy as np) from...importof module members (e.g.,from os.path import basename, join)from...importof submodules (e.g.,from sklearn import metrics)- Aliased
from...import(e.g.,from os.path import join as path_join)
Step-by-Step Implementation
First, we'll use Python's built-in ast module to parse the source code and traverse all import-related nodes:
import ast from typing import List, Dict, Union def collect_imports(file_path: str) -> Dict[str, List[Union[str, Dict[str, str]]]]: """ Collect all imports from a Python file, including from...import statements. Returns a dictionary with two keys: - 'imports': List of regular imports (with aliases if present) - 'from_imports': List of from...import entries, each containing module and imported names """ imports = [] from_imports = [] with open(file_path, 'r', encoding='utf-8') as f: source_code = f.read() # Parse the source code into an AST tree = ast.parse(source_code) # Traverse all nodes in the AST for node in ast.walk(tree): # Handle regular imports (e.g., import os, import numpy as np) if isinstance(node, ast.Import): for alias in node.names: import_entry = { 'module': alias.name, 'alias': alias.asname if alias.asname else None } imports.append(import_entry) # Handle from...import statements (e.g., from os.path import basename, join as path_join) elif isinstance(node, ast.ImportFrom): # The parent module (e.g., 'os.path' for from os.path import ...) parent_module = node.module if node.module else '' # Handle relative imports (e.g., from .utils import helper) if node.level > 0: parent_module = '.' * node.level + parent_module imported_names = [] for alias in node.names: name_entry = { 'name': alias.name, 'alias': alias.asname if alias.asname else None } imported_names.append(name_entry) from_imports.append({ 'parent_module': parent_module, 'imported_names': imported_names }) return { 'imports': imports, 'from_imports': from_imports }
Example Usage
Suppose you have a file sample_imports.py with the following code:
import os import numpy as np from os.path import basename, join as path_join from sklearn import metrics from .local_module import helper_func
Running collect_imports('sample_imports.py') will return:
{ 'imports': [ {'module': 'os', 'alias': None}, {'module': 'numpy', 'alias': 'np'} ], 'from_imports': [ { 'parent_module': 'os.path', 'imported_names': [ {'name': 'basename', 'alias': None}, {'name': 'join', 'alias': 'path_join'} ] }, { 'parent_module': 'sklearn', 'imported_names': [{'name': 'metrics', 'alias': None}] }, { 'parent_module': '.local_module', 'imported_names': [{'name': 'helper_func', 'alias': None}] } ] }
Key Notes
- Handles Relative Imports: The code accounts for relative imports (like
from .utils import ...) by prepending the appropriate number of dots based onnode.level. - Captures Aliases: Both module aliases (e.g.,
import numpy as np) and member aliases (e.g.,from os.path import join as path_join) are recorded. - Static Analysis Only: This approach won't catch dynamic imports (e.g.,
importlib.import_module('os')), since those are resolved at runtime. For dynamic imports, you'd need additional runtime inspection combined with AST parsing. - No Runtime Execution: Since we're parsing the source code directly, there's no risk of executing the code (which is great for analyzing untrusted code).
Why This Works Better Than inspect
The inspect module relies on examining the live module's namespace. When you use from X import Y, only Y is added to the namespace—not the full X module (unless you also imported X separately). AST parsing looks at the actual source code structure, so it can detect every import statement exactly as it's written, regardless of how it affects the runtime namespace.
内容的提问来源于stack exchange,提问作者code base 5000

