正在阅读Google Python 编码规范 01:从变量命名开始,读懂规则、含义与边界
香港天气加载中…
QWeather ↗
← Back to the notebook

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_underfailed_case_count、timeout_s
全局变量、类变量、实例变量lower_with_underactive_sessions、self.device_id
模块或类的常量CAPS_WITH_UNDERMAX_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。检查名字不能代替解析完整代码;软关键字还与具体语法位置有关。预期输出见下表对应的布尔值,可直接运行核对。

Python
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 的绑定,不会清空原列表;最后两行分别为原列表和 []。

本文建议:如果这里只是为了重试而给失败列表起别名,先明确你希望共享修改还是独立处理。一个更好听的名字不会自动建立数据副本。这个机制可与已有的函数调用与局部绑定笔记一起复盘。

Python
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 · 实践建议:把对象、单位和统计口径说清楚

本文实践建议:先问“这个值是什么”,再问“用什么单位、什么统计方式”。下面这些改名要以实际含义为前提,不能看到一个数就随意补上单位。

信息不足明确语义后的名称需要确认的事实
timeoutread_timeout_s哪一步的超时?接口接收秒还是毫秒?
sizeblock_size_bytes块大小还是文件大小?单位是否为字节?
latencyp99_read_latency_ms单次值、均值还是分位数?统计窗口另行记录。
timeelapsed_s 或 started_at经过的时长与一个时间点应分开。时间点还要说明时钟或时区。
countfailed_case_count数的是用例、失败断言、重试记录,还是故障单?

注意:重命名不会完成单位换算。把数值 250 从 timeout_ms 原样赋给 timeout_s,并不会把它变成 0.25。下例显式除以 1000 后才得到 0.25。接口若接收秒,就应传入换算后的值。

Python
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_queuequeue 在这里描述处理角色;是否需要保留该词取决于实际接口

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,通常比在所有内置名字后机械添加下划线更清楚。

Python
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 文档明确说明它不提供这些性质的运行时检查;也不能据此认为引用到的列表被冻结。本文未运行静态类型检查器,不声称检查器已验证这些示例。

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。看不到原拼写,不等于内容不可访问。本文通过类演示变量行为,暂不展开完整继承规则。

Python
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,不能直接把变量改名后保留原来的循环次数。

Python
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 代码保留了一致性例外。这不是允许随意混用风格,而是提醒我们先看接口来源与兼容需求。

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 不计入。代码中的 > 决定边界,名称和文档需要与它一致。若产品要求“达到或超过”,则应连同测试一起调整比较条件。

这次重写改善了可读性,并没有自动证明统计口径正确。还应确认输入是否都是读操作、单位是否一致、重复记录是否需要去重,以及空输入应返回什么。命名帮助评审者提出这些问题,验证负责回答它们。

Python
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 · 来源、原创部分与验证范围

本文对命名规则作简要归纳;测试场景、示例、逐步分析和练习为本次原创讲解,不是原指南逐段翻译。阅读规则的完整措辞、例外和后续更新,请以原文为准。

  1. Google Python Style Guide:3.16 Naming,含 3.16.1—3.16.5;规则的主要依据。
  2. 用户指定的中文译文:Python 风格规范 · 命名:辅助阅读,与英文原文交叉核对。
  3. PEP 8:变量名、参数名、常量、非公开接口与兼容性。
  4. Python 语言参考 · 词法分析:标识符、关键字和下划线的特殊上下文。
  5. Python 教程 · Names and Objects / Private Variables:名称绑定、别名和类中的名称改写。
  6. Python 执行模型 · 名称解析:局部作用域中的绑定与内置名称解析。
  7. Python 内置类型 · str.isidentifier,以及 typing.Final:标识符形式检查和静态检查的边界。

代码示例在 Python 3.12.14 中逐段执行并核对标准输出;重写的延迟统计还检查了空输入、等于阈值及不同阈值的结果。没有对本文运行 Pylint 或静态类型检查器,也没有把代码格式正确等同于业务正确。

后续可以依次展开函数和类命名、导入、注释与 docstring、异常、类型注解和自动检查。当前实际发布范围是本篇变量命名。相关概念和文章入口可从学习地图复盘。

PythonGoogle 风格指南变量命名代码可读性测试开发

先说清名字代表什么,再用代码和证据检查它的行为。

MORE IN DEVELOPMENT读懂 SSH 本地端口转发:-N、-L 与两端的 127.0.0.1