项目介绍
把图片、笔记和代码整理到 images、documents、code、other。文件操作有实际后果,所以默认仅预览;显式 —apply 才移动。请先用复制出来的测试目录,不要直接整理手机整个存储区。
学习目标
- 使用 pathlib 读取明确指定的目录。
- 按扩展名分类,跳过目录、隐藏文件与符号链接。
- 先预览再修改,理解执行边界。
- 对同名目标添加后缀,避免覆盖。
前置知识与运行环境
需要本地 Python 3 和源、目标目录读写权限。普通个人目录通常不需管理员权限,但安卓应用沙箱与授权可能限制范围。浏览器不能按电脑路径读取手机真实目录;网站运行器最多处理其虚拟文件。
先备份并阅读预览结果。示例假设目录由你控制、没有其他程序同时修改;它不是面向敌对文件系统的安全工具,也没有自动撤销或事务回滚。
前置:Python 函数、条件与循环、文件路径基础。项目涉及的标准库会在下方解释,不要求首次就记住全部接口。
实现步骤
- 创建 demo,放入复制来的 main.py、note.txt、photo.jpg。
- 建立扩展名到分类的字典,扩展名转小写。
- 只处理第一层,不递归进入其他文件夹。
- 检查目标不是符号链接或普通文件。
- 规划全部动作。同名时添加 _1、_2 等后缀。
- 无 —apply 时只打印;有参数时再创建目录与移动。
完整代码
源码 ZIP 的 examples/projects 目录含同一文件。保存完整代码,不要只复制 main。
"""Preview-first local file organizer; only --apply moves files."""
import argparse
import shutil
from pathlib import Path
GROUPS = {
".png": "images", ".jpg": "images", ".jpeg": "images",
".pdf": "documents", ".txt": "documents", ".md": "documents",
".py": "code", ".c": "code", ".js": "code",
}
def plan_moves(folder):
reserved = set()
moves = []
for source in sorted(folder.iterdir()):
if (not source.is_file() or source.is_symlink()
or source.name.startswith(".")):
continue
group = GROUPS.get(source.suffix.lower(), "other")
directory = folder / group
if directory.is_symlink() or (directory.exists() and not directory.is_dir()):
raise ValueError(f"目标目录不安全或不是文件夹:{directory.name}")
target = directory / source.name
index = 1
while target.exists() or target in reserved:
target = directory / f"{source.stem}_{index}{source.suffix}"
index += 1
reserved.add(target)
moves.append((source, target))
return moves
def main():
parser = argparse.ArgumentParser(description="按扩展名整理文件;默认仅预览")
parser.add_argument("folder", type=Path)
parser.add_argument("--apply", action="store_true", help="实际移动文件")
args = parser.parse_args()
try:
folder = args.folder.expanduser().resolve()
if not folder.is_dir():
raise ValueError("请选择已有文件夹")
moves = plan_moves(folder)
for source, target in moves:
print(f"{source.name} -> {target.relative_to(folder)}")
if args.apply:
target.parent.mkdir(exist_ok=True)
# Trusted personal folder; no concurrent writers assumed.
if target.exists():
raise ValueError("目标文件已出现,请重新预览后再执行")
shutil.move(str(source), str(target))
print(f"{'已移动' if args.apply else '预览'} {len(moves)} 个文件")
except (OSError, ValueError) as error:
parser.exit(1, f"错误:{error}\n")
if __name__ == "__main__":
main()
代码解释
- plan_moves 返回 source、target 对,分开规划与执行,方便测试。
- reserved 记录本轮已预订目标,exists 同时检查磁盘上已有文件。
- stem 是主名,suffix 是扩展名,冲突时只改变主名。
- —apply 默认 False,因此第一次不创建目录、不移动文件。
- 移动前再次检查目标是否出现,遇到并发变化停止,避免覆盖。
- 分类仅看扩展名,不分析文件内容。改名为 .txt 的图片仍被当作文档。
运行方法与验证
在独立练习目录运行:
python file-organizer.py ./demo
python file-organizer.py ./demo --apply
样例数据的查看或预览结果为:
main.py -> code/main.py
note.txt -> documents/note.txt
photo.jpg -> images/photo.jpg
预览 3 个文件
完整代码已在临时目录核对,文件整理的预览、真实移动与冲突分别检查。实际操作时仍需验证文件,不只看成功提示。
常见错误
- 路径不存在:核对工作目录,含空格的路径加引号。
- 目标是文件或符号链接:程序会拒绝,先在练习目录处理。
- 途中失败:检查已完成的移动;示例无法自动恢复之前步骤。
拓展方向
增加日志后再考虑恢复脚本;允许配置扩展名;增加递归前先设计排除范围。始终保持默认预览,对冲突、空目录与权限失败分别测试。
这一节,你理解到哪里了?
这是你的自我反馈,与练习估计、课程进度分别记录。
本文为TheZ原创教学内容。正文采用 CC BY-NC-SA 4.0,代码采用 MIT 许可。请尊重署名与授权范围。