Python导出模块的源码
importlib.util.find_spec
importlib.util.find_spec 用于查找并返回指定模块的"模块规范"(module spec)。模块规范是一个包含模块详细信息的对象,它描述了如何加载该模块。
使用场景
- 检查模块是否存在:如果返回值为 None,表示模块不存在
- 获取模块的源代码:结合 loader.get_source() 方法可以获取模块源代码
- 获取模块的文件路径:通过 origin 属性获取模块文件的位置
- 检查模块是否为包:通过 submodule_search_locations 是否为 None 来判断
至于为什么这么搞嘛,当然是为了对抗某种python代码加密方式。
实现
import importlib.util
import os
from pathlib import Path
def import_and_output(module_name):
# 获取模块的规范
spec = importlib.util.find_spec(module_name)
if spec is None:
raise ImportError(f"无法找到模块: {module_name}")
# 使用加载器获取源代码
source_code = spec.loader.get_source(module_name)
if source_code is None:
raise RuntimeError("无法获取模块源代码")
print(source_code)
# 示例:导入并输出名为'example'的模块内容
# import_and_output('app')
def get_loadable_modules_recursive(root_dir=None, exclude_dirs=None):
"""
递归获取目录下所有可加载的 Python 模块
:param root_dir: 起始目录(默认当前目录)
:param exclude_dirs: 要排除的目录名列表(如 venv, .git 等)
:return: 模块全名列表(例如 "package.sub.module")
"""
if root_dir is None:
root_dir = os.getcwd()
if exclude_dirs is None:
exclude_dirs = {'__pycache__', 'venv', '.git', '.idea'}
modules = []
root_path = Path(root_dir).resolve()
for dir_path, dir_names, file_names in os.walk(root_dir, topdown=True):
# 过滤排除目录
dir_names[:] = [d for d in dir_names if d not in exclude_dirs]
# 检查是否为有效 Python 包(存在 __init__.py)
is_package = '__init__.py' in file_names
relative_path = Path(dir_path).relative_to(root_path)
package_parts = list(relative_path.parts)
for file in file_names:
if file == '__init__.py' or not file.endswith('.py'):
continue
# 生成模块层级名称
module_name = file[:-3]
full_module_parts = package_parts.copy()
# 修改这里: 不再要求必须是包
if dir_path == str(root_path):
# 根目录下的模块
full_module_parts.append(module_name)
full_module = '.'.join(full_module_parts)
elif is_package:
# 是包内的模块
full_module_parts.append(module_name)
full_module = '.'.join(full_module_parts)
else:
# 非包内的模块 - 直接构建模块路径
# 这里我们需要确定相对路径,并形成导入模块名
if not full_module_parts: # 空列表处理
full_module = module_name
else:
full_module_parts.append(module_name)
full_module = '.'.join(full_module_parts)
# 验证模块名有效性
if not all(part.isidentifier() for part in full_module_parts + [module_name]):
continue
# 添加模块文件的完整路径信息以便后续处理
module_file_path = os.path.join(dir_path, file)
modules.append((full_module, module_file_path))
return modules
# modules = get_loadable_modules_recursive(
# exclude_dirs=['venv', 'tests']
# )
# print("可加载模块列表:\n", '\n'.join(modules))
# import_and_output('extensions.ext_commands')
def backup_modules_to_target(target_dir, root_dir=None, exclude_dirs=None):
"""
备份所有可加载的Python模块源代码到目标目录
:param target_dir: 目标目录路径
:param root_dir: 起始目录(默认当前目录)
:param exclude_dirs: 要排除的目录名列表
:return: 成功备份的模块数量
"""
# 获取所有可加载的模块
module_info = get_loadable_modules_recursive(root_dir, exclude_dirs)
backup_count = 0
# 确保目标目录存在
target_path = Path(target_dir)
target_path.mkdir(exist_ok=True, parents=True)
for module_name, module_file_path in module_info:
try:
# 直接从文件路径读取源代码,而不是通过importlib
with open(module_file_path, 'r', encoding='utf-8') as f:
source_code = f.read()
# 构建目标文件路径
module_parts = module_name.split('.')
module_dir = target_path.joinpath(*module_parts[:-1])
module_file = module_parts[-1] + '.py'
module_path = module_dir / module_file
# 确保目标目录存在
module_dir.mkdir(exist_ok=True, parents=True)
# 写入源代码到目标文件
with open(module_path, 'w', encoding='utf-8') as f:
f.write(source_code)
print(f"已备份模块: {module_name} 到 {module_path}")
backup_count += 1
except Exception as e:
print(f"备份模块 {module_name} 时出错: {e}")
print(f"备份完成,共备份了 {backup_count} 个模块")
return backup_count
# 示例用法
backup_modules_to_target('backup_modules')