读懂 Python,才能更 Pythonic

花了这么多功夫,我终于完成了 Python 源码剖析这个系列,而您也在源码学习中加深了对 Python 虚拟机的了解。我们一起完成了一件不可思议的事情,在此先为自己鼓个掌!

工欲善其事,必先利其器。想要将某个工具用好,先要充分了解它的结构、特性,才能在使用时扬长避短。这也是我们一起研究 Python 源码的初衷——充分理解 Python 虚拟机,力求将 Python 发挥得淋漓尽致!

摆脱误区

Python 是一门非常容易上手的语言,大家都自信满满,觉得它没什么高深的地方,根本无须深入学习。然而,正是这种盲目自信和无知的心态,使我们止步不前,甚至还导致非常严重的后果。

我工作早年间,曾经接到一个 Python 程序优化任务。这是一个用 Python 2 编写的的程序,我在优化的过程中,发现了一些令人哭笑不得的误用。举个例子,程序内部有一个用于保存主机信息的字典,以主机 IP 为键( key ):

1
2
# hosts是一个以IP为键的主机信息字典
hosts = {}

然后,程序在处理数据时,这样判断一个 IP 是否在字典里:

1
2
3
for ip in ips:
	if ip in hosts.keys():
    	# do something

没用过 Python 2 的童鞋可能不清楚,我稍微解释一下。在 Python 2 ,字典 keys 方法以列表( list )的形式返回字典所有 key 。这是一个典型 O(N) 操作,需要创建列表对象,而且涉及大量的 key 拷贝。

接下来,判断一个 IP 是否在列表内也是一个 O(N) 操作!字典存在的最大意义,就是能够在 O(1) 常数时间内找到与给定 key 关联的 value 。直接用 in 操作符在 O(1) 时间内完成字典 key 存在性判断不香吗?

1
2
3
for ip in ips:
    if ip in hosts:
        # do something

虽然程序最后发现的问题不止这个,但这行糟糕的代码也为拖垮程序出了不少力呀。如果对 Python 虚拟机的特性,特别是对 Python 内建对象都了然于胸,还会犯这种低级错误吗?

作为研发,不应该沦为一个需求翻译师,机械地将需求翻译成程序代码。尝试着以计算机的思维来思考问题,并从中体会计算机系统和软件设计的美妙之处。对未知的世界要保持好奇心,不断探索,扩展自己的能力范围。

成就总结

正是为了摆脱误区,进阶更高级 Python 研发,我们走到一起研读 Python 虚拟机源码。经过这段时间的源码研究,我们更加清晰地了解了 Python 虚拟机的运行机制,并从中学到不少编程技巧。

对象模型 部分,我们一起总结了 Python 的对象体系,明确了实例对象与类型对象的区别和联系,为进一步学习打下了坚实的基础。

内建对象 部分,我们一起研究了 Python 常用内建对象的底层实现,并讨论了关键操作的执行效率。任何负责任的程序员,都应该时刻关注执行效率,时刻关注底层数据结构。

诚然,Python 对常用数据结构进行封装,提供了更加高级的容器对象。不少 Python 程序员习惯于操作容器对象,对底层数据结构已经失去敏感。经常有初学者提问,为什么 Python 不提供栈?然而,如果你对 list 对象内部动态数组结构足够熟悉,就不会如此发问了。动态数组的结构决定了,我们可以将 list 当做栈来使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
>>> stack = []

# 入栈
>>> stack.append(1)
>>> stack.append(2)
>>> stack.append(3)

# 出栈
>>> stack.pop()
3
>>> stack.pop()
2
>>> stack.pop()
1

虚拟机 部分,我们一起研究了 Python 字节码以及虚拟机执行字节码的基本机制。任何令人费解的问题,均可在字节码中找到答案。另外,正确认识全局解释器锁( GIL )对线程的负面影响后,我们找到了通过多进程提升计算密集型程序执行效率的技巧。

函数机制 部分,我们彻底弄清了函数对象从创建到调用的所有来龙去脉,掌握了创建函数对象的 tricky 方法。我们还一起研究了装饰器若干高级用法,合理应用装饰器技法,使代码更优雅,更 Pythonic

类机制 部分,我们一起见证了类对象及实例对象诞生的过程,并充分理解类继承机制。面向对象是久经考验的编程思想,善用面向对象理念对优化程序代码逻辑很有帮助。魔术对象提供了一个模仿 Python 数据模型的途径,让自定义类自然而然地契合 Python 的运行哲学。此外,我们还研究了元类的原理,它在框架设计中威力强大。

协程 部分,我们先考察了生成器的行为,并从字节码出发研究它的执行机制。明白生成器的运行机制后,再结合 IO 多路复用技术,协程就很好理解了。asyncio 已被 Python 纳入标准库,是践行协程式应用开发的不二选择。

内存管理机制 部分,我们研究了 Python 内存池和垃圾回收机制的实现思路。虽然这些知识在实际项目中用处不大,却对提升编程视野意义非凡。学习切忌功利化,所谓开卷有益,在每一天的点点滴滴中厚积薄发。

精益求精

不管在开发还是在学习过程中,都应该保持一种 精进 精神——不满足于现状,精益求精,将每件事情都做到极致。只有保持力争上游的干劲,才能不断进步。每天只需进步一点点,日积月累,终将蜕变。

就算是非常小的细节,也可能大有学问,不应该被忽略。仔细推敲一下,这样细微的代码片,它是否还有优化的空间呢?

1
2
3
4
5
6
# hosts是一个以IP为键的主机信息字典
hosts = {}

for ip in hosts:
    info = hosts[ip]
    # do something with ip and info

先遍历字典每个 key ,然后再通过下标操作取出对应的 value ,这样的写法一点也不 Pythonic 。洞悉字典的本质后,不禁思考:遍历字典时能否同时拿到 keyvalue 呢?很自然就引出更简练的写法:

1
2
for ip, host in hosts.items():
    # do something with ip and info

另一个例子,初学者经常使用 range 函数来生成连续序号,来达到遍历列表的目的:

1
2
3
4
5
# hosts是一个主机信息列表
hosts = []

for i in range(len(hosts)):
    hosts[i] = process(hosts[i])

同样,这是一段可行,但不那么 Pythonic 的代码。借助 Python 内建函数 enumerate ,在遍历 list 元素的同时,可轻松获得元素下标,简洁而优雅:

1
2
for i, host in enumerate(hosts):
    hosts[i] = process(host)

就算 Python 没有提供 enumerate 内建函数,我们也可以将它“发明”出来:

1
2
3
4
5
6
7
8
def my_enumerate(iterable):
    i = 0
    for x in iterable:
        yield i, x
        i += 1

for i, host in my_enumerate(hosts):
    hosts[i] = process(host)

每打下一段代码,不忘问问自己:我对这段代码满意吗?它还有优化空间吗?还有更优雅更高效的写法吗?最佳实践是什么?然后尝试着寻找问题的答案,假以时日,成功可期。

再次出发

在学习过程中,我们对 Python 虚拟机有了一个全面的认识,收获满满。但这并不意味着 Python 虚拟机学习已经画上句号。实际上,由于篇幅所限,不少细节都没来得及展开,等着您到源码中一探究竟。关于源码的任何疑问,均可到交流群( 278378402 )中讨论。

除了虚拟机,Python 还有很多进阶内容在等着我们。况且,学习是没有终点的,学完 Python 虚拟机,还可以研究 操作系统 ,研究 网络协议 ,研究 数据库研究分布式 系统……

如果您想更深入地了解 Python 这门语言,可以接着阅读一些进阶书籍,例如:

  • 《流畅的Python》
  • 《Python Tricks》
  • 《Effective Python》
  • etc

编程语言其实只是一个工具,洞悉计算机系统与计算机程序的本质,才是做好软件开发的关键。想要在程序开发的道路上走得更远,计算机科学的这几门基础课程必须烂熟于胸:

  • 数据结构与算法
  • 操作系统
  • 网络协议
  • 数据库
  • 编译原理

它们就像武侠小说中的绝世武功,专注于内功修炼;一旦参透,提升突飞猛进!认识到计算机基础的重要作用之后,你没理由不心动。那就赶紧行动起来吧!这里再推荐几本非常经典的书:

  • 《数据结构与算法分析》
  • 《深入理解计算机系统》
  • 《Unix环境高级编程》
  • 《Linux内核设计与实现》
  • 《TCP/IP详解(卷一)》
  • 《高性能MySQL》
  • etc

这些都是名声在外的经典名作,全部吃透后必有脱胎换骨的感觉。美中不足的是,这些书目还是比较难啃的,学起来相对来说会有些枯燥,难以坚持。因此,这也是我下一步创作努力的方向。

接下来,我会从这几个话题中选出一个来写。结合实际项目和例子,采用形象的图表和通俗的语言,力求将原本枯燥的基础知识讲明白。大家有什么好的建议或者比较感兴趣的话题,可以私我。

时代发展越来越快,科技进步日新月异。在这样的背景下,我们更不能停下学习的脚本。只有保持学习的习惯,才能不断更新知识面,不至于被历史的车轮远远抛在后面。让我们重新出发,一起学习!

感恩有您

这个专栏对我来说,是一次全新的尝试。从选题、列提纲、源码梳理、画图、撰写到最终成品,前前后后经历了一年的时间。创作过程中,遇到的困难一个接一个,曾无数次想要放弃。

好在,您我因文字结下了不解之缘。正是这段缘分,不断鼓励着我继续坚持。从催更的留言中,我仿佛看到你们期盼的眼神。在周末的早晨,与懒床之魔的搏斗殊为不易。但只要看一眼充满温情的留言,我便瞬间充满斗志。

时至今日,在你们的陪伴下,我完成了这个近十万字的作品。每次更新前,我虽校对再三,疏漏也在所难免。是你们的宽容,让我放下沉重的包袱。许多读者非常耐心地为我指出错别字和代码上的纰漏,令我颇为感动。

与其说是我一个在创作,不如说是我们一起在结伴学习。您指出的错误,让我及时修正认知误区;您提出的建议,让我发现写作的新思路;您提出的疑问,带来思维的碰撞,引人深思。如此种种,专栏虽以我为名,实则属于大家。

是时候说再见了,但我相信我们很快就会再次见面!

末了,祝我亲爱的读者们都有一个美好的前程!

如果觉得我写得还行,记得点赞关注哟~

洞悉 Python 虚拟机运行机制,探索高效程序设计之道!

到底如何才能提升我的 Python 开发水平,向更高一级的岗位迈进呢? 如果你有这些问题或者疑惑,请订阅我们的专栏 Python源码深度剖析 ,阅读更多章节:

【Python源码剖析】系列文章首发于公众号【小菜学编程】,敬请关注: