
Python 官方明确禁止用户随意定义 __*__ 形式的标识符;这类名称属于解释器保留范围,擅自使用存在不可预知的兼容性风险——即便某些库(如 Pydantic)实际这么做了,也属权宜之计,而非推荐实践。
python 官方明确禁止用户随意定义 `__*__` 形式的标识符;这类名称属于解释器保留范围,擅自使用存在不可预知的兼容性风险——即便某些库(如 pydantic)实际这么做了,也属权宜之计,而非推荐实践。
在 Python 开发中,你永远不需要、也不应主动定义自己的双下划线(dunder)类属性,例如 __my_custom_metadata__ 或 __pydantic_config__。这一点并非风格建议,而是来自 Python 语言规范的硬性约束。
__*__
系统定义的名称,俗称“dunder”名称。这些名称由解释器及其实现(包括标准库)所定义……*任何未明确记录的 `____` 名称用法,均可能在无警告情况下被未来版本破坏。**
这意味着:
✅ 允许使用:__init__、__str__、__eq__、__class_vars__(仅当官方文档或 PEP 明确将其作为公共协议的一部分时,如 typing.ClassVar 的语义约定);
❌ 严禁定义:__mylib_version__、__schema_cache__、__internal_config__ 等任何以双下划线开头和结尾的自定义名称——无论是否加 ClassVar 注解,都违反语言规范。
为什么 Pydantic 等库“违规”使用了 __pydantic_*__?
Pydantic 中大量出现的 __pydantic_core_schema__、__pydantic_validator__ 等,并非遵循规范的设计选择,而是历史权衡下的技术妥协:
- 目标是将元数据与实例属性严格隔离(避免污染 obj.__dict__);
- 希望借助 __*__ 的视觉显著性提示“这是框架内部契约”;
- 同时依赖于一个现实判断:Python 解释器几乎不可能在未来占用 __pydantic_*__ 这类高度领域化的命名空间。
但这不构成效仿理由。正如官方文档所强调:“未明确记录的用法,即视为未授权,随时可能失效。”
正确替代方案:清晰、安全、符合 Python 惯例
| 需求场景 | ❌ 错误做法 | ✅ 推荐做法 | 说明 |
|---|---|---|---|
| 声明框架需读取的类级元数据 | __sqlalchemy_table__ = 'users' | __tablename__ = 'users'(SQLAlchemy) 或 model_config = ConfigDict(validate_default=True)(Pydantic v2) |
使用单下划线前缀 + 清晰命名(如 _config, _metadata),或采用显式配置对象(如 ConfigDict, BaseModel.model_config) |
| 标记仅供类型检查器识别的类变量 | __private_attributes__: ClassVar[dict] | __private_attributes__: ClassVar[dict] = {}(注:此处 ClassVar 是类型提示,非运行时行为) | ClassVar 本身不依赖 dunder;真正需要的是带默认值的类变量声明,而非 dunder 名称 |
| 实现框架钩子(如初始化后回调) | __post_init_hook__ = ... | 定义标准方法(如 model_post_init(self, context: Any))或注册函数到 @model_validator(mode='after') | 将行为暴露为公开、可文档化、可重写的方法,而非隐藏在 dunder 名称后 |
示例:安全定义模型元数据(Pydantic v2+ 风格)
from pydantic import BaseModel, ConfigDict
class User(BaseModel):
name: str
age: int
# ✅ 正确:使用显式、文档化的配置机制
model_config = ConfigDict(
validate_default=True,
extra='forbid',
frozen=False
)
# ✅ 正确:自定义类级数据用单下划线前缀(语义清晰,无保留字风险)
_default_role: str = 'user'
_api_endpoint: str = '/api/users'
# ✅ 正确:钩子通过标准装饰器暴露
@classmethod
def model_post_init(cls, __context: Any, __self: Any) -> None:
print(f"Initialized {cls.__name__}")总结:三条铁律
- *绝不发明 `__**:所有双下划线包围的名称必须源自 Python 语言规范、CPython 实现或已发布的 PEP(如dataclass_fields__`);
- 优先显式优于隐式:用 config、metadata、settings 等可读性强的属性名,配合类型注解和文档字符串;
- 信任框架的 API 设计:若某库要求你设置 __xxx__,请查阅其最新文档——它很可能已在新版本中迁移到更安全的接口(如 Pydantic 从 __pydantic_*__ 全面转向 model_config 和 @field_validator)。
记住:可维护性与向后兼容性,永远比命名的“视觉分组感”更重要。遵守规范不是教条,而是对整个 Python 生态负责。










