服务需持续运行并响应请求,不同于单次执行脚本;须用专业服务器(如uvicorn)、正确处理信号、规范日志、管理全局资源与依赖生命周期。

Python 脚本执行完就退出,服务必须持续响应请求
脚本是单次任务:读文件、算数据、写结果、sys.exit() —— 进程结束,资源全释放。服务不是这样:它启动后得一直占着端口、监听连接、处理并发请求,哪怕暂时没流量也不能退。
常见错误现象:ConnectionRefusedError: [Errno 111](连不上)、Address already in use(端口被占)、进程启动后立刻消失(没加阻塞逻辑)。
- 用
while True:+time.sleep(1)模拟“不退出”?不行 —— 它不响应网络请求,只是空转,CPU 白耗,且无法优雅停止 - 真正服务要依赖框架的事件循环或 WSGI/ASGI 服务器(如
uvicorn、gunicorn),不是靠自己轮询 - 脚本里调
requests.get("http://localhost:8000")测试服务?得先确认服务已启动并完成绑定,否则容易因竞态失败
信号处理和进程生命周期管理完全不同
脚本收到 SIGINT(Ctrl+C)通常直接终止;服务必须能捕获 SIGHUP、SIGTERM,做清理(关数据库连接、刷缓存、保存状态),再退出。
使用场景:用 systemd 管理 Python 服务时,若没处理 SIGTERM,systemctl stop 会超时后强制 kill,导致数据丢失。
立即学习“Python免费学习笔记(深入)”;
- 用
signal.signal(signal.SIGTERM, cleanup_handler)注册清理函数,但注意:多线程下信号只发给主线程 - 异步服务(如
asyncio)要用asyncio.create_task()启动清理协程,不能直接在信号回调里 await -
atexit.register()在服务中基本无效 —— 它只在正常退出时触发,而SIGKILL或 OOM killer 下完全不执行
日志、错误、标准输出的行为差异极大
脚本的 print() 直接输出到终端;服务跑在后台时,stdout 和 stderr 若没重定向,内容会丢失或混入系统日志,排查问题时根本找不到。
性能影响:频繁 print() 在高并发服务里会成为 I/O 瓶颈,尤其当 stdout 是行缓冲但实际未 flush 时,日志延迟严重。
- 服务必须用
logging模块,配置handlers写入文件或 syslog,禁用裸print() - 日志级别别全开
DEBUG—— 在生产环境,一次请求打 50 行日志会让磁盘 I/O 拉满 - 未捕获异常在脚本里会打印 traceback 并退出;在服务里可能只静默失败,请求卡住或返回 500,但进程还在跑 —— 必须配全局异常处理器(如 Flask 的
@app.errorhandler(Exception))
环境隔离与依赖加载时机决定稳定性
脚本每次运行都重新 import 全部模块,出错就停;服务启动时加载一次依赖,之后所有请求共享同一份内存中的对象 —— 全局变量、单例、数据库连接池都是“活”的,改了代码不重启就无效。
容易踩的坑:import 时做了耗时操作(如读大文件、连远程 API),会导致服务启动慢甚至超时失败;或者模块内初始化了不可复用的对象(如未设 timeout 的 requests.Session)。
- 把重操作移到请求处理函数里(如按需建 HTTP client),或用懒加载模式(
if not hasattr(g, 'db')) - 避免在模块顶层执行
os.chdir()、os.environ.update()—— 会影响后续所有请求的路径和环境变量 - 用
venv隔离依赖是基础,但服务还要注意:升级包后必须重启进程,热重载(如watchdog)在生产环境极不稳定
最常被忽略的是:服务不是“脚本加个 while 循环”,而是对资源生命周期、错误传播路径、外部交互节奏的重新建模。一个 time.sleep() 看似简单,背后牵扯的是信号安全、线程模型、监控可观测性 —— 这些不厘清,服务上线后的问题只会更隐蔽。










