Google Python 编码规范 01:从变量命名开始,读懂规则、含义与边界
从 snake_case 出发,逐步解释变量名、常量、单位、数量、内置名称遮蔽和下划线;区分 Google 规则、Python 机制与实践建议。
01 · 先建立判断标准:能运行、合规范、说得清,是三件事
这是 Google Python 风格指南系列的第 1 篇,先讲变量名。本文把证据分成三类:Google 规则摘要、Python 语言机制、本文实践建议。实践建议中的布尔前缀、单位后缀等写法,不冒充 Google 的逐条硬性要求。
阅读入口是你提供的中文目录;规则核对采用 Google 英文原文第 3.16 节。核对日期:2026-10-14。中文页面与英文页面可能不同步,例如本次英文原文另有 3.16.5 Mathematical Notation,中文命名章节未列出对应小节。
风格指南约束采用它的项目;它不是 Python 解释器对所有程序的统一语法要求。维护旧接口时也要考虑兼容性,不能只为换名字而让调用者失效。来源与核验说明 ↓
02 · Google 对变量命名的要求,先看这张简表
Google 规则摘要:名称要有描述性;避免陌生、含糊或删字母式缩写;避免不必要地把容器类型写进名字。常用变量形式如下,示例由本文重新编写。
| 对象或用途 | 命名形式 | 本文示例 |
|---|---|---|
| 局部变量、函数参数 | lower_with_under | failed_case_count、timeout_s |
| 全局变量、类变量、实例变量 | lower_with_under | active_sessions、self.device_id |
| 模块或类的常量 | CAPS_WITH_UNDER | MAX_ATTEMPT_COUNT |
| 内部变量或内部常量 | 相应形式前加 _ | _cached_status、_DEFAULT_TIMEOUT_S |
单字母名称通常应避免,但短范围的计数器、迭代变量、异常、文件句柄等有例外;有明确来源的数学记号也有专门例外。不要自创首尾双下划线名称;类中的双前导下划线会触发名称改写,Google 更推荐单前导下划线。详见英文原文 3.16.1—3.16.5。
怎么理解:failed_case_count 的三个词分别告诉读者“失败的”“用例”“数量”。下划线负责分词,真正的信息来自词义。仅把 abc 改成 a_b_c,并不会让含义更清楚。
03 · userId 为什么能运行,class 为什么不能当普通变量名?
Python 机制:在 ASCII 范围内,名称可由字母、数字和下划线组成,但不能以数字开头;合法的 Unicode 字符也能构成名称。大小写有区别。硬关键字不能作为普通标识符。
| 写法 | 语法判断 | 本篇的风格判断 |
|---|---|---|
user_id | 可作普通变量名 | 采用 |
userId / UserId | 可作普通变量名 | 普通变量优先改为 snake_case |
2_users | 不能作变量名 | 不是风格问题,而是语法问题 |
case-count | 不能作为一个变量名;其中的连字符会被当作减号 | 改为 case_count |
class | 是硬关键字,不能作普通变量名 | 可选用更明确的名称,如 test_class |
请求数 | 可以构成合法变量名 | 跨团队代码中,本文建议采用共享的英文术语 |
下面依次检查“是否具有标识符形式”和“是否为硬关键字”。因此 class 会得到 True True,仍不能写成 class = 1。检查名字不能代替解析完整代码;软关键字还与具体语法位置有关。预期输出见下表对应的布尔值,可直接运行核对。
import keyword
names = ['user_id', 'userId', '2_users', 'case-count', 'class', '请求数']
for name in names:
print(name, name.isidentifier(), keyword.iskeyword(name))04 · 名字绑定对象:改名、复制和修改内容有什么区别?
Python 机制:把 failed_cases 想成当前作用域中指向某个对象的名字。赋值给另一个名字,本身不会复制列表。这个区别也帮助我们理解,为什么名字应当描述用途,而不能代替对对象行为的理解。
下面前三步是:建立一个列表;让 retry_candidates 也绑定它;通过后者追加元素。两个名字仍对应同一个列表,所以第一行输出 ['case_a', 'case_b'],第二行是 True。最后重新赋值只改变 retry_candidates 的绑定,不会清空原列表;最后两行分别为原列表和 []。
本文建议:如果这里只是为了重试而给失败列表起别名,先明确你希望共享修改还是独立处理。一个更好听的名字不会自动建立数据副本。这个机制可与已有的函数调用与局部绑定笔记一起复盘。
failed_cases = ['case_a']
retry_candidates = failed_cases
retry_candidates.append('case_b')
print(failed_cases)
print(retry_candidates is failed_cases)
retry_candidates = []
print(failed_cases)
print(retry_candidates)05 · 实践建议:把对象、单位和统计口径说清楚
本文实践建议:先问“这个值是什么”,再问“用什么单位、什么统计方式”。下面这些改名要以实际含义为前提,不能看到一个数就随意补上单位。
| 信息不足 | 明确语义后的名称 | 需要确认的事实 |
|---|---|---|
timeout | read_timeout_s | 哪一步的超时?接口接收秒还是毫秒? |
size | block_size_bytes | 块大小还是文件大小?单位是否为字节? |
latency | p99_read_latency_ms | 单次值、均值还是分位数?统计窗口另行记录。 |
time | elapsed_s 或 started_at | 经过的时长与一个时间点应分开。时间点还要说明时钟或时区。 |
count | failed_case_count | 数的是用例、失败断言、重试记录,还是故障单? |
注意:重命名不会完成单位换算。把数值 250 从 timeout_ms 原样赋给 timeout_s,并不会把它变成 0.25。下例显式除以 1000 后才得到 0.25。接口若接收秒,就应传入换算后的值。
read_timeout_ms = 250
read_timeout_s = read_timeout_ms / 1000
print(read_timeout_ms)
print(read_timeout_s)06 · 实践建议:一个对象、一组对象、数量和映射,分别命名
试着只看名字回答:“我下一步会遍历它,还是拿它参与加法?”名字不必包含完整类型系统,但应帮助读者形成合理预期。
| 数据角色 | 本文建议的名称 | 阅读时形成的预期 |
|---|---|---|
| 一个用例标识 | case_id | 代表一个用例 |
| 一组用例标识 | case_ids | 可以逐个处理的标识集合或序列;名称不承诺它一定是 list |
| 失败用例的数量 | failed_case_count | 计数,而不是失败用例本身 |
| 用例标识到状态的对应关系 | case_id_to_status | 映射方向是 ID → 状态 |
| 按到达顺序等待处理的任务 | pending_case_queue | queue 在这里描述处理角色;是否需要保留该词取决于实际接口 |
Google 反对的是不必要的类型信息。把字典从一种实现换为另一种映射后,case_id_to_status 仍表达同一业务关系;case_id_to_status_dict 可能只增加了实现细节。反过来,某个术语若能说明重要行为,也不能机械地删掉。
data、result、value 并非任何地方都不能用。如果前后两行就能看懂含义,它们可能足够;一旦同时处理多种结果,就需要区分 read_result、validation_result 等实际角色。
07 · 实践建议:让布尔值读起来像一个明确判断
布尔变量通常要在 if 中阅读。本文建议根据业务语义使用 is_、has_、can_ 等形式;这不是 Google 第 3.16 节规定的统一必选前缀。
| 名称 | 读出来的意思 | 需要避免的歧义 |
|---|---|---|
is_connected | 当前是否已连接 | 不要与“能否尝试连接”混用 |
has_failed_cases | 是否存在失败用例 | 不要让它同时表示失败数量 |
can_retry | 当前策略是否允许重试 | 它不保证重试一定成功 |
retry_enabled | 重试功能是否启用 | 与此刻是否还有重试额度是两件事 |
disable_retry = False 合法,也不一定该改;但读 if not disable_retry 时需要处理两次否定。设计新接口时可以考虑正向表述。维护现有接口时,应先查调用者,不直接改变参数名或语义。
08 · i、j、f、e 到底能不能用?
可以在合适的语境中使用。Google 列出了计数器或迭代变量、异常标识、with 中的文件句柄等例外,并强调名称应与可见范围相称;有来源的数学记号另有说明。因此“Google 禁止所有单字母变量”不准确。
本文实践建议:比较下面两种阅读任务。一个三行循环里的 i,你很容易知道它是索引;同一个函数里嵌套设备、卷和用例三层循环,再看到 i、j、k,读者就要不断回头确认对应关系。此时使用 device、volume、case_id 更容易检查“操作对象是不是选错了”。
字数也不是唯一标准:io_size_bytes 使用领域中明确的 IO 术语,可能比把所有词都拉长更实用;但项目内部自造缩写应解释。PEP 8 还特别提醒不要把容易与数字混淆的 l、O、I 用作单字母变量。
09 · list 是内置名字,为什么不适合拿来装列表?
Python 机制与本文建议:list 是内置名字,并非硬关键字;你能把它重新绑定到一个列表。但在对应作用域里,随后写 list(...) 时,取到的可能是这个列表对象,而不是内置的列表类型。普通列表不能这样调用,所以会触发 TypeError。
下例故意在函数内部使用有问题的名称,让遮蔽局限在这个函数内。第一行输出 TypeError;函数外的内置 list 仍可用,第二行是 [0, 1]。错误捕获仅用于演示,不是在真实程序中掩盖命名错误的修复方案。
类似地,尽量避免用 str、dict、sum、id 等现有内置名字表示无关数据。根据对象角色选择 case_ids、message、case_id_to_status、device_id,通常比在所有内置名字后机械添加下划线更清楚。
def demonstrate_shadowing():
list = ['case_a'] # 故意示范不推荐的命名。
try:
list(range(2))
except TypeError as error:
print(type(error).__name__)
demonstrate_shadowing()
case_ids = list(range(2))
print(case_ids)10 · 大写常量名,为什么还可以重新赋值?
约定与机制要分开:MAX_ATTEMPT_COUNT 的大写形式表达“按项目约定,把它当作不应重新赋值的常量”。Python 不会因为名称大写就自动阻止赋值,也不会让它所引用的可变对象变成只读。
下面故意违反这一约定:先把值从 3 改为 4,再修改一个由大写名称绑定的列表。输出是 4 和包含两个状态的列表。这证明的是运行时允许这些动作;它不说明这样的常量设计值得采用。
typing.Final 可以向静态类型检查器表达不应重新赋值的意图,但 Python 文档明确说明它不提供这些性质的运行时检查;也不能据此认为引用到的列表被冻结。本文未运行静态类型检查器,不声称检查器已验证这些示例。
MAX_ATTEMPT_COUNT = 3
MAX_ATTEMPT_COUNT = 4 # 运行时允许;故意违反“常量”约定。
print(MAX_ATTEMPT_COUNT)
EXPECTED_STATUSES = ['ready']
EXPECTED_STATUSES.append('failed')
print(EXPECTED_STATUSES)11 · _cache、__cache 和 __cache__,不是三级保密权限
Python 机制:前导或尾随下划线可能表达约定,也可能对应特定语法机制,不能只数下划线就推断访问权限。
| 形式 | 含义和边界 | 本文的使用判断 |
|---|---|---|
cached_status | 普通名称 | 可作为公开属性;是否公开应由接口约定说明 |
_cached_status | 表达非公开实现细节;并不禁止外部访问 | 调用者不应依赖其长期稳定性 |
类定义中的 __cached_status | 触发名称改写,通常用于减少继承中的名称冲突 | 不能用作安全隔离;Google 不鼓励普遍使用这种形式 |
__cached_status__ | 落入首尾双下划线的保留名称形式 | 不要自创新协议名;__init__ 等已有协议仍按官方定义实现 |
class_ | 尾随单下划线可用于避开关键字冲突 | 也可先考虑更具体的名称,如 test_class |
下例中,probe._cached_status 可以直接读到。probe.__request_count 读不到,但名称改写后的属性仍可访问。四行输出依次为 ready、False、True、2。看不到原拼写,不等于内容不可访问。本文通过类演示变量行为,暂不展开完整继承规则。
class StorageProbe:
def __init__(self):
self._cached_status = 'ready'
self.__request_count = 2
probe = StorageProbe()
print(probe._cached_status)
print(hasattr(probe, '__request_count'))
print(hasattr(probe, '_StorageProbe__request_count'))
print(probe._StorageProbe__request_count)12 · _ 也可以绑定对象,不会自动把值“扔掉”
Python 机制:在普通赋值或循环目标的位置,_ 可以是普通名字。下例用它表示“这个循环值没有业务用途”,循环后仍能打印出最后绑定的值 2。该写法不意味着 Python 没有保存这个绑定。
但不要把结论推广到所有位置:结构化模式匹配中,case _: 的下划线是通配符,不产生同样的绑定;交互式解释器和国际化代码也可能给下划线赋予不同用途。看到下划线,应先识别上下文。
本文建议:下面用显式的 max_attempt_count 表达“总尝试次数”。如果需求是“首次尝试之外最多再重试 3 次”,那总尝试次数应是 4,不能直接把变量改名后保留原来的循环次数。
max_attempt_count = 3
for _ in range(max_attempt_count):
pass
print(_)
max_retry_count = 3
max_attempt_count = 1 + max_retry_count
print(max_attempt_count)13 · 参数名也是接口:重命名不能只看函数体
局部临时变量的改名通常可以控制在函数内部。参数名则可能被调用者写进关键字调用里。下面把旧参数 timeout 换为 timeout_s 后,原来的 timeout=3 调用会触发 TypeError;新名称的调用输出 3。
本文实践建议:改参数名前搜索调用者、示例、测试、配置和外部文档;公开接口按项目的兼容策略迁移。若外部系统的 JSON 字段叫 deviceId,内部可以转成 device_id,但不能擅自把发送出去的字段名也改掉。命名一致性需要服从已约定的接口。
Google 也为紧密耦合的外部或非 Python 代码保留了一致性例外。这不是允许随意混用风格,而是提醒我们先看接口来源与兼容需求。
def read_status(timeout_s):
return timeout_s
try:
read_status(timeout=3)
except TypeError as error:
print(type(error).__name__)
print(read_status(timeout_s=3))14 · 从能运行到容易检查:重写一段测试结果统计
两段代码做同一件事:统计严格超过阈值的读延迟。第一段的 a、b、x 没有解释参数角色;第二段把含义放进名称,并明确单位。这里的阈值是本文的演示数据,不是存储产品的性能标准。
输入 [8, 12, 20, 25] 毫秒,阈值 20 毫秒,只有 25 被计入,所以两行输出都是 1;等于阈值的 20 不计入。代码中的 > 决定边界,名称和文档需要与它一致。若产品要求“达到或超过”,则应连同测试一起调整比较条件。
这次重写改善了可读性,并没有自动证明统计口径正确。还应确认输入是否都是读操作、单位是否一致、重复记录是否需要去重,以及空输入应返回什么。命名帮助评审者提出这些问题,验证负责回答它们。
def f(a, b):
return sum(1 for x in a if x > b)
def count_slow_reads(read_latencies_ms, threshold_ms):
"""Count read latencies strictly greater than the threshold, in ms."""
return sum(
1 for read_latency_ms in read_latencies_ms
if read_latency_ms > threshold_ms
)
read_latencies_ms = [8, 12, 20, 25]
threshold_ms = 20
print(f(read_latencies_ms, threshold_ms))
print(count_slow_reads(read_latencies_ms, threshold_ms))15 · 复盘:换一段自己的代码试一试
先选择你自己的一个小函数,给其中三个变量写下“对象、单位、含义或边界”,再判断是否需要改名。下面问题是本文提供的练习建议,不代表你已经完成。
1. userId 能运行,为什么这里仍建议改成 user_id?
合法标识符与项目风格是两层判断。解释器接受 userId;本篇采用的普通变量风格是小写词加下划线。维护公开接口时还要检查兼容性。
2. 把 timeout_ms 改名为 timeout_s,数值 250 会自动变成 0.25 吗?
不会。单位转换必须由实际计算完成,并检查所有接口约定。
3. MAX_ATTEMPT_COUNT = 3 表示最多重试三次吗?
按本文定义它表示总共尝试三次。若首次尝试后最多再重试三次,总次数为四。名称、需求说明与循环边界应一致。
4. _cache 与 __cache 哪个真正禁止外部读取?
二者都不构成这样的访问安全边界。前者是非公开约定;后者在类定义语境下涉及名称改写。
5. 把列表赋给大写名称,就能防止追加元素吗?
不能。大写是常量命名约定;对象是否可变与名称大小写是不同问题。
6. 先把所有参数名改成 snake_case,再运行测试,足够吗?
不够。还需搜索关键字调用、外部字段、示例与文档,并评估已有测试是否覆盖真实调用者。风格修改也可能改变接口可用性。
用于自己的简短检查:名字能说明业务角色吗?单位和数量口径清楚吗?是否遮蔽了常用内置名字?下划线表达的是哪一种约定或机制?改名会影响哪些调用者?
16 · 来源、原创部分与验证范围
本文对命名规则作简要归纳;测试场景、示例、逐步分析和练习为本次原创讲解,不是原指南逐段翻译。阅读规则的完整措辞、例外和后续更新,请以原文为准。
- Google Python Style Guide:3.16 Naming,含 3.16.1—3.16.5;规则的主要依据。
- 用户指定的中文译文:Python 风格规范 · 命名:辅助阅读,与英文原文交叉核对。
- PEP 8:变量名、参数名、常量、非公开接口与兼容性。
- Python 语言参考 · 词法分析:标识符、关键字和下划线的特殊上下文。
- Python 教程 · Names and Objects / Private Variables:名称绑定、别名和类中的名称改写。
- Python 执行模型 · 名称解析:局部作用域中的绑定与内置名称解析。
- Python 内置类型 · str.isidentifier,以及 typing.Final:标识符形式检查和静态检查的边界。
代码示例在 Python 3.12.14 中逐段执行并核对标准输出;重写的延迟统计还检查了空输入、等于阈值及不同阈值的结果。没有对本文运行 Pylint 或静态类型检查器,也没有把代码格式正确等同于业务正确。
后续可以依次展开函数和类命名、导入、注释与 docstring、异常、类型注解和自动检查。当前实际发布范围是本篇变量命名。相关概念和文章入口可从学习地图复盘。