【Day14 Java转Python】从Java到Python——用Python重构一个Java小工具(文件批量重命名实战)

终于到了最后一天!前13天我们学完了Python的核心语法、面向对象、异常处理、模块、装饰器等。今天我们用它们来干一件实事 :写一个命令行工具,批量重命名文件。

这个需求在工作中太常见了:比如把一堆 IMG_001.jpg 改成 2025-04-16-001.jpg,或者把文件名中的空格替换成下划线。

我们用Python来实现,然后对比一下:如果用Java写同样的工具,要写多少行? 看完你就知道为什么Python被称为"胶水语言"了。


1. 项目需求:批量重命名工具

我们要实现一个命令行工具 rename.py,支持以下功能:

  • python rename.py --pattern "IMG_{}.jpg" --start 1 → 将当前目录所有jpg文件按 IMG_1.jpg, IMG_2.jpg ... 重命名
  • python rename.py --replace " " "_" → 将所有文件名中的空格替换为下划线
  • python rename.py --prefix "vacation_" → 给所有文件添加前缀
  • python rename.py --suffix "_backup" → 给所有文件添加后缀(在扩展名前)
  • python rename.py --lower → 将所有文件名转为小写
  • 支持组合使用,比如同时替换空格并转小写
  • 支持预览模式--dry-run):只打印将要执行的操作,不真正重命名
  • 支持递归子目录--recursive
  • 交互式确认(--yes 跳过确认)

技术点覆盖

  • os 模块:文件操作、路径处理
  • globos.listdir:获取文件列表
  • argparse:命令行参数解析(替代手动解析sys.argv)
  • 异常处理:文件不存在、权限错误、重名冲突
  • 列表推导式、生成器
  • 装饰器(可选:计时、日志)
  • 字符串方法、正则(可选)

2. Python实现(完整代码)

python 复制代码
#!/usr/bin/env python3
# rename.py - 批量重命名工具

import os
import re
import sys
import argparse
from pathlib import Path
from datetime import datetime
from functools import wraps

# ---------- 装饰器:计时 ----------
def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = datetime.now()
        result = func(*args, **kwargs)
        end = datetime.now()
        print(f"⏱️ 耗时: {(end-start).total_seconds():.3f}秒", file=sys.stderr)
        return result
    return wrapper

# ---------- 核心重命名逻辑 ----------
def get_files(directory, recursive, pattern=None):
    """获取待处理的文件列表"""
    if recursive:
        # 递归获取所有文件(包括子目录)
        if pattern:
            return list(Path(directory).rglob(pattern))
        else:
            return [p for p in Path(directory).rglob('*') if p.is_file()]
    else:
        if pattern:
            return list(Path(directory).glob(pattern))
        else:
            return [p for p in Path(directory).iterdir() if p.is_file()]

def apply_transformations(name, args):
    """根据命令行参数对文件名应用一系列变换"""
    original = name
    # 1. 替换空格
    if args.replace:
        old, new = args.replace.split(',') if ',' in args.replace else (args.replace, '')
        if old:
            name = name.replace(old, new)
    # 2. 添加前缀
    if args.prefix:
        name = args.prefix + name
    # 3. 添加后缀(在扩展名前)
    if args.suffix:
        base, ext = os.path.splitext(name)
        name = base + args.suffix + ext
    # 4. 转小写/大写
    if args.lower:
        name = name.lower()
    if args.upper:
        name = name.upper()
    # 5. 使用序号模式(会覆盖其他变换?模式通常独立使用,我们单独处理)
    # 注意:模式序号模式会在外部循环中单独处理,此处不应用
    return name

def generate_sequential_name(original_name, pattern, start, index):
    """根据模式生成带序号的名称,例如 pattern = 'IMG_{}.jpg',则第1个变成 IMG_1.jpg"""
    # 从原文件提取扩展名
    base, ext = os.path.splitext(original_name)
    # 如果pattern中没有包含扩展名,自动加上
    if not pattern.endswith(ext):
        # 用户可能写了带扩展名的pattern,也可能没写
        # 简单处理:如果pattern不含点,则自动添加原扩展名
        if '.' not in pattern:
            new_name = pattern.format(index) + ext
        else:
            new_name = pattern.format(index)
    else:
        new_name = pattern.format(index)
    return new_name

@timer
def main():
    parser = argparse.ArgumentParser(description='批量重命名工具')
    parser.add_argument('--path', default='.', help='目标目录(默认当前目录)')
    parser.add_argument('--recursive', '-r', action='store_true', help='递归处理子目录')
    parser.add_argument('--pattern', help='序号模式,如 "IMG_{}.jpg",{}会被序号替换')
    parser.add_argument('--start', type=int, default=1, help='序号起始值(配合--pattern使用)')
    parser.add_argument('--replace', help='替换字符,格式 "旧,新" 或 "旧"(新为空)')
    parser.add_argument('--prefix', help='添加前缀')
    parser.add_argument('--suffix', help='添加后缀(在扩展名前)')
    parser.add_argument('--lower', action='store_true', help='转为小写')
    parser.add_argument('--upper', action='store_true', help='转为大写')
    parser.add_argument('--dry-run', action='store_true', help='预览模式,不实际重命名')
    parser.add_argument('--yes', '-y', action='store_true', help='跳过确认提示')
    parser.add_argument('--verbose', '-v', action='store_true', help='显示详细信息')
    
    args = parser.parse_args()
    
    # 检查至少有一个变换操作
    if not (args.pattern or args.replace or args.prefix or args.suffix or args.lower or args.upper):
        print("❌ 请至少指定一种重命名规则(--pattern, --replace, --prefix, --suffix, --lower, --upper)")
        sys.exit(1)
    
    # 获取文件列表
    try:
        # 如果指定了pattern,只匹配对应扩展名的文件?我们简单处理:获取所有文件,然后应用变换
        files = get_files(args.path, args.recursive, pattern='*')
    except Exception as e:
        print(f"❌ 读取目录失败: {e}", file=sys.stderr)
        sys.exit(1)
    
    if not files:
        print("⚠️ 没有找到任何文件")
        return
    
    # 准备重命名映射
    rename_pairs = []
    if args.pattern:
        # 按序号模式重命名,只对匹配pattern中扩展名的文件生效?简化:对所有文件
        # 但更好的做法:只对具有相同扩展名的文件进行序号重命名
        # 我们按文件扩展名分组,分别生成序号
        ext_groups = {}
        for f in files:
            ext = f.suffix.lower()
            ext_groups.setdefault(ext, []).append(f)
        for ext, group in ext_groups.items():
            for idx, f in enumerate(sorted(group), start=args.start):
                new_name = generate_sequential_name(f.name, args.pattern, args.start, idx)
                new_path = f.parent / new_name
                rename_pairs.append((f, new_path))
    else:
        # 其他变换规则,每个文件独立
        for f in files:
            new_name = apply_transformations(f.name, args)
            if new_name == f.name:
                continue
            new_path = f.parent / new_name
            rename_pairs.append((f, new_path))
    
    # 去重:如果有多个文件要重命名为同一个目标,冲突检测
    target_map = {}
    conflicts = []
    for src, dst in rename_pairs:
        if dst in target_map:
            conflicts.append((src, dst, target_map[dst]))
        else:
            target_map[dst] = src
    
    if conflicts:
        print("❌ 检测到重名冲突:")
        for src, dst, existing_src in conflicts:
            print(f"   {src} 和 {existing_src} 都想改成 {dst}")
        print("请调整规则后重试。")
        sys.exit(1)
    
    # 显示将要执行的操作
    if not rename_pairs:
        print("✅ 没有需要重命名的文件(变换后名称未变)")
        return
    
    print(f"📋 将重命名 {len(rename_pairs)} 个文件:")
    for src, dst in rename_pairs[:10]:  # 最多显示10个
        print(f"  {src.name} -> {dst.name}")
    if len(rename_pairs) > 10:
        print(f"  ... 以及其他 {len(rename_pairs)-10} 个")
    
    if args.dry_run:
        print("🏁 预览模式,未实际执行")
        return
    
    # 确认
    if not args.yes:
        confirm = input("确认执行?(y/n): ")
        if confirm.lower() != 'y':
            print("已取消")
            return
    
    # 执行重命名
    success_count = 0
    for src, dst in rename_pairs:
        try:
            src.rename(dst)
            if args.verbose:
                print(f"✅ {src.name} -> {dst.name}")
            success_count += 1
        except Exception as e:
            print(f"❌ 重命名 {src.name} 失败: {e}", file=sys.stderr)
    
    print(f"🎉 完成!成功重命名 {success_count} / {len(rename_pairs)} 个文件")

if __name__ == "__main__":
    main()

使用示例

bash 复制代码
# 将所有jpg文件重命名为 photo_1.jpg, photo_2.jpg ...
python rename.py --pattern "photo_{}.jpg" --start 1

# 将所有文件名中的空格替换为下划线(预览)
python rename.py --replace " " "_" --dry-run

# 给所有txt文件添加前缀 "note_"
python rename.py --prefix "note_" --path ./docs --recursive

# 组合:转小写并替换空格
python rename.py --lower --replace " " "_" --yes

3. 如果要用Java实现......

Java实现类似工具需要:

  • 使用 java.io.Filejava.nio.file.FilesPath
  • 递归遍历目录需要自己写栈或递归函数
  • 命令行参数解析:推荐用 picocliApache Commons CLI,或者手写 switch
  • 字符串操作(replace、lowercase等)
  • 重命名冲突检测、异常处理
  • 编译打包成JAR

代码量估算 :Python约200行,Java至少需要 500~600行(含依赖配置、主类、参数解析类、工具类、异常处理等)。

更重要的是,Java没有像 pathlib 这样优雅的路径操作库,也没有 argparse 这样开箱即用的参数解析。开发效率和代码可读性差距巨大。


4. 知识点回顾(前13天在本次项目中的应用)

天数 知识点 在rename工具中的体现
Day1 变量与类型 动态存储文件名、路径
Day2 列表 files 列表、rename_pairs 列表
Day3 字典 分组扩展名、冲突检测使用字典
Day4 控制流 if args.patternfor循环处理文件
Day5 函数与参数 apply_transformationsgenerate_sequential_name
Day6 字符串与正则 替换、大小写转换、扩展名提取
Day7 推导式与生成器 列表推导式获取文件列表
Day8 类与对象 可以使用类封装(虽然这里函数式也够)
Day9 继承 未使用,但可扩展不同策略
Day10 @property 未直接使用
Day11 异常与with try/except 捕获重命名失败
Day12 模块与包 argparsepathlib 标准库导入
Day13 装饰器 @timer 计时
Day14 综合实战 全部

5. 总结与下一步

恭喜你完成了14天的Java转Python系列!你已经具备了用Python独立开发命令行工具的能力。下一步可以:

  1. 发布你的工具 :将 rename.py 打包成可执行文件(用 pyinstaller),或上传到PyPI。
  2. 增加更多功能:支持正则表达式替换、支持按文件修改时间排序等。
  3. 学习Web框架:用Flask或FastAPI将你的工具变成Web服务。
  4. 数据分析:用pandas处理Excel/CSV,替代VBA。

最终对比

维度 Python Java
开发时间 0.5天 2天
代码行数 ~200 ~600
可读性 极高 中等
依赖管理 标准库足够 需要第三方库
运行便利性 有Python即可 需要JRE或打包

送给你一句话

"Java让你成为一个工程师,Python让你成为一个创造者。"

现在,去用Python创造更多有趣的东西吧!别忘了把你的14天系列文章整理成专栏,分享给更多Java转Python的朋友。


(本文代码基于Python 3.14,在VSCode中测试通过。如果你觉得这个系列对你有帮助,请点赞、收藏、转发,让更多人看到!)

相关推荐
吕源林4 小时前
如何处理SQL插入后的数据一致性校验_使用Checksum比对
jvm·数据库·python
2301_777599374 小时前
SQL如何实现动态分组统计_使用存储过程与动态SQL
jvm·数据库·python
Shorasul4 小时前
HTML怎么在GeneratePress中精调图片对齐_GP轻量主题CSS覆盖方法
jvm·数据库·python
无籽西瓜a4 小时前
【西瓜带你学设计模式 | 第十八期 - 命令模式】命令模式 —— 请求封装与撤销实现、优缺点与适用场景
java·后端·设计模式·软件工程·命令模式
qq_334563554 小时前
Python开发Flask项目如何部署到云服务器_使用Fabric自动化发布脚本
jvm·数据库·python
郝学胜-神的一滴4 小时前
ReLU激活函数全解析:从原理到实战,解锁深度学习核心激活单元
人工智能·pytorch·python·深度学习·算法
2301_764150564 小时前
JavaScript中预取Prefetch与预加载Preload策略
jvm·数据库·python
aXin_ya4 小时前
微服务 第二天
java·数据库·微服务
生信小窝4 小时前
079B-Zonae Cogito决策支持系统与R语言可视化结合的Marxan保护区规划课程【2027】
人工智能·python·r语言