简介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(levellogging.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(parentsTrue, exist_okTrue) 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(parentsTrue, exist_okTrue) 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(parentsTrue, exist_okTrue)递归创建已存在也不报错。移动前先检查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的三个参数值得细说。parentsTrue表示如果父目录不存在就递归创建exist_okTrue表示目标目录即使已经存在也不抛异常。这两个参数组合起来写脚本时几乎不用再提前判断目录是否存在。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, encodingutf-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], keeplast) df[quantity] df[quantity].fillna(0).astype(int) df[amount] pd.to_numeric(df[amount], errorscoerce).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(parentsTrue, exist_okTrue) summary.to_csv(out_path, indexFalse, encodingutf-8-sig) return {records: len(df), products: len(summary)}逻辑说明read_csv的usecols只读需要的列文件大时可以省内存order_id用str类型避免长单号被解析成数字并显示为科学计数法。quantity用可空Int64就是为了允许NaN存在后续再用fillna填充为0。amount列用pd.to_numeric的errorscoerce遇到“金额待确认”“-”这类非数字值时变成NaN再统一填充0。dropna只删掉order_id为空的行drop_duplicates根据order_id去重keeplast表示重复订单保留最后一条这符合“后一次记录更准确”的业务假设。最后groupby聚合输出产品维度的汇总。参数说明如果源文件是GBK编码将encoding改成gbk如果Excel保存的CSV带BOMutf-8-sig能自动处理签名写入时加utf-8-sig也可以让Excel直接打开不乱码。使用usecols还有一个好处即使CSV里后来多了新列这段代码也只关心需要的列不会因为列数变化而崩。你如果希望保留原始列顺序可以在usecols里按顺序写。3.3 关键参数read_csv的dtype、usecols、parse_datesdtype参数是整个清洗流程的根基。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参数可以把文件分块读取。常见做法是设chunksize100000循环读入每个chunk先做同样的清洗逻辑再用分块去重和聚合。下面这段是它的骨架result {} for chunk in pd.read_csv(input_path, chunksize100000, encodingutf-8-sig, dtype{order_id: str}): chunk chunk.dropna(subset[order_id]) result pd.concat([result, chunk], ignore_indexTrue) # 最后再做去重和聚合注意分块后不能直接对整个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, encodingutf-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, encodingutf-8) as fp: json.dump(tasks, fp, ensure_asciiFalse, indent2) def add_task(title: str, priority: int) - None: tasks load_tasks() next_id max((t[id] for t in tasks), default0) 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} 不存在, filesys.stderr) sys.exit(1) def main(argvNone): parser argparse.ArgumentParser(progtodo, description一个简单的命令行待办工具) sub parser.add_subparsers(destcommand, requiredTrue) add_p sub.add_parser(add, help添加任务) add_p.add_argument(title) add_p.add_argument(-p, --priority, typeint, default1, choices[1, 2, 3]) add_p.set_defaults(funcadd_task) list_p sub.add_parser(list, help列出任务) list_p.add_argument(-a, --all, actionstore_true, help包含已完成任务) list_p.set_defaults(funclist_tasks) done_p sub.add_parser(done, help完成任务) done_p.add_argument(task_id, typeint) done_p.set_defaults(funcdone_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_asciiFalse任务中文写进文件后是明文不是\uXXXX。add_task计算next_id时用generator加default0比len(tasks)1更安全因为删除操作会打乱id连续性。done_task里如果找不到任务会在stderr输出并把退出码置为1这符合命令行工具的惯例。参数说明argparse的subparsers的destcommand让子命令名落到args.commandrequiredTrue是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(argvNone)只负责把参数填进函数测试时就传一个字符串列表。如果你想给这个工具加上“列表以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都显式写encodingutf-8读CSV时根据文件来源用utf-8或utf-8-sig写CSV时用utf-8-sig兼容Excel。日志文件也建议在logging.FileConfig里指定encodingutf-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. 进阶把范例重构进自己的项目骨架范例的价值在于复用而不是反复CtrlC。最后一个阶段我建议你把前面的脚本往“项目骨架”方向改逻辑写成纯函数、入口保持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, encodingutf-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时把所有逻辑塞进一个大脚本文件名路径写死参数满天飞最后改需求翻车三次。后来我每次拿到一个范例都会先做三件事跑一遍、改一个参数、把入口拆出来。这个习惯让我的脚本从一次性的变成了能复用的也让我自己少了很多半夜排查的后悔药。希望帮到你。本文还有配套的精品资源点击获取