三年Python开发,我踩过的那些坑和收获

📅 2026/6/27 7:05:04
三年Python开发,我踩过的那些坑和收获
从2018年正式用Python做项目到现在算下来也有快八年时间了。这中间用过Flask写小工具用Django搭过完整的业务后台用Celery做过异步任务调度也用Pandas和NumPy处理过数据分析的需求。不能说精通但确实在这个语言上花了不少功夫。写这篇文章不是想讲什么高深的理论就是想把这些年实际开发中遇到的坑、总结的经验整理一下。如果能帮到正在学Python或者刚入行的朋友那就最好了。关于GIL别太焦虑也别无视刚用Python做Web服务的时候最怕别人问我“Python有GIL性能是不是不行”说实话GIL确实是CPython的一个限制但它并没有那么可怕。对于Web应用来说大部分时间花在I/O等待上——数据库查询、外部API调用、文件读写这些操作都会释放GIL所以多线程在处理I/O密集型任务时并没有想象中那么不堪。真正要注意的是CPU密集型场景。我们曾经有一个图像处理的模块用Python做了大量的像素遍历和矩阵计算跑起来确实很慢。后来换了两种方案——把计算部分用NumPy向量化实现性能提升了十几倍另一个更彻底的方案是用multiprocessing启动多个进程每个进程处理一批数据充分利用多核CPU。所以我的感受是先搞清楚你的场景是什么类型的任务再决定要不要为GIL焦虑。大多数业务系统GIL不是瓶颈如果真的遇到了总归有办法绕过去。装饰器用得好代码少一半Python的装饰器是我用得最多的语言特性之一也是让代码最优雅的工具。以前写Flask接口的时候每个视图函数都要写一堆重复的代码——参数校验、权限检查、日志记录、异常捕获。最开始是每个函数里手写后来发现太冗余就抽成了装饰器。举个例子我们当时有个需求所有接口都要校验请求里的token并且把解析出来的用户信息挂到上下文中。写了一个login_required装饰器往视图函数上一加所有鉴权逻辑就统一了。代码清爽了很多也减少了遗漏校验的风险。还有计时装饰器用来统计接口耗时重试装饰器用来处理外部API偶尔的超时缓存装饰器给某些计算密集的结果加一层本地缓存。装饰器的核心价值在于分离关注点。业务逻辑就是业务逻辑不要掺和鉴权、日志、性能统计这些横切关注点。当然也别滥用三层以上的装饰器嵌套可读性就会急剧下降。生成器和迭代器带来的内存解放有一回处理日志文件单个文件有10个G用readlines()一次性读进来内存直接爆了。后来改成逐行读取pythonwith open(large.log, r) as f: for line in f: process(line)for line in f本质上就是在用迭代器每次只读一行到内存问题迎刃而解。后来写数据处理管道的时候我习惯用生成器函数把每个处理步骤串起来。比如读数据 - 过滤 - 转换 - 聚合每一步都是一个生成器数据像流水一样流过整个管道。这样做的好处是内存占用恒定而且每一步都可以独立测试和复用。如果你在写一个需要处理大量数据的程序优先考虑用生成器来惰性求值而不是一次性加载全部数据。上下文管理器的魔力with open()大概是所有人最早接触的上下文管理器。但除了文件操作它还能干很多事情。我们项目里用Redis做缓存每次操作完要释放连接。最开始总是在try...finally里写释放逻辑代码很丑。后来自定义了一个上下文管理器pythoncontextmanager def redis_client(): client create_redis_client() try: yield client finally: client.close()用起来就变成了pythonwith redis_client() as client: client.set(key, value)连接自动管理再也不用担心忘记释放了。数据库事务也可以用同样的方式封装with transaction()自动处理commit和rollback。这个小工具让代码的健壮性和可读性都提升了不少。异常处理不要沉默新人常犯的一个错误是pythontry: do_something() except Exception: pass这种写法把异常悄无声息地吞掉了。一旦出问题查都不知道从哪查起。更好的做法是pythontry: do_something() except SpecificError as e: logger.error(f处理失败: {e}, exc_infoTrue) # 要么重新抛出要么返回一个合理的默认值原则有三个只捕获你预期会发生的异常类型记录足够的上下文信息不要让程序在未知状态下继续执行。我们线上有一次严重的事故就是一个空值的except Exception把关键的报错信息吞了导致问题被掩盖了整整两天才发现。从那以后Code Review对异常处理格外严格。类型注解带来的变化Python 3.5引入类型注解的时候我一开始是抵触的——写Python不就图个灵活嘛加类型注解不是自缚手脚后来团队扩大到十几个人协作成本变高了经常出现“这个函数到底要传什么类型、返回什么类型”的问题。文档写了也没人看不如用类型注解把接口契约写在代码里。再配合上Pydantic做数据校验用Mypy做静态检查很多低级错误在运行前就能发现。尤其是重构的时候改了某个函数的签名Mypy会把所有调用处标出来省了很多排查时间。类型注解不是让你写Java风格的Python而是让代码更可读、更可靠。要不要用完全取决于项目规模和团队协作需求小脚本没必要硬上。用好标准库Python的标准库真的很强大很多时候不用急着上第三方包。collections.defaultdict和Counter让字典操作更简洁itertools里的各种迭代器工具处理数据流非常高效functools.lru_cache一句话给函数加上缓存dataclasses省去写大量__init__的样板代码pathlib路径操作比os.path优雅太多了花了点时间把标准库过一遍你会发现很多日常需求其实都有现成的解决方案代码更稳定也减少了依赖。写测试的习惯以前觉得写测试是浪费时间直到有一次上线前改了一行代码导致一个很边缘的逻辑出了bug线上跑了三天才被发现损失了不少数据。后来强制要求每个新功能必须带测试核心模块的覆盖率不低于80%。用pytest写起来其实很快再加上pytest-cov看覆盖率心里踏实很多。CI/CD流水线里把测试和覆盖率检查加上覆盖率不达标不允许合并。标准定下来之后线上故障率确实肉眼可见地下降了。最后想说的话Python是一门很好上手的语言但上手快和用好是两码事。这些年我越来越觉得语言本身只占20%剩下的80%是对业务的理解、对工程化的坚持、以及对代码可维护性的追求。如果你刚入门不用急着学各种花哨的框架先把基础打牢——搞懂可变对象和不可变对象的区别、弄明白浅拷贝和深拷贝、搞清楚is和的差异。这些细节看似琐碎但往往是线上bug的根源。写代码是为了解决问题不是为了炫技。优雅、简洁、可读、可靠这些才是一段好代码应该具备的品质。共勉。