☰
Python范例精讲:文件批处理、数据清洗与命令行工具避坑指南
2026/10/14 16:47:18 网站建设 项目流程

简介:Python 范例压缩包专为 Python 初学者与进阶自学者设计,覆盖从基础语法到实际项目开发的完整路径。包内示例涉及变量、数据类型、控制流、函数定义,以及面向对象、异常处理、文件操作、网络编程和单元测试等关键知识点,并加入了 os、sys、math、pandas/numpy 等常用模块的调用示范。整个压缩包共 43 个文件,包括 20 个 py 源码、14 个 pyc 编译文件以及少量 xml、plist 配置与工程记录文件,大小仅 54KB,便于快速下载和按目录检索;pyc 文件还可作为字节码参考,帮助理解 Python 的执行机制。模块划分清晰,涵盖线程、装饰器、Excel/CSV 数据处理、日志收集、配置解析、系统信息采集、自动化脚本与简单 Web 开发实例,适合通过阅读代码掌握真实开发中的常见工具链。目前已有 440 人学习,对希望将理论转化为实践的学习者来说,是一份轻量而实用的参考资料。

1. Python 范例:别把它当代码库,把它当工程模板

很多人收藏Python范例,真正落地时却发现自己连“能跑”都难。问题的根源通常不是语法,而是范例里的路径写法、异常兜底和参数抽取没被看懂。这篇文章选三个最贴近日常工作的Python范例——文件批处理、数据清洗、命令行待办工具——逐个拆开讲,再单独给出一份避坑清单。每一段都从“这个范例为什么值得写”开始,然后给出完整代码、关键参数和扩展方向,让你能照着改成自己的版本。

适合有Python语法基础、想把临时脚本提升为可维护程序的开发者。如果你希望看完整项目工程,那这些范例也是理解项目骨架的第一步。废话不多说,直接看第一个范例。

2. 范例一:文件批量重命名与归档——先学会路径与异常兜底

如果只抄一句shutil.move,你会在真实目录里遇到一堆意外:文件名里没有日期、目标目录已存在、权限不足、路径分隔符在Windows和Linux下不一样。这一章的范例补上了这些边界,代码可以直接跑,也能让你看清一段“交付级”脚本比“玩具脚本”多做了什么。

2.1 为什么选文件批处理作为第一个范例

文件批处理是Python自动化里最高频的小目标,也是最适合练手的场景。它不需要第三方依赖,只要有Python解释器就能跑,反馈非常快。更重要的是,它把路径解析、目录创建、文件移动、日志和异常兜底这几项基础能力集中到了一起。你如果能把一个批处理脚本写稳,后面写数据处理、接口调用都会顺手很多。

很多人在这里翻车不是不会用os.path,而是不清楚Path对象和字符串路径的边界。比如Windows路径用反斜杠,Linux用正斜杠,写死分隔符的代码一换环境就崩。又比如目录已经存在时mkdir会抛错,但你可能根本没想过提前判断。这些细节在范例里都是重点。下面这个脚本实现一个很常见的需求:把下载目录中文件名带日期的日志文件,按“年-月”归档到另一个目录。

2.2 完整范例:把下载目录的日志按月份归档

#!/usr/bin/env python3 # -*- coding: utf-8 -*- from pathlib import Path import re import shutil import logging logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") log = logging.getLogger(__name__) def archive_logs(src: str, dst: str) -> int: src_path = Path(src) dst_path = Path(dst) if not src_path.is_dir(): raise NotADirectoryError(f"源目录不存在: {src}") dst_path.mkdir(parents=True, exist_ok=True) moved = 0 date_pat = re.compile(r"(\d{4})-(\d{2})-\d{2}") for f in src_path.glob("*.log"): m = date_pat.search(f.stem) if not m: log.warning("跳过无日期文件: %s", f.name) continue year, month = m.group(1), m.group(2) target_dir = dst_path / f"{year}-{month}" target_dir.mkdir(parents=True, exist_ok=True) target = target_dir / f.name if target.exists(): log.error("目标已存在,跳过: %s", target) continue shutil.move(str(f), str(target)) moved += 1 log.info("已移动: %s -> %s", f.name, target) return moved if __name__ == "__main__": import sys if len(sys.argv) != 3: print("用法: python archive_logs.py <源目录> <目标目录>") sys.exit(1) count = archive_logs(sys.argv[1], sys.argv[2]) print(f"完成,共移动 {count} 个文件")

逻辑说明:先用Path把字符串路径变成路径对象,这样后面所有路径拼接都用“/”运算符,跨平台不用手动处理分隔符。date_pat从文件名里提取“年-月”,如果文件名没有日期,脚本不会中断,只是记录警告并跳过。目标目录用mkdir(parents=True, exist_ok=True)递归创建,已存在也不报错。移动前先检查target.exists(),避免覆盖同名文件。最后返回移动数量,方便调用方知道这一轮做了什么。

参数说明:glob(".log")只匹配当前目录的log文件,如果目录里还有子目录,改成"**/.log"就可以递归匹配,但建议先在小目录里测试。logging的level可以根据需要调成DEBUG,能看到更多细节。shutil.move接收字符串路径,虽然很多Python版本也支持Path对象,这里转成str是减少边缘问题。脚本入口用sys.argv判断,简单直接;如果你要把这个脚本交给别人用,建议换成argparse,后面第4章就有例子。

2.3 关键参数:Path.mkdir、glob、shutil.move 的取舍

Path.mkdir的三个参数值得细说。parents=True表示如果父目录不存在就递归创建,exist_ok=True表示目标目录即使已经存在也不抛异常。这两个参数组合起来,写脚本时几乎不用再提前判断目录是否存在。

glob的默认行为是匹配当前目录,不包含隐藏文件。要注意的是,glob("/.log")虽然能递归,但如果目录层级特别深,性能会明显下降。更推荐的做法是先用Path.rglob(".log")或glob("/*.log")跑一遍,再在循环里判断f.is_file(),因为glob可能匹配到目录名。

shutil.move在跨盘移动时会走复制后删除的逻辑,如果目标盘空间不足,可能移动失败。另一个容易被忽略的点是,shutil.move在目标文件已存在时会覆盖,所以范例里先做exists()判断。你如果想把“移动”改成“备份+改名”,可以把move换成copy2,然后再决定是否删除原文件。

2.4 这个范例能改出哪些变体

最常见的变体是按扩展名归档。把glob(".log")换成glob(".*"),然后用f.suffix取扩展名,再按扩展名建目录,就能整理下载目录里的各种文件。第二个变体是按文件修改时间归档,不再依赖文件名里的日期,用f.stat().st_mtime配合datetime.fromtimestamp得到日期,适合文件名不规范的旧文件。第三个变体是保留原文件的“复制归档”,把shutil.move换成shutil.copy2,并且跳过target.exists()时也可以覆盖,适合备份场景。

我建议你拿到范例后不要直接跑真实数据,先建一个临时目录,放几个不同日期的文本文件测试。这样你能看清每个参数的作用,也不会因为误操作把重要文件移动后后悔莫及。文件处理是若干个范例里最容易验证的,因为它结果立竿见影。

3. 范例二:CSV数据清洗与统计——pandas的取舍与内存意识

数据处理离不开pandas,但pandas不是万能的。数据量小到几十万行时,用标准库csv也能处理;可一旦涉及缺失值、类型转换、去重和分组统计,pandas的声明式写法会把代码量减少一大半。这一章的范例围绕“订单明细表”清洗展开,目标是得到一份产品维度的汇总表。

3.1 为什么数据处理范例绕不开pandas

我接触过的“Python范例”需求十有六七是CSV清洗。原因是Excel处理几十万行会卡,而且去重、填充缺失、日期解析这些操作在Excel里容易出错,pandas则可以用几行代码稳定复现。但很多人直接pd.read_csv后就开干,结果发现列名有空格、金额列被识别成字符串、单号被当成科学计数法。这一章先给完整代码,再讲关键参数。

pandas版本之间的行为差异也不算小。例如read_csv的dtype参数里可以用字符串映射,但面对缺失值时,普通int类型无法保存NaN,必须用Int64这种可空整数类型。这个细节如果不清楚,清洗时就会遇到“ValueError: cannot convert float NaN to integer”。所以范例里特意把数量列声明为"Int64",而不是默认的float。

3.2 完整范例:清洗订单明细并输出产品汇总

import pandas as pd from pathlib import Path def clean_orders(input_path: str, output_path: str) -> dict: df = pd.read_csv( input_path, encoding="utf-8-sig", dtype={"order_id": str, "quantity": "Int64"}, parse_dates=["order_date"], usecols=["order_id", "order_date", "product", "quantity", "amount"], ) df = df.dropna(subset=["order_id"]) df = df.drop_duplicates(subset=["order_id"], keep="last") df["quantity"] = df["quantity"].fillna(0).astype(int) df["amount"] = pd.to_numeric(df["amount"], errors="coerce").fillna(0.0) summary = df.groupby("product").agg( total_quantity=("quantity", "sum"), total_amount=("amount", "sum"), order_count=("order_id", "count"), ).reset_index() out_path = Path(output_path) out_path.parent.mkdir(parents=True, exist_ok=True) summary.to_csv(out_path, index=False, encoding="utf-8-sig") return {"records": len(df), "products": len(summary)}

逻辑说明:read_csv的usecols只读需要的列,文件大时可以省内存;order_id用str类型,避免长单号被解析成数字并显示为科学计数法。quantity用可空Int64,就是为了允许NaN存在,后续再用fillna填充为0。amount列用pd.to_numeric的errors="coerce",遇到“金额待确认”“-”这类非数字值时变成NaN,再统一填充0。dropna只删掉order_id为空的行,drop_duplicates根据order_id去重,keep="last"表示重复订单保留最后一条,这符合“后一次记录更准确”的业务假设。最后groupby聚合,输出产品维度的汇总。

参数说明:如果源文件是GBK编码,将encoding改成"gbk";如果Excel保存的CSV带BOM,utf-8-sig能自动处理签名,写入时加utf-8-sig也可以让Excel直接打开不乱码。使用usecols还有一个好处:即使CSV里后来多了新列,这段代码也只关心需要的列,不会因为列数变化而崩。你如果希望保留原始列顺序,可以在usecols里按顺序写。

3.3 关键参数:read_csv的dtype、usecols、parse_dates

dtype参数是整个清洗流程的根基。pandas默认会把看起来像数字的列改成int64或float64,但订单号一般不是数字,而是需要保留前导零的字符串。把order_id定义为str后,读取速度会略慢,但避免了几类难查的bug。parse_dates=["order_date"]会把日期字符串解析成datetime类型,后续如果要做按月汇总,直接访问dt属性就行;如果日期格式不标准,可以在parse_dates里指定日期格式参数,或者先读进来再pd.to_datetime。

usecols是很多人不重视的参数。读入DataFrame时如果把不需要的文本列排除掉,内存占用明显下降。比如订单表里可能有一个备注列,每行几百字,全部读进来会让chunksize失效。除了usecols,还可以用dtype的{"amount": "float32"}来控制精度,但要注意聚合结果是否够精确。经验是:数量这种整数用Int64;金额一般用float64,因为float32在求和时可能丢失小数精度,财务对账不容许这种误差。

parse_dates要注意时区问题。如果订单日期带时区偏移,pandas默认会转成带时区的datetime,聚合时可能产生意外。建议在读取前统一约定源文件用UTC或本地时间,不要让同一个脚本在不同环境里得到不同结果。数据处理脚本最容易出玄学问题,十次有九次是类型没对齐。

3.4 大文件处理:chunksize和内存意识

当CSV超过内存可以承受的范围时,read_csv的chunksize参数可以把文件分块读取。常见做法是设chunksize=100000,循环读入每个chunk,先做同样的清洗逻辑,再用分块去重和聚合。下面这段是它的骨架:

result = {} for chunk in pd.read_csv(input_path, chunksize=100000, encoding="utf-8-sig", dtype={"order_id": str}): chunk = chunk.dropna(subset=["order_id"]) result = pd.concat([result, chunk], ignore_index=True) # 最后再做去重和聚合

注意:分块后不能直接对整个DataFrame调用drop_duplicates,因为重复订单可能出现在两个块里。更稳妥的办法是每一块清洗后,用order_id作为去重键做增量更新,重量级建议直接换DuckDB或Spark。如果你的数据只是几百万行,分块加增量聚合通常够用。这个骨架代码看起来是玩具,但它展示了内存受限时的思考方向:不要让所有数据同时进入内存,把大问题拆成小问题。

4. 范例三:命令行待办工具——学argparse与状态存储

命令行工具是Python脚本从“自己跑”走向“别人用”的分水岭。前面的文件批处理用sys.argv也能应付,但一旦需要子命令、位置参数、可选项和帮助文本,argparse就是标准答案。这一章的范例实现一个todo待办工具,支持add、list、done三个子命令,状态存到JSON文件。

4.1 为什么命令行工具最适合练状态持久化

命令行工具看起来简单,它逼你思考三件事:用户怎么输入参数、参数怎么校验、程序的“记忆”存在哪里。很多范例把状态存到内存列表里,程序一退出就丢失,只能算demo。用JSON文件保存状态,既能看到内容,也方便备份和调试。JSON读写用标准库就能完成,不需要数据库,三五个文件级别的状态用它最合适。

选argparse而不是click或typer,原因是argparse是标准库,不需要额外依赖;在生成环境受限时,这个优势很重要。如果将来需要更复杂的交互,再迁移到click也不迟。命令行工具还特别适合写单元测试,因为输入是参数,输出是退出码和标准输出,可以直接断言。

4.2 完整范例:todo.py 的 add/list/done 子命令

#!/usr/bin/env python3 import argparse import json import sys from pathlib import Path TODO_FILE = Path("todo.json") def load_tasks(): if not TODO_FILE.exists(): return [] try: with TODO_FILE.open("r", encoding="utf-8") as fp: data = json.load(fp) return data if isinstance(data, list) else [] except json.JSONDecodeError: return [] def save_tasks(tasks): with TODO_FILE.open("w", encoding="utf-8") as fp: json.dump(tasks, fp, ensure_ascii=False, indent=2) def add_task(title: str, priority: int) -> None: tasks = load_tasks() next_id = max((t["id"] for t in tasks), default=0) + 1 tasks.append({"id": next_id, "title": title, "priority": priority, "done": False}) save_tasks(tasks) print(f"已添加:{title}") def list_tasks(show_all: bool) -> None: tasks = load_tasks() for t in tasks: if t["done"] and not show_all: continue mark = "[x]" if t["done"] else "[ ]" print(f"{t['id']:>3} {mark} (P{t['priority']}) {t['title']}") def done_task(task_id: int) -> None: tasks = load_tasks() for t in tasks: if t["id"] == task_id: t["done"] = True save_tasks(tasks) print(f"完成:{t['title']}") return print(f"任务 {task_id} 不存在", file=sys.stderr) sys.exit(1) def main(argv=None): parser = argparse.ArgumentParser(prog="todo", description="一个简单的命令行待办工具") sub = parser.add_subparsers(dest="command", required=True) add_p = sub.add_parser("add", help="添加任务") add_p.add_argument("title") add_p.add_argument("-p", "--priority", type=int, default=1, choices=[1, 2, 3]) add_p.set_defaults(func=add_task) list_p = sub.add_parser("list", help="列出任务") list_p.add_argument("-a", "--all", action="store_true", help="包含已完成任务") list_p.set_defaults(func=list_tasks) done_p = sub.add_parser("done", help="完成任务") done_p.add_argument("task_id", type=int) done_p.set_defaults(func=done_task) args = parser.parse_args(argv) if args.command == "add": args.func(args.title, args.priority) elif args.command == "list": args.func(args.all) elif args.command == "done": args.func(args.task_id) if __name__ == "__main__": main()

逻辑说明:load_tasks在文件不存在或JSON损坏时都返回空列表,不会让用户看到一长串traceback。save_tasks用ensure_ascii=False,任务中文写进文件后是明文,不是\uXXXX。add_task计算next_id时用generator加default=0,比len(tasks)+1更安全,因为删除操作会打乱id连续性。done_task里如果找不到任务,会在stderr输出并把退出码置为1,这符合命令行工具的惯例。

参数说明:argparse的subparsers的dest="command"让子命令名落到args.command;required=True是Python 3.7才有,如果你的Python版本较老,需要自己判断args.command是否为空。priority用choices限制为1、2、3,用户传4时argparse会直接报错。set_defaults(func=...)把处理函数挂到参数对象上,让主流程变得很短。

4.3 关键设计:subparsers、JSON存储、退出码

subparsers的价值在于帮助文本自动生成。用户敲python todo.py add -h,能看到add子命令的每一个参数说明,这是sys.argv永远做不到的。JSON存储方案还有一个好处:todo.json是纯文本,你可以直接打开看,也可以改,甚至可以把它纳入版本管理做备份。缺点也很明显:并发写入时可能互相覆盖,这个工具只适合单用户串行使用。

退出码是整个命令行设计里最容易忽略的。脚本正常完成默认返回0,任何异常会返回1。用户可以在bash里用$?拿到退出码,进一步做条件执行。如果你的工具希望被其他脚本调用,尽量遵守这个惯例:成功返回0,失败返回非0。另一个有用的做法是给main提供argv参数,测试时可以直接main(["add", "买牛奶"]),不需要真的走命令行。

4.4 从命令行工具到可导入模块

这个todo工具虽然叫命令行工具,但add_task、list_tasks、done_task都是纯Python函数,不直接依赖sys.argv。这意味着别的Python脚本可以直接import todo,然后调用todo.add_task("写周报", 2),复用同一份状态。这正是范例从“脚本”走向“程序”的关键一步:把业务逻辑和入口分开。main(argv=None)只负责把参数填进函数,测试时就传一个字符串列表。

如果你想给这个工具加上“列表以JSON格式输出”能力,可以给list_tasks加一个--json参数,内部用json.dumps输出,方便其他程序消费。再进一步,状态存储从JSON文件换成sqlite,只需要改写load_tasks和save_tasks两个函数,main的其余代码不用动。这说明把I/O集中到函数边界的重要性。

5. Python范例落地避坑:五个最容易翻车的细节

你是不是经常遇到“复制了网上的范例,运行时却报错”的情况?大多数范例都不会把运行环境、边界条件和错误处理写全。这一章聚焦五个我自己反复踩过的坑,每个都按现象、原因、解决来写,希望能帮你少走弯路。

5.1 路径用字符串拼接:一次跨平台就让你崩溃

现象:脚本在Windows上跑得好好的,复制到Linux服务器上,突然找不到文件。原因:代码里写死了反斜杠或直接用字符串拼接路径,没有让操作系统去解释。解决:使用pathlib的Path对象,用“/”拼接路径,或者用os.path.join。路径是相对还是绝对也要统一,绝对路径用Path.home()或Path.cwd()拼出来,不要硬编码某个用户目录。

5.2 文本编码不指定:CSV和日志的乱码根源

现象:用gbk编码写入的文件在Linux上读取乱码,或用utf-8写入的文件在Windows上打开出现乱码。原因:不同系统默认编码不同,python的open默认编码跟随环境,不是总等于utf-8。解决:所有涉及文件的open都显式写encoding="utf-8";读CSV时根据文件来源用utf-8或utf-8-sig;写CSV时用utf-8-sig兼容Excel。日志文件也建议在logging.FileConfig里指定encoding="utf-8"。

5.3 不用虚拟环境:依赖冲突让范例变成黑匣子

现象:项目A的脚本要用pandas 1.5,项目B的脚本要用pandas 2.0,你直接pip install后,其中一个脚本开始报错“module has no attribute”。原因:两个项目共用同一个site-packages,版本互相覆盖。解决:每个项目都建自己的虚拟环境,python -m venv .venv,然后激活后安装依赖,并把依赖清单导出到requirements.txt。范例代码虽然小,只要用到第三方库,这一条就必须做。

5.4 print当日志:线上脚本故障排查的后悔药

现象:脚本放在定时任务里,每天凌晨跑。某天没生成输出文件,去查结果只看到一段print,根本不知道是网络超时还是数据为空。原因:print输出到stdout,在定时任务里既不写文件也不带时间,无法追溯。解决:用logging模块,输出到文件并带时间戳;关键步骤打debug级别日志,成功和失败打info或error。不要在关键路径上print敏感数据。

5.5 异常吞噬:except Exception: pass 是最贵的偷懒

现象:脚本最后显示“处理完成”,但实际处理了0条,因为循环里有一个字段总是解析失败,异常被你吞掉了。原因:except Exception: pass把错误完全隐藏,程序继续跑,结果看起来成功,实际是失败。解决:至少改成except Exception as e: logging.exception(e),让错误出现在日志里;如果业务允许跳过,也要明确记录是哪一行、为什么跳过。这个坑在数据处理脚本里最危险,因为数据量一大你根本不会逐行检查。

6. 进阶:把范例重构进自己的项目骨架

范例的价值在于复用,而不是反复Ctrl+C。最后一个阶段,我建议你把前面的脚本往“项目骨架”方向改:逻辑写成纯函数、入口保持main、可变参数抽离成配置。这样你以后遇到新的需求,可以像搭积木一样把已有范例组合起来。

6.1 用函数和main入口给脚本留一条测试后路

第二、三、四章的例子都已经是函数化结构。以文件批处理为例,archive_logs接收两个字符串参数,不读写sys.argv。这样的函数可以直接在交互式环境里调用,也可以用unittest写测试。最简测试可以这样写:

import tempfile from pathlib import Path import archive_logs def test_archive(tmp_path): src = tmp_path / "src" src.mkdir() (src / "2024-05-01.log").write_text("x", encoding="utf-8") count = archive_logs.archive_logs(str(src), str(tmp_path / "dst")) assert count == 1

这里的技巧是临时目录由pytest的tmp_path提供,测试结束后自动清理。如果没有pytest,用tempfile.TemporaryDirectory也行。凡是用sys.argv读参数的main函数,测试时直接传列表即可,不需要真的启动进程。

6.2 用配置文件抽离可变参数

脚本一旦要给别人用,写死在代码里的目录、编码、接口地址就会变成坑。推荐把可变参数抽到配置文件中。下表是一个最小配置集:

参数范例中的变量配置建议
源目录srcJSON或yaml的input_dir
目标目录dstoutput_dir
文件编码encodingutf-8-sig
日志级别levelINFO
接口地址api_urlenv或配置文件

读取配置的方式很简单:如果项目不大,用json.load读config.json;如果涉及环境变量,结合os.getenv。配置要提供默认值,还要在启动时校验关键字段是否存在。

我以前做模拟项目X时,把所有逻辑塞进一个大脚本,文件名路径写死,参数满天飞,最后改需求翻车三次。后来我每次拿到一个范例,都会先做三件事:跑一遍、改一个参数、把入口拆出来。这个习惯让我的脚本从一次性的变成了能复用的,也让我自己少了很多半夜排查的后悔药。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询