写了十年Python,代码从脚本写到框架,从单机写到分布式,才渐渐看清一些东西。语法只是表象,真正决定你写出什么质量的代码,是你对底层逻辑的理解。以下六条,是我踩过无数坑后才真正刻进骨子里的。
一切皆对象,但对象有“协议”
Python里连函数、类、模块都是对象,这谁都知道。可真正理解它,是在你明白对象之间靠“协议”通信之后。len()为什么能作用于字符串、列表、字典?因为背后调用了__len__。for循环为什么能遍历各种容器?因为实现了__iter__和__next__。你写的每一个类,都可以通过实现魔术方法融入Python的整个生态。对象不是数据的容器,而是行为的契约。
可变与不可变,决定了参数传递的真相
Python没有值传递和引用传递之分,只有“传对象引用”。但可变对象和不可变对象的区别,让同样的引用产生截然不同的结果。列表作为默认参数会累积,元组不会。字典的键必须是不可变的,因为哈希值不能变。理解可变性,才能理解为什么你的函数有时会悄悄修改外部数据。
命名空间与作用域,是代码的隐形骨架
LEGB规则——Local、Enclosing、Global、Built-in——背下来容易,用起来难。闭包为什么能记住外部变量?global和nonlocal为什么让人又爱又恨?因为Python的作用域是在编译时确定的,但名字绑定是在运行时发生的。你以为你在读变量,其实你在读一个被层层包裹的引用链。
迭代器与生成器,是惰性求值的艺术
列表推导式会立刻生成全部数据,生成器表达式不会。yield让函数变成状态机,itertools让无限序列成为可能。十年前我写爬虫,一次性把几十万条数据塞进列表,内存爆掉。后来用生成器逐条处理,代码反而更清晰。惰性求值不是优化技巧,而是一种思维方式的转变——从“准备好一切”到“用到才算”。
描述符与属性查找,揭开了类属性的神秘面纱
property、classmethod、staticmethod,甚至普通方法的self绑定,背后都是描述符协议。__get__、__set__、__delete__三个方法,决定了属性访问时到底发生了什么。理解描述符,你才能明白为什么instance.method()会自动传入self,为什么@property能拦截赋值。属性不是数据,而是一段可编程的访问逻辑。
GIL不是缺陷,而是设计选择
全球解释器锁让多线程无法并行执行CPU密集任务,但它保护了CPython的内存管理,让单线程性能足够好,让C扩展开发足够简单。十年里我见过太多人抱怨GIL,却很少人真正用好多进程、异步IO和C扩展。GIL决定了Python的并发模型:CPU密集用多进程,IO密集用异步或线程,计算密集下沉到C。接受它,才能选对工具。
这六条,每一条都曾让我在深夜调试时恍然大悟。它们不写在教程的第一章,却贯穿你写下的每一行代码。语法让你跑起来,底层逻辑让你跑得远。