HOWTO · Python

Python 中的常量

了解如何使用 UPPER_CASE 名称定义 Python 常量、分组相关值,以及在需要固定成员集合时使用 Enum。

本页内容

Python 没有能让名称永久只读的 const 声明。通常的做法是在模块级定义一个 UPPER_CASE 名称,并把重新赋值视为代码审查错误。可以使用类来分组相关值;如果值是固定的命名成员集合,应使用 Enum,避免调用者替换成任意成员。

使用 UPPER_CASE 名称定义模块级常量

把模块或包共享的值放在模块顶部附近,并使用大写字母和下划线命名。其他模块可以正常导入这些名称。这个约定表达了意图,但 Python 仍允许重新绑定名称。

TIMEOUT_SECONDS = 30
SUPPORTED_FORMATS = ("json", "toml")

print(TIMEOUT_SECONDS)
TIMEOUT_SECONDS = 60  # Legal Python, but a reassignment of a constant by convention.
print(TIMEOUT_SECONDS)

输出:

30
60

元组可以防止修改该元组对象,但不能让模块名称不可变:SUPPORTED_FORMATS 仍可重新绑定。同样,列表等可变值也能被原地修改。实际可行时选择不可变值,并把属于模块公共配置或 API 的常量放在模块中。

使用类分组相关值

类可以为相关值提供可读的命名空间,例如通过 Settings.DEFAULT_TIMEOUT 访问。这有助于组织代码,但普通类属性不是语言强制的常量;类本身以及通常的实例都可以被修改。

class Settings:
    DEFAULT_TIMEOUT = 30
    RETRY_LIMIT = 3


print(Settings.DEFAULT_TIMEOUT)
Settings.DEFAULT_TIMEOUT = 60
print(Settings.DEFAULT_TIMEOUT)

输出:

30
60

自定义描述器或元类可以拒绝某些赋值,但会增加复杂度,还必须处理实例属性、子类化和其他修改路径。不要因为普通类看起来能防止重新赋值就使用它;除非有明确且经过测试的强制需求,否则只把它当作命名空间。

使用 Enum 表示固定的命名值集合

当值必须属于定义好的成员集合时,使用标准库的 enum 模块。通过类访问 Enum 成员,尝试赋予新的成员值会引发异常。当集合本身具有状态、颜色或模式等领域含义时,Enum 比大写变量更合适。

from enum import Enum


class Color(Enum):
    RED = "red"
    BLUE = "blue"


print(Color.RED.value)
try:
    Color.RED = "green"
except AttributeError as error:
    print(type(error).__name__)

输出:

red
AttributeError

Enum 不会让程序中的所有值都不可变,也不能与原始字符串互换。成员身份重要时使用 Color.RED,API 需要底层值时使用 Color.RED.value。解析外部输入时构造 Color("red");未知值会引发 ValueError,可以在边界处明确处理。

将 namedtuple 和函数作为替代方案

较旧的 collections.namedtuple() 技术可以分组不可变字段。Constants = namedtuple("Constants", "PI E") 后执行 values = Constants(3.14159, 2.71828),会创建不能给字段赋值的元组。但它不能阻止重新绑定 values,对于命名选项也不如 Enum 表达力强。

返回值的函数可以隐藏构造过程或按需计算,但它不是常量声明。例如 def api_version(): return "v1" 表示访问器,而不是不可变绑定。简单常量优先使用模块级名称,分组使用类,封闭集合使用 Enum

选择并验证模式

普通共享值使用 UPPER_CASE 模块名称,需要命名空间改善组织时使用类,固定领域集合使用 Enum。始终说明修改边界:普通名称和类属性可以重新绑定,而 enum 成员拒绝重新赋值。上述示例验证了成功访问以及重要的失败或重新赋值行为。代码审查、测试和清晰的 API 仍是轻量约定的重要组成部分。

总结

Python 没有内置的 const 关键字。使用模块级 UPPER_CASE 名称定义普通常量,需要时用类分组相关常量;如果调用者应使用多个命名成员之一,则选择 Enumnamedtuple() 和函数访问器是专门的替代方案,而不是强制的常量。