Python 内置机制、模块与文档字符串
系统整理 Python 内置命名空间、assert、文档字符串、模块与 __name__。
建议先了解
- Python 基础语法
1. 这篇笔记解决什么问题
本篇整理 Python 中几个容易混淆但非常基础的机制:builtins、内置常量、assert、注释与 docstring、源文件编码声明、模块与 __name__。
学完后应能回答:为什么 print 不需要导入;Python 有没有真正的常量;assert 应该检查什么;为什么三引号不是多行注释;一个 .py 文件与 module 是什么关系;__name__ == "__main__" 到底在判断什么。
2. 前置知识
- Python 变量、函数、类的基础语法。
import的基本用法。
待补充:LEGB 名字解析、import system 的完整实现。
3. 核心概念
3.1 内置命名空间(Built-in Namespace)
以下名字通常可以直接使用:
print("hello")
len([1, 2, 3])
int("123")
list()
dict()
它们来自 builtins 模块:
import builtins
builtins.print("hello")
名字查找常用 LEGB 模型理解:
Local → Enclosing → Global → Builtins
3.2 Python 的“常量”
Python 没有用户可声明的通用运行时 const 机制。
PI = 3.1415926
PI = 100 # 仍然合法
全大写只是约定。typing.Final 可辅助静态检查:
from typing import Final
PI: Final = 3.1415926
但它不是运行时强制不可重新绑定。
常见内置常量包括:
True
False
None
NotImplemented
Ellipsis
__debug__
3.3 assert
assert 表达的是:
按照程序设计,这个条件在这里必须成立;若不成立,通常说明内部逻辑有 bug。
例如:
assert hidden_size % num_heads == 0
assert 0 <= probability <= 1
它适合检查内部不变量,不适合承担权限、密码、业务规则、外部输入校验。
普通运行时 __debug__ 通常为 True;使用 python -O 时会进入优化模式,assert 不应被依赖为程序正常逻辑的一部分。
3.4 注释与文档字符串(Docstring)
Python 真正的注释语法只有:
# comment
Python 没有 C 风格 /* ... */ 多行注释。
三引号:
"""
hello
world
"""
本质是字符串字面量,不是注释。
若字符串字面量出现在模块、类、函数或方法主体的第一条语句位置,则会成为 docstring:
def add(a, b):
"""Return the sum of a and b."""
return a + b
可读取:
print(add.__doc__)
help(add)
3.5 文件编码声明
# coding: utf-8
或:
# -*- coding: utf-8 -*-
描述的是 .py 源文件字节如何解码为源码字符,必须位于文件前两行。
它与运行时:
"你好".encode("utf-8")
不是同一阶段。前者是“读源码”,后者是“运行时 str → bytes”。
3.6 模块(Module)
普通 .py 文件通常是一个模块的源码载体:
project/
├── main.py
└── math_utils.py
import math_utils
print(type(math_utils))
# <class 'module'>
严格来说 module 不一定来自 .py:也可能是内置模块或 C 扩展模块。
3.7 __name__
__name__ 可以理解为模块的一个重要属性。
直接执行:
python a.py
此时:
__name__ == "__main__"
被导入:
import a
则 a.py 中的 __name__ 通常为 "a";包内模块可能为 "pkg.mod"。
经典入口:
def main():
...
if __name__ == "__main__":
main()
含义是:只在该模块作为入口直接执行时运行 main()。
4. 直观理解
builtins 可以理解为名字解析的最后一层公共名字空间;assert 是开发者给内部逻辑安装的“早期报警器”;docstring 是“被 Python 保存的说明字符串”;__name__ 则记录模块当前以什么身份被加载。
这些机制看起来都像“特殊语法”,但实际上处于不同层次:名字解析、运行时自检、源码解析、模块元数据。
5. 工作原理
5.1 三引号字符串的三种情况
"""abc"""
│
├── 赋值给变量 → 普通字符串
├── 位于 module/class/function 开头 → docstring
└── 单独放在其他位置 → 无用字符串表达式
5.2 __name__ 的两个典型状态
文件 a.py
│
├── 直接运行 → 模块名 __main__
└── 被 import → 模块名 a(或完整限定名)
6. 示例
示例 1:assert 适合内部不变量
hidden_size = 768
num_heads = 12
assert hidden_size % num_heads == 0
这里条件失败通常意味着模型配置或程序逻辑不满足自身设计。
示例 2:用户输入不要依赖 assert
age = int(input("age: "))
if not 0 <= age <= 150:
raise ValueError("invalid age")
外部输入非法是一种正常可能出现的运行情况。
7. 代码、命令或公式
import builtins
print(dir(builtins))
python script.py
python -O script.py
python -OO script.py
[!WARNING]
-O/-OO的精确优化行为和版本细节应以对应 Python 官方文档再次核验,不应把本次对话中的描述当成跨版本实现保证。
8. 容易混淆的概念
| 概念 A | 概念 B | 核心区别 |
|---|---|---|
| 大写变量 | 真正 const | 大写只是约定 |
typing.Final | 运行时不可修改 | Final 主要服务静态检查 |
# | """...""" | 前者是真注释,后者是字符串 |
| docstring | 普通字符串 | docstring 是出现在特殊首语句位置的字符串 |
| 源文件编码 | str.encode() | 前者处理源码 bytes,后者处理运行时字符串 |
.py 文件 | module 对象 | 前者是源码载体,后者是运行时对象 |
__main__ | main() | 前者是入口模块名,后者只是普通函数命名习惯 |
9. 常见误区
误区:Python 有真正的三引号多行注释
错误原因:单独字符串表达式通常没有可见效果。
正确理解:三引号始终产生字符串字面量;只有 # 是注释语法。
如何验证:把它放在函数第一行,再查看 func.__doc__。
误区:assert 是通用输入范围校验
错误原因:混淆内部不变量与外部错误。
正确理解:内部“不应该失败”的假设适合 assert;业务和安全校验使用显式条件与异常。
如何验证:比较普通运行和优化运行。
误区:一个 .py 文件在任何意义下都严格等于 module
错误原因:忽略内置模块与扩展模块。
正确理解:普通 .py 文件通常对应模块源码,但 module 是更广的运行时概念。
10. 与其他知识的关系
11. 可以亲手完成的验证
实验目标:验证 __name__、docstring 和 assert。
- 创建
a.py,打印__name__; - 直接运行,再从
b.py导入; - 给函数加入 docstring,打印
.__doc__; - 加入一个失败的
assert; - 分别用普通模式与
-O运行。
预期:__name__ 随加载身份变化;docstring 可读取;assert 不应作为业务逻辑依赖。
实验不能证明:不同 Python 实现的底层加载和优化实现完全相同。
12. 尚未解决的问题
- import system 的查找、加载、缓存流程未展开。
sys.modules的角色未展开。- builtins namespace 在 CPython frame 中如何连接尚未展开。
- docstring 在
-OO下的精确版本行为需要官方文档核验。
13. 自测问题
- 为什么
print()不需要import builtins? PI = 3.14为什么不是真正语言层面的常量?- 什么条件适合使用
assert? - 为什么
"""..."""不是多行注释? # coding: utf-8与.encode("utf-8")有什么区别?- 直接执行模块和导入模块时,
__name__分别是什么?
参考答案
- 因为 builtins 名字位于 built-in namespace,名字解析可直接找到。
- Python 没有用户可声明的通用运行时 const 机制,大写只是约定。
- 程序内部设计上必须成立、失败通常意味着 bug 的条件。
- 因为它始终是字符串字面量,只是在某些位置会成为 docstring。
- 前者决定源码文件 bytes 如何解码,后者把运行时
str编码为bytes。 - 直接执行为
__main__;被导入时通常为模块名或限定模块名。
14. 一句话总结
Python 的 builtins、assert、docstring、编码声明和 __name__ 分别属于名字解析、自检、文档元数据、源码解码和模块身份机制;它们应按语义层次区分,而不是都当成“特殊语法”。