如题, 我们编写以下代码, 请问它的输出是什么呢?

this.py
1
2
import this
print("Hello World!")

标准库的this模块

this模块是Python标准库的一部分, 其实是Python的一个小彩蛋, 当你导入时会显示一首 Python之禅 的英文诗, 阐述了Python的设计与编程哲学.

其中的编写代码也很有趣, 使用了key为13的凯撒加密, 感兴趣的可以自行研究, The Zen of Python 的原文将放置在文章末尾.

这里代码的结果

实际上, 运行结果当然不是这首小诗, 而是:

1
2
Hello World!
Hello World!

那么, 为什么会出现两次 Hello World 呢? 这就得从 Python 的导入规则讲起.

Python 的模块导入规则

首先, 我们可以去查询一下文档.

Python的导入机制工作原理如下:

  • 导入语句:当你使用import语句时,Python会执行两个操作:搜索指定的模块,然后将搜索结果绑定到本地作用域的一个名称上。
  • 模块搜索:Python会在一系列预定义的位置搜索模块,这些位置包括内置模块、sys.path中的目录以及包。
  • 模块缓存:如果模块已经被导入,Python会使用sys.modules缓存中的模块,而不是重新加载模块。
  • 模块执行:一旦找到并加载了模块,模块中的所有顶层代码都会被执行。

原理解析

import this

运行第一行时, 实际上先进行模块搜索, 具体原理在文档的这里.

Python includes a number of default finders and importers. The first one knows how to locate built-in modules, and the second knows how to locate frozen modules. A third default finder searches an import path for modules. The import path is a list of locations that may name file system paths or zip files. It can also be extended to search for any locatable resource, such as those identified by URLs.

也就是说, 搜索顺序是:

  • 内置模块
  • Frozen Modules (我也不清楚怎么翻译, 大概就是被打包好的可执行模块)
  • sys.path 中的目录
    • Python 执行的入口文件所在的路径
    • 系统的环境变量 $PYTHONPATH 所表示的目录
    • site 路径,也就是 python3.5/Lib/site-packages 这种。

那么, 问题来了, this 难道不是一个内置模块吗? 为什么还要搜索 sys.path , 最终定位到了文件自身呢?

这时, 我们就应该验证一下, 打开 Python 解释器, 输出一下 timethis 看看.

1
2
3
4
>>> time
<module 'time' (built-in)>
>>> this
<module 'this' from 'C:\\Users\\Gang\\AppData\\Local\\Programs\\Python\\Python311\\Lib\\this.py'>

Built-in Modules 与 Standard Library

所以, 这里就涉及到一些概念的区分了, 我们查看标准库文档可以看到:

Python’s standard library is very extensive, offering a wide range of facilities as indicated by the long table of contents listed below. The library contains built-in modules (written in C) that provide access to system functionality such as file I/O that would otherwise be inaccessible to Python programmers, as well as modules written in Python that provide standardized solutions for many problems that occur in everyday programming.

也就是说只有内置模块是用C语言编写的, 而标准库中的其他模块则是由 Python 编写的, 正因如此, this并不是一个内置模块, 而是由 Python 编写的标准库模块. 从而导致 this 最终被定位到了文件自身.

递归

写过递归函数的朋友都知道, 像下面这样的函数是会达到 Python 的递归深度限制的:

1
2
def hello():
hello()

那么为什么没有反复导入模块本身造成无限循环呢?

其实在上面的搜索过程中还有一点没有说, 那就是查找最初的开始地点是模块缓存, 也就是说每次导入时 Python 会先检查 sys.modules 是否已经存在该模块, 存在则直接返回, 不会再次导入.

这样就能解释为什么只有两个 Hello World了, 因为第一个是导入模块自身时, 再次运行到import由于检查到缓存从而运行到第二行的 Hello World, 第二个则是运行模块自身时的输出.

小结

写这么多, 其实还是想提醒大家一件事, 那就是记得不要把文件名给命名成要用到的 Python 模块名了😂😂😂

The Zen of Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

给一个 Copilot 的翻译版本:

Python之禅
作者:Tim Peters

优美胜于丑陋。 显式优于隐式。
简单优于复杂。 复杂总比复杂好。
扁平优于嵌套。 稀疏优于密集。
可读性很重要。 特殊情况不足以打破规则。
尽管实用性胜过纯洁性。 错误不应该默默过去。
除非明确沉默。 现在做总比不做好。
虽然从不做往往比马上做好。
如果实现难以解释,那是个坏主意。
如果实现易于解释,可能是个好主意。
命名空间是个好主意——让我们做更多这样的事!