案例目的
有效处理产品简称和币种中不包含下划线的情况,确保文件被正确重命名和复制到目标目录。
案例流程
- 配置参数:
- SOURCE_ROOT为包含情景文件夹的根目录路径
- TARGET_ROOT为文件要保存的目标目录路径
- 文件处理流程:
- 自动识别符合产品简称_币种格式的文件夹(如FMT_CNY)
- 在文件夹根目录查找测试集文件(模式:*STEAM_文件夹名.csv)
- 在VAL子目录查找验证集文件(模式:*_STEAM_文件夹名_VALIDATION.csv)
- 按指定格式重命名并复制到目标目录
- 重命名规则:
- 测试集:TERM_PM_{币种}TEST{产品简称}.csv
· 验证集:TERM_PM_{币种}VAL{产品简称}.csv
- 测试集:TERM_PM_{币种}TEST{产品简称}.csv
- 示例转换:
- 原始测试集:/FMT_CNY/20241231_STEAM_FMT_CNY_.csv
- 新文件名:TERM_PM_CNY_TEST_FMT.csv
- 原始验证集:/FMT_CNY/VAL/20241231_STEAM_FMT_CNY_VALIDATION.csv
- 新文件名:TERM_PM_CNY_VAL_FMT.csv
案例重点
- 精确匹配:
- 使用split(‘_’, 1)确保只分割一次下划线
- 最新文件选择:
- 通过文件名排序选择最新文件(文件名开头是日期格式)
- 自动跳过旧版本文件
- 健壮性设计:
- 自动创建目标目录(如果不存在)
- 跳过不符合命名规则的文件夹
- 处理VAL目录不存在的情况
- 保留元数据:
- 使用shutil.copy2保留文件原始元数据和时间戳
import os
import glob
import shutil
import re
def process_data_folders(source_root, target_root):
"""
处理数据情景文件夹中的文件:
1. 识别产品简称+币种命名的文件夹
2. 查找测试集和验证集文件
3. 重命名并复制到目标目录
"""
# 确保目标目录存在
os.makedirs(target_root, exist_ok=True)
# 遍历根目录下的所有文件夹
for folder_name in os.listdir(source_root):
folder_path = os.path.join(source_root, folder_name)
# 只处理文件夹
if not os.path.isdir(folder_path):
continue
# 检查文件夹名称格式 (产品简称_币种)
if '_' not in folder_name:
continue
# 分割产品简称和币种 (确保只分割一次)
parts = folder_name.split('_', 1)
if len(parts) != 2:
continue
product, currency = parts
# 处理测试集文件 (位于情景文件夹根目录)
test_pattern = f"*_STEAM_{folder_name}_.csv"
test_files = glob.glob(os.path.join(folder_path, test_pattern))
if test_files:
# 选择最新的测试集文件 (按文件名排序)
latest_test = sorted(test_files, key=os.path.basename)[-1]
# 构建新文件名
new_test_name = f"TERM_PM_{currency}_TEST_{product}.csv"
shutil.copy2(latest_test, os.path.join(target_root, new_test_name))
# 处理验证集文件 (位于VAL子文件夹)
val_dir = os.path.join(folder_path, "VAL")
if os.path.exists(val_dir):
val_pattern = f"*_STEAM_{folder_name}_VALIDATION.csv"
val_files = glob.glob(os.path.join(val_dir, val_pattern))
if val_files:
# 选择最新的验证集文件
latest_val = sorted(val_files, key=os.path.basename)[-1]
new_val_name = f"TERM_PM_{currency}_VAL_{product}.csv"
shutil.copy2(latest_val, os.path.join(target_root, new_val_name))
def main():
# 配置路径
SOURCE_ROOT = "/path/to/your/source/root" # 替换为您的源目录
TARGET_ROOT = "/path/to/your/target/root" # 替换为目标目录
process_data_folders(SOURCE_ROOT, TARGET_ROOT)
print("文件处理完成!")
if __name__ == "__main__":
main()
使用步骤
- 将脚本中的SOURCE_ROOT和TARGET_ROOT替换为实际路径
- 运行脚本
- 检查目标目录中的文件是否符合命名规范
注意事项
- 如果同一情景文件夹中有多个测试集/验证集文件,脚本会选择文件名最大的(通常是最新的)文件
- 脚本不会删除原始文件,只会在目标目录创建副本
- 如果产品简称或币种包含下划线,该文件夹会被跳过
- 如果不需要保留元数据,可将shutil.copy2改为shutil.copy
V2.0
使用 concurrent.futures 优化,利用多线程批量文件操作。
import os
import glob
import shutil
import re
import pandas as pd
from concurrent.futures import ThreadPoolExecutor, as_completed
def move_column_efficient(source_file, target_file, col_identifier, new_idx):
"""
高效处理CSV文件:读取、处理列、保存
"""
# 读取CSV文件
df = pd.read_csv(source_file)
# 这里添加数据处理逻辑
# 例如:移动列、重命名列、过滤数据等
# 保存处理后的数据
df.to_csv(target_file, index=False)
return target_file
def process_single_folder(folder_path, target_root, folder_name):
"""
处理单个文件夹中的文件
"""
# 分割产品简称和币种 (确保只分割一次)
parts = folder_name.split('_', 1)
if len(parts) != 2:
return []
product, currency = parts
processed_files = []
# 处理测试集文件 (位于情景文件夹根目录)
test_pattern = f"*_STEAM_{folder_name}_.csv"
test_files = glob.glob(os.path.join(folder_path, test_pattern))
if test_files:
# 选择最新的测试集文件 (按文件名排序)
latest_test = sorted(test_files, key=os.path.basename)[-1]
# 构建新文件名
new_test_name = f"TERM_PM_{currency}_TEST_{product}.csv"
target_path = os.path.join(target_root, new_test_name)
move_column_efficient(latest_test, target_path, col_identifier, new_idx)
processed_files.append(target_path)
# 处理验证集文件 (位于VAL子文件夹)
val_dir = os.path.join(folder_path, "VAL")
if os.path.exists(val_dir):
val_pattern = f"*_STEAM_{folder_name}_VALIDATION.csv"
val_files = glob.glob(os.path.join(val_dir, val_pattern))
if val_files:
# 选择最新的验证集文件
latest_val = sorted(val_files, key=os.path.basename)[-1]
new_val_name = f"TERM_PM_{currency}_VAL_{product}.csv"
target_path = os.path.join(target_root, new_val_name)
move_column_efficient(latest_val, target_path, col_identifier, new_idx)
processed_files.append(target_path)
return processed_files
def process_data_folders_parallel(source_root, target_root, max_workers=None):
"""
并行处理数据情景文件夹中的文件
"""
# 确保目标目录存在
os.makedirs(target_root, exist_ok=True)
# 获取所有文件夹
folders = []
for folder_name in os.listdir(source_root):
folder_path = os.path.join(source_root, folder_name)
if os.path.isdir(folder_path) and '_' in folder_name:
folders.append((folder_path, target_root, folder_name))
# 使用线程池并行处理
processed_files = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# 提交所有任务
future_to_folder = {
executor.submit(process_single_folder, *args): args
for args in folders
}
# 收集结果
for future in as_completed(future_to_folder):
try:
result = future.result()
processed_files.extend(result)
except Exception as exc:
folder_args = future_to_folder[future]
print(f"处理文件夹 {folder_args[2]} 时生成异常: {exc}")
return processed_files
def main():
# 配置路径
SOURCE_ROOT = "/path/to/your/source/root" # 替换为您的源目录
TARGET_ROOT = "/path/to/your/target/root" # 替换为目标目录
# 设置最大工作线程数 (None表示使用默认值,通常为CPU核心数*5)
# 对于I/O密集型任务,可以设置更高,如16或32
MAX_WORKERS = 16
# 并行处理文件夹
processed_files = process_data_folders_parallel(SOURCE_ROOT, TARGET_ROOT, MAX_WORKERS)
print(f"处理完成!共处理了 {len(processed_files)} 个文件")
if __name__ == "__main__":
main()
优化说明
-
并行处理结构:
- 将原来的顺序处理改为并行处理,每个文件夹的处理作为一个独立任务
- 使用
ThreadPoolExecutor管理线程池
-
函数重构:
process_single_folder: 处理单个文件夹的逻辑process_data_folders_parallel: 并行调度所有文件夹处理
-
参数说明:
max_workers: 控制并行线程数- 对于I/O密集型任务,可以设置较高值(如16-32)
- 如果遇到磁盘I/O瓶颈,可以适当降低此值
-
异常处理:
- 使用
as_completed捕获和处理单个文件夹处理中的异常 - 不会因为一个文件夹处理失败而影响其他文件夹
- 使用
-
内存考虑:
- 内存足够大(128GB),可以同时处理多个文件
- 如果遇到内存问题,可以降低
max_workers或使用批处理
性能优化建议
-
调整线程数:
- 开始时可以尝试
max_workers=8,然后逐步增加观察效果 - 使用
time命令测量执行时间:time python your_script.py
- 开始时可以尝试
-
监控资源使用:
- 使用
htop或任务管理器监控CPU和内存使用情况 - 如果磁盘I/O成为瓶颈,考虑减少线程数或使用SSD
- 使用
-
进一步优化:
- 如果
move_column_efficient中的数据处理很耗时,可以考虑优化这些操作 - 对于特别大的CSV文件,可以考虑使用
dask或分块处理
- 如果
V2.1
import os
import glob
import re
import pandas as pd
from concurrent.futures import ProcessPoolExecutor, as_completed
import multiprocessing
from functools import partial
# 定义全局变量(如果需要)
col_identifier = 'CC' # 示例值,请根据实际情况修改
new_idx = 1 # 示例值,请根据实际情况修改
def move_column_efficient(source_file, target_file, col_identifier, new_idx):
"""
高效处理CSV文件:读取、处理列、保存
"""
try:
# 读取CSV文件
df = pd.read_csv(source_file)
# 这里添加数据处理逻辑
# 例如:移动列、重命名列、过滤数据等
# 保存处理后的数据
df.to_csv(target_file, index=False)
return target_file
except Exception as e:
print(f"处理文件 {source_file} 时出错: {str(e)}")
return None
def process_single_folder(folder_info):
"""
处理单个文件夹中的文件
"""
folder_path, target_root, folder_name = folder_info
try:
# 分割产品简称和币种 (确保只分割一次)
parts = folder_name.split('_', 1)
if len(parts) != 2:
return []
product, currency = parts
processed_files = []
# 处理测试集文件 (位于情景文件夹根目录)
test_pattern = f"*_STEAM_{folder_name}_.csv"
test_files = glob.glob(os.path.join(folder_path, test_pattern))
if test_files:
# 选择最新的测试集文件 (按文件名排序)
latest_test = sorted(test_files, key=os.path.basename)[-1]
# 构建新文件名
new_test_name = f"TERM_PM_{currency}_TEST_{product}.csv"
target_path = os.path.join(target_root, new_test_name)
result = move_column_efficient(latest_test, target_path, col_identifier, new_idx)
if result:
processed_files.append(result)
# 处理验证集文件 (位于VAL子文件夹)
val_dir = os.path.join(folder_path, "VAL")
if os.path.exists(val_dir):
val_pattern = f"*_STEAM_{folder_name}_VALIDATION.csv"
val_files = glob.glob(os.path.join(val_dir, val_pattern))
if val_files:
# 选择最新的验证集文件
latest_val = sorted(val_files, key=os.path.basename)[-1]
new_val_name = f"TERM_PM_{currency}_VAL_{product}.csv"
target_path = os.path.join(target_root, new_val_name)
result = move_column_efficient(latest_val, target_path, col_identifier, new_idx)
if result:
processed_files.append(target_path)
return processed_files
except Exception as e:
print(f"处理文件夹 {folder_name} 时出错: {str(e)}")
return []
def process_data_folders_parallel(source_root, target_root, max_workers=None):
"""
并行处理数据情景文件夹中的文件
"""
# 确保目标目录存在
os.makedirs(target_root, exist_ok=True)
# 获取所有文件夹
folders = []
for folder_name in os.listdir(source_root):
folder_path = os.path.join(source_root, folder_name)
if os.path.isdir(folder_path) and '_' in folder_name:
folders.append((folder_path, target_root, folder_name))
# 设置合理的最大工作进程数
if max_workers is None:
max_workers = min(len(folders), multiprocessing.cpu_count())
processed_files = []
# 使用进程池并行处理
with ProcessPoolExecutor(max_workers=max_workers) as executor:
# 提交所有任务
future_to_folder = {
executor.submit(process_single_folder, folder_info): folder_info
for folder_info in folders
}
# 收集结果
for future in as_completed(future_to_folder):
try:
result = future.result(timeout=3600) # 设置超时时间(1小时)
processed_files.extend(result)
except Exception as exc:
folder_info = future_to_folder[future]
print(f"处理文件夹 {folder_info[2]} 时生成异常: {exc}")
return processed_files
def main():
# 配置路径
SOURCE_ROOT = "/path/to/your/source/root" # 替换为您的源目录
TARGET_ROOT = "/path/to/your/target/root" # 替换为目标目录
# 设置最大工作进程数
MAX_WORKERS = multiprocessing.cpu_count() # 使用CPU核心数
print(f"开始处理,使用 {MAX_WORKERS} 个进程")
# 并行处理文件夹
processed_files = process_data_folders_parallel(SOURCE_ROOT, TARGET_ROOT, MAX_WORKERS)
print(f"处理完成!共处理了 {len(processed_files)} 个文件")
if __name__ == "__main__":
# 在Windows上使用多进程时必须保护主模块
multiprocessing.freeze_support() # 可选,对于打包成exe有用
main()
关键修改和说明
-
参数传递:
- 确实需要将参数打包成元组传递,因为多进程需要序列化参数
- 修改了
process_single_folder函数以接受单个元组参数
-
异常处理:
- 在关键位置添加了异常捕获,防止单个任务失败导致整个进程崩溃
- 添加了超时设置,防止任务无限期挂起
-
进程数设置:
- 使用
multiprocessing.cpu_count()获取CPU核心数 - 设置合理的最大进程数,避免创建过多进程导致系统资源耗尽
- 使用

被折叠的 条评论
为什么被折叠?



