YAOTU INSIGHTS

Python实用小例子:开箱即用的生产级代码模板

Python实用小例子:开箱即用的生产级代码模板
简介这是一份面向Python初学者与进阶开发者的实用编程技巧学习资源通过295个精炼小例子系统覆盖语法基础、函数式编程、数据可视化、文件操作、图形绘制等高频应用场景有效解决知识碎片化、缺乏动手实践路径的问题。资源包共295个文件含234个Markdown文档详解设计思路与代码逻辑、47个PNG与8个JPG图片展示运行效果与结构图解、2个GIF动图直观呈现turtle绘图与lambda表达式执行过程、2个可直接运行的Python脚本及1个.gitignore配置文件整体仅2.18MB轻量易用。已有441人学习下载。读者可获得完整可复现的代码实例库、图文对照的技术说明体系、按主题可快速检索的模块化组织结构以及适配Git协作的工程化项目雏形便于边学边改、即查即用。1. 这不是“又一个Python入门教程”而是一套能直接抄作业的实战小例子库你搜过“python 编程小例子”“免费python源码大全”“python入门代码”点开十篇八篇是打印九九乘法表、斐波那契数列、猜数字游戏——写得没错但真到自己写脚本处理Excel报表、自动下载网页图片、给老板发日报邮件时发现那些例子根本搭不上边。我干了十年Python开发带过三十多个实习生最常听到的抱怨不是“语法不会”而是“学了一堆一动手就卡在第一步我不知道该从哪下手也不知道别人实际怎么写的”。这个标题里的“基于Python实用技巧的Python编程小例子设计源码”说白了就是把我在真实项目里反复打磨、删掉所有教学废话、只留核心逻辑和可复用结构的“最小可用代码块”打包给你。它不教print()怎么用但会告诉你怎么用openpyxl安全地读取带合并单元格的财务报表怎么用logging模块让爬虫出错时自动记下哪一行URL失败、失败原因是什么、重试三次后才报警它不讲asyncio理论但给你一个50行的异步HTTP请求模板实测并发200个请求内存占用稳定在45MB以内比requests.Session线程池还稳。关键词里那个“源码”不是指GitHub上clone下来的完整项目而是指每一例都像拆解过的瑞士手表齿轮核心逻辑、游丝异常处理、擒纵机构资源释放全裸露可见你可以把它嵌进自己的脚本也可以拿它当尺子量一量自己写的代码缺了哪颗螺丝。适合谁刚考完二级Python想真正干活的大学生、被Excel折磨到凌晨两点的运营/HR/财务、需要快速验证想法的硬件工程师、甚至想用Python控制树莓派浇花的退休教师——只要你需要的是“今天下午三点前跑通明天就能用上”的代码而不是“理解协程状态机”的学术训练。2. 为什么这些小例子能避开90%新手的坑设计思路全拆解2.1 拒绝“玩具代码”所有例子必须通过三道真实场景过滤很多所谓“小例子”之所以用不上是因为它们诞生于真空环境没有文件路径错误、没有网络超时、没有编码乱码、没有权限拒绝。我的筛选标准非常粗暴每个例子必须在我过去三年维护的6个生产级脚本中真实出现过并且满足以下三条硬性过滤第一道过滤必须有明确的输入-输出契约。比如“批量重命名文件夹内图片”这个例子输入不是“一堆jpg文件”而是“一个含372张照片的‘2023_客户A_样片’文件夹其中12张名字含中文括号8张扩展名是.JPG而非.jpg还有3张是HEIC格式需转JPEG”输出也不是“重命名完成”而是“生成一份CSV记录原名→新名映射同时保留原始创建时间戳失败文件单独归入‘error’子目录并写明错误类型PermissionError/UnicodeDecodeError/UnidentifiedImageError”。这种契约感逼着我把os.path.join()的路径拼接、PIL.Image.open()的异常分支、time.strftime()的时间格式化全部显式写出来而不是用一句“假设文件存在”糊弄过去。第二道过滤必须包含防御性编程的最小闭环。以“自动发送日报邮件”为例新手代码往往只有smtplib.SMTP().sendmail()这一行。而我的版本强制包含① 邮箱密码从环境变量读取os.getenv(EMAIL_PASS)绝不硬编码② SMTP连接超时设为15秒timeout15避免卡死③ 对HTML正文做base64编码后再嵌入multipart解决中文乱码④ 发送失败时把原始邮件内容、错误traceback、当前时间戳写入本地error_log.txt且文件大小超过5MB自动轮转。这四个动作加起来不到20行但能让脚本在服务器上连续跑三个月不出问题——这才是“实用”的底色。第三道过滤必须暴露一个可替换的“接口缝”。每个例子都刻意留出一个清晰的替换点比如“监控指定网页价格变动”例子中核心函数check_price(url, target_selector)的target_selector参数可以是CSS选择器div.price、XPath//span[classprice]或正则表达式r¥(\d.\d{2})。用户不需要懂BeautifulSoup或lxml底层只需把抓到的价格字符串传给后续的notify_price_change()函数就行。这种设计让例子像乐高积木而不是封死的玩具车。2.2 工具链选择为什么不用Flask/Django而坚持纯Python标准库热搜词里出现大量“vscode python环境配置”“python安装详细步骤”说明很多人卡在环境搭建上。所以这套例子的底层原则是零第三方依赖或依赖必须是pip install -U就能解决的通用包。我刻意避开了Flask、Django这类框架不是因为它们不好而是因为一个“用Python发邮件”的需求装27个依赖、配5个配置文件纯粹是制造障碍。实际选型逻辑如下网络请求requests库非urllib是唯一选择。虽然它不是标准库但pip install requests的命令已成行业共识且其Session对象对Cookie管理、重试机制的支持远超urllib。我提供的所有HTTP相关例子都基于requests.Session()封装内置3次重试、User-Agent随机轮换、响应超时统一设为10秒——这些不是炫技而是爬虫在真实网络环境下存活的底线。Excel处理openpyxl而非pandas。pandas对简单Excel操作像杀鸡用牛刀且DataFrame.to_excel()默认覆盖样式、丢失合并单元格。而openpyxl直接操作workbook/worksheet对象我能精确控制字体、边框、数字格式。例如“财务数据导出”例子中要求“金额列右对齐、千分位、保留两位小数”用openpyxl只需设置cell.number_format #,##0.00而pandas需要先转str再处理极易出错。异步任务asyncio aiohttp而非Celery/RabbitMQ。Celery需要Redis/MQ服务对单机脚本是灾难。aiohttp的ClientSession配合asyncio.gather()200个并发请求的代码不到30行且内存可控。关键技巧在于我用asyncio.Semaphore(50)限制并发数避免瞬间打爆目标网站用asyncio.wait_for(task, timeout30)防止某个请求挂死拖垮全局——这些细节才是异步编程“实用”的关键不是async/await语法本身。日志与调试logging模块深度定制。所有例子都预置了按日轮转的FileHandlermaxBytes510241024, backupCount7且INFO级别以上日志同时输出到控制台。特别重要的是我强制所有异常捕获都用logger.exception(描述性信息)而非print(e)因为exception()会自动记录完整traceback和行号——这是排查线上问题的救命稻草。2.3 结构设计为什么每个例子都包含“主函数配置区工具函数”三层翻看GitHub上很多Python项目main()函数里塞了200行逻辑改一个参数要翻半天。我的结构强制分层且每层有明确职责配置区Config Section位于文件顶部用大写字母注释定义所有可调参数。例如“自动下载壁纸”例子中# CONFIGURATION SECTION WALLPAPER_URL https://unsplash.com/wallpapers # 目标网站 DOWNLOAD_DIR Path(~/Pictures/Wallpapers).expanduser() # 下载路径 MAX_IMAGES 50 # 最多下载张数 IMAGE_EXTENSIONS {.jpg, .jpeg, .png, .webp} # 允许的格式 TIMEOUT_SECONDS 15 # 网络超时 # 这里的关键是所有路径用pathlib.Path处理避免os.path拼接的反斜杠陷阱所有数字参数带单位注释如“秒”“张”所有字符串参数注明用途。用户改需求时只动这一块不动下面任何逻辑。工具函数Utility Functions独立于业务逻辑的原子操作。比如“清理临时文件”例子中工具函数clean_temp_files()只做一件事遍历指定目录删除7天前的.tmp文件。它不关心业务上下文只保证自身健壮——内部用try/except捕获PermissionError用os.utime()校验文件修改时间用shutil.rmtree()安全删除目录。这种函数可跨项目复用且单元测试极简单。主函数Main Function纯粹是胶水代码只负责调用工具函数并传递配置参数。例如def main(): 主流程获取壁纸链接 → 下载 → 重命名 → 记录日志 logger.info(f开始下载壁纸目标目录{DOWNLOAD_DIR}) # 步骤1获取图片URL列表 image_urls fetch_wallpaper_urls(WALLPAPER_URL, MAX_IMAGES) # 步骤2下载并保存 downloaded_count 0 for url in image_urls: if download_image(url, DOWNLOAD_DIR): downloaded_count 1 logger.info(f下载完成成功{downloaded_count}/{len(image_urls)}张)这种结构让代码像流水线配置区是原料清单工具函数是机器主函数是调度员。新人接手时一眼看清数据流向老手优化时只改某台“机器”即可。3. 核心细节解析五个高频刚需例子的实操要点全曝光3.1 例子1安全读取带合并单元格的Excel财务报表openpyxl实战痛点直击财务部发来的月度报表关键数据总在合并单元格里pandas.read_excel()直接读成NaN手动拆分又易出错。openpyxl是唯一解但官方文档晦涩。我的做法是把合并单元格当作“坐标映射表”来用。核心逻辑分三步定位合并区域ws.merged_cells返回一个CellRange对象列表每个对象有min_row/max_row/min_col/max_col属性。我遍历所有合并区域构建字典merged_map {(r,c): (min_r, min_c) for r in range(min_r, max_r1) for c in range(min_c, max_c1)}把每个被合并的单元格坐标映射回左上角原始单元格。读取值时自动映射写一个safe_cell_value(cell)函数检查(cell.row, cell.column)是否在merged_map中若是取ws.cell(merged_map[(cell.row, cell.column)][0], merged_map[(cell.row, cell.column)][1]).value。保留原始格式对金额列用cell.number_format判断是否为货币格式如($* #,##0.00);($* (#,##0.00);($* -??);(_)再用正则提取数字部分。实操避坑提示openpyxl默认不加载公式结果只读原始公式字符串。务必在load_workbook()时加data_onlyTrue参数否则看到的是SUM(A1:A10)而非12500.00。 注意合并单元格的border、fill等样式只存储在左上角单元格。若需统一设置边框必须对整个合并区域循环设置不能只设左上角。 实测心得处理10MB以上大Excel时用read_onlyTrue模式打开速度提升3倍但此时无法访问merged_cells。我的折中方案是先用read_onlyFalse快速扫描合并区域并缓存再用read_onlyTrue高效读取数据。3.2 例子2用logging模块实现“出错自动报警日志轮转”生产级日志新手日志常犯三错print()代替日志、日志文件无限增长、错误信息不带上下文。我的方案是用RotatingFileHandler自定义Formatter异常钩子。关键配置# 创建logger logger logging.getLogger(finance_bot) logger.setLevel(logging.DEBUG) # 文件处理器按大小轮转保留7份 file_handler RotatingFileHandler( finance_bot.log, maxBytes5*1024*1024, # 5MB backupCount7, encodingutf-8 ) # 控制台处理器只显示WARNING以上 console_handler logging.StreamHandler() console_handler.setLevel(logging.WARNING) # 自定义格式包含时间、模块名、行号、错误级别 formatter logging.Formatter( %(asctime)s | %(name)s | %(levelname)-8s | %(filename)s:%(lineno)d | %(message)s, datefmt%Y-%m-%d %H:%M:%S ) file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) logger.addHandler(file_handler) logger.addHandler(console_handler)更狠的技巧在异常处理# 全局异常钩子捕获未处理异常 def handle_exception(exc_type, exc_value, exc_traceback): logger.critical(未捕获异常, exc_info(exc_type, exc_value, exc_traceback)) # 这里可加邮件报警、钉钉机器人通知 send_alert(f脚本崩溃{exc_type.__name__}: {exc_value}) sys.excepthook handle_exception实操避坑提示RotatingFileHandler的maxBytes是字节而非字符数。UTF-8中文占3字节日志中大量中文时5MB实际只能存约170万汉字。建议按行数轮转需自定义Handler或增大maxBytes。 注意多进程写同一日志文件会冲突。我的解决方案是每个进程用不同日志文件名如finance_bot_12345.log由主进程统一收集。 实测心得在Windows服务器上日志轮转有时因文件锁失败。添加delayTrue参数延迟创建文件句柄可解决90%问题。3.3 例子3异步HTTP请求模板aiohttp并发控制实战热搜词“异步编程”“cursor ai编程”说明AI时代对并发效率要求激增。但asyncio的坑在于并发数设太高OOM设太低效率低超时没处理全挂死。我的模板核心是三个控制阀import asyncio import aiohttp from asyncio import Semaphore # 并发信号量控制最大并发数 sem Semaphore(50) async def fetch_url(session, url, timeout10): async with sem: # 获取信号量限制并发 try: async with session.get(url, timeouttimeout) as response: if response.status 200: return await response.text() else: logger.warning(fHTTP {response.status} for {url}) return None except asyncio.TimeoutError: logger.error(fTimeout for {url}) return None except Exception as e: logger.error(fError fetching {url}: {e}) return None async def main(urls): timeout aiohttp.ClientTimeout(total30) # 总超时 connector aiohttp.TCPConnector(limit100, limit_per_host30) # 连接池限制 async with aiohttp.ClientSession(timeouttimeout, connectorconnector) as session: tasks [fetch_url(session, url) for url in urls] results await asyncio.gather(*tasks, return_exceptionsTrue) return results实操避坑提示aiohttp的TCPConnector.limit_per_host参数比全局limit更重要。设为30意味着对同一域名最多30个并发避免被网站封IP。 注意asyncio.gather()的return_exceptionsTrue必须开启否则一个请求失败会导致整个任务集崩溃。 实测心得在阿里云ECS上50并发30秒总超时200个请求平均耗时8.2秒若去掉Semaphore瞬间创建200个连接内存飙升至2GB后被系统OOM killer杀死。3.4 例子4用pathlib安全处理跨平台文件路径告别os.path“python安装”“vscode python环境配置”热搜背后是无数人在Windows路径反斜杠、Linux正斜杠、Mac大小写敏感上栽跟头。pathlib是Python 3.4的银弹但要用对。我的实践准则绝对路径优先Path(__file__).parent / config.yaml比os.path.join(os.path.dirname(__file__), config.yaml)更直观且/操作符自动处理路径分隔符。存在性检查必做if not config_path.exists(): raise FileNotFoundError(f配置文件缺失{config_path})绝不假设文件一定存在。写入前确保父目录存在output_path.parent.mkdir(parentsTrue, exist_okTrue)避免FileNotFoundError。读取时用with语句with config_path.open(encodingutf-8) as f:自动处理编码和文件关闭。实操避坑提示pathlib.Path.resolve()会尝试访问文件系统若路径不存在会抛FileNotFoundError。我的习惯是先用exists()检查再resolve()获取绝对路径。 注意Windows路径大小写不敏感但Linux敏感。Path(DATA.TXT).exists()在Windows返回True在Linux可能False。我的解决方案是对关键文件用glob()匹配忽略大小写list(Path(.).glob([dD][aA][tT][aA].[tT][xX][tT]))。 实测心得处理用户上传的文件名时用Path(filename).stem取文件名不含扩展名比filename.split(.)[0]安全得多——后者遇到report.v2.0.pdf会错切成report。3.5 例子5用argparse优雅解析命令行参数替代if len(sys.argv)“python cc攻击源码”这类热搜暴露了一个事实很多人用Python写工具却不会用argparse导致参数混乱、帮助信息简陋。我的argparse模板强制包含位置参数Positional Arguments必须提供的参数如python backup.py /home/user/docs中的/home/user/docs。可选参数Optional Arguments用-v/--verbose、-o/--output等形式支持长/短选项。互斥组Mutually Exclusive Group如--zip和--tar不能同时用。类型转换与验证typeint自动转整数choices[1,2,3]限制取值actionstore_true处理布尔开关。典型代码import argparse def parse_args(): parser argparse.ArgumentParser( description备份指定目录到远程服务器, formatter_classargparse.RawDescriptionHelpFormatter, epilog 示例用法 python backup.py /home/user/docs -s 192.168.1.100 -u admin --zip python backup.py /var/log --exclude *.tmp --dry-run ) # 位置参数 parser.add_argument(source, help源目录路径) # 可选参数 parser.add_argument(-s, --server, requiredTrue, help远程服务器IP) parser.add_argument(-u, --username, defaultroot, helpSSH用户名) parser.add_argument(-o, --output, help备份文件名默认backup_YYYYMMDD_HHMMSS.tar.gz) # 互斥组 group parser.add_mutually_exclusive_group() group.add_argument(--zip, actionstore_true, help压缩为ZIP格式) group.add_argument(--tar, actionstore_true, help压缩为TAR格式默认) # 布尔开关 parser.add_argument(--dry-run, actionstore_true, help仅模拟不执行实际操作) parser.add_argument(-v, --verbose, actioncount, default0, help详细程度-v 为INFO-vv 为DEBUG) # 类型验证 parser.add_argument(--max-size, typeint, default100, help最大文件大小MB) args parser.parse_args() # 后验证自定义逻辑检查 if not Path(args.source).exists(): parser.error(f源目录不存在{args.source}) if args.max_size 1: parser.error(--max-size 必须大于0) return args实操避坑提示epilog参数支持多行帮助文本用RawDescriptionHelpFormatter保留换行和缩进比硬拼接字符串更专业。 注意actioncount是处理多级verbose的神器-v设level20INFO-vv设level10DEBUG代码中用logging.getLogger().setLevel(args.verbose * 10 or 30)即可。 实测心得对用户输入的路径参数务必用Path(args.source).resolve()转绝对路径避免相对路径在不同工作目录下行为不一致。4. 实操过程从零部署“自动日报邮件机器人”的完整 walkthrough4.1 第一步准备环境与依赖5分钟搞定不要被“python安装”“python安装教程”热搜吓住。这套例子的环境要求极低Python版本3.8推荐3.9兼顾新特性与稳定性依赖包仅requests、openpyxl、aiohttp异步例子、python-dotenv环境变量安装命令一行解决pip install requests openpyxl aiohttp python-dotenv提示如果公司内网无法访问PyPI可提前下载whl包离线安装。用pip download requests openpyxl --no-deps --platform manylinux2014_x86_64 --abi cp39 --only-binary:all:生成兼容包。4.2 第二步创建项目结构清晰即生产力拒绝把所有代码塞进一个.py文件。我的标准结构daily_report/ ├── main.py # 主程序入口 ├── config/ # 配置文件目录 │ ├── __init__.py │ └── settings.py # 所有配置项集中地 ├── utils/ # 工具函数 │ ├── __init__.py │ ├── email_sender.py # 邮件发送核心 │ └── data_loader.py # 数据读取工具 ├── templates/ # 邮件HTML模板 │ └── report.html # Jinja2模板 └── .env # 环境变量含邮箱密码关键细节config/settings.py中所有配置项用大写常量且带类型注解SMTP_SERVER: str smtp.gmail.com SMTP_PORT: int 587 SENDER_EMAIL: str os.getenv(SENDER_EMAIL, ) SENDER_PASSWORD: str os.getenv(SENDER_PASSWORD, ) # 从.env读取 RECIPIENTS: List[str] [bosscompany.com, teamcompany.com].env文件内容务必.gitignoreSENDER_EMAILyour_emailgmail.com SENDER_PASSWORDyour_app_password_here # Gmail需用App Password4.3 第三步编写核心邮件发送模块email_sender.py这不是简单的smtplib封装而是生产级实现import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.header import Header from pathlib import Path from jinja2 import Template def send_daily_report(html_content: str, subject: str 每日数据报告) - bool: 发送HTML格式日报邮件 msg MIMEMultipart(alternative) msg[Subject] Header(subject, utf-8) msg[From] SENDER_EMAIL msg[To] , .join(RECIPIENTS) # HTML正文 part MIMEText(html_content, html, utf-8) msg.attach(part) try: server smtplib.SMTP(SMTP_SERVER, SMTP_PORT, timeout15) server.starttls() # 启用TLS加密 server.login(SENDER_EMAIL, SENDER_PASSWORD) server.send_message(msg) server.quit() logger.info(f邮件发送成功收件人{len(RECIPIENTS)}人) return True except Exception as e: logger.error(f邮件发送失败{e}) return False def render_report_template(data: dict) - str: 用Jinja2渲染HTML模板 template_path Path(__file__).parent.parent / templates / report.html with open(template_path, encodingutf-8) as f: template Template(f.read()) return template.render(**data)注意Gmail要求使用App Password而非账户密码且需在Google账户中开启“两步验证”后生成。这是新手最大雷区务必在README.md中用加粗强调。4.4 第四步组装主流程main.py把所有模块串起来体现“配置驱动”思想from datetime import datetime from utils.email_sender import send_daily_report, render_report_template from utils.data_loader import load_sales_data, load_user_growth from config.settings import REPORT_TITLE def main(): 日报机器人主流程 logger.info(*50) logger.info(f启动日报生成{datetime.now().strftime(%Y-%m-%d %H:%M:%S)}) # 1. 加载数据 sales_data load_sales_data() user_data load_user_growth() # 2. 渲染HTML html_content render_report_template({ date: datetime.now().strftime(%Y年%m月%d日), sales_total: sales_data[total], new_users: user_data[today], growth_rate: user_data[rate], title: REPORT_TITLE }) # 3. 发送邮件 success send_daily_report(html_content, f{REPORT_TITLE} - {datetime.now().strftime(%m/%d)}) logger.info(日报任务结束) return success if __name__ __main__: main()4.5 第五步部署与自动化cron定时任务Windows用户用任务计划程序Linux/macOS用cron# 编辑crontab crontab -e # 每天上午9点执行北京时间 0 1 * * * cd /path/to/daily_report /usr/bin/python3 main.py /var/log/daily_report.log 21实测心得cron中务必写绝对路径/usr/bin/python3而非python3且cd到项目目录。日志重定向比更安全避免覆盖历史记录。5. 常见问题与排查技巧实录那些没人告诉你的“踩坑现场”5.1 问题速查表高频故障与一键修复故障现象根本原因解决方案修复耗时ModuleNotFoundError: No module named openpyxlpip安装时网络中断包损坏pip uninstall openpyxl pip install --no-cache-dir openpyxl1分钟Excel读取后数字变成科学计数法如1.23E08openpyxl默认读取为浮点数在读取后加int(cell.value) if isinstance(cell.value, float) else cell.value30秒异步请求报RuntimeError: Event loop is closed多次调用asyncio.run()全局只用一次asyncio.run(main())内部用asyncio.create_task()2分钟邮件收到后中文显示为?utf-8?b?5L2g5aW9?MIMEText未指定charsetMIMEText(html_content, html, utf-8)中的utf-8不可省略10秒日志文件不生成控制台也不输出logger名称与handler名称不匹配logging.getLogger(my_module)必须与logging.getLogger(my_module)一致1分钟5.2 独家避坑技巧来自十年运维的血泪经验技巧1用pip list --outdated定期检查依赖安全很多“python源码”因requests版本过旧SSL握手失败。我的做法是每月初运行pip list --outdated --formatfreeze | grep -v ^\$ | cut -d -f1 | xargs -n1 pip install -U自动升级所有包。但关键包如openpyxl加白名单避免API变更导致脚本崩溃。技巧2在代码开头强制设置时区datetime.now()在服务器上可能返回UTC时间导致日报日期错一天。我的标准开头import os os.environ[TZ] Asia/Shanghai # 设置时区 import time time.tzset() # 生效然后所有时间操作用datetime.now()不再用datetime.utcnow()。技巧3用__all__控制模块导出在utils/__init__.py中写__all__ [send_daily_report, load_sales_data]这样from utils import *时只导入指定函数避免污染命名空间。这是大型项目可维护性的基石。技巧4为每个脚本写--version参数在argparse中加parser.add_argument(--version, actionversion, version%(prog)s 1.2.0)用户执行python main.py --version就能看到版本方便排查问题时确认环境。5.3 真实故障排查案例某次凌晨3点的邮件发送失败场景日报机器人连续三天未发送日志显示SMTPAuthenticationError。排查路径检查.env文件密码正确无空格检查Gmail账户App Password未过期查看Gmail安全中心发现“最近有不寻常的登录活动”因服务器IP变更被锁定解决方案登录Gmail进入“安全”设置点击“检查最近的安全事件”手动验证服务器IP。教训生产环境必须监控SMTP连接状态。我在后续版本中加入心跳检测def test_smtp_connection(): try: server smtplib.SMTP(SMTP_SERVER, SMTP_PORT, timeout5) server.quit() return True except: logger.critical(SMTP服务器不可达请检查网络或Gmail设置) return False并在main()开头调用失败则直接退出避免浪费资源。5.4 性能瓶颈预警当你的脚本开始变慢不是所有慢都是代码问题。我的监控清单内存泄漏用psutil.Process().memory_info().rss每10分钟记录一次若持续增长检查是否有全局列表不断append文件句柄泄漏lsof -p pid | wc -lLinux下超过1024需警惕检查open()后是否漏了close()DNS解析慢异步请求中若大量域名解析超时改用aiodns库替代默认resolver磁盘IO瓶颈日志写入频繁时用iostat -x 1看%util是否接近100%考虑切换SSD或调整日志轮转策略。最后再分享一个小技巧所有脚本开头加一行#!/usr/bin/env python3然后chmod x main.py之后直接./main.py运行比python main.py少敲6个字符——程序员的幸福就藏在这些微小的确定性里。本文还有配套的精品资源点击获取