PEP 844:``public`` 和 ``private`` 内置函数

PEP 844:``public`` 和 ``private`` 内置函数

💡 原文英文,约5200词,阅读约需19分钟。
📝

内容提要

该PEP提议新增内置函数public()和private(),作为装饰器或函数调用,用于在定义处声明模块的公共接口,并自动维护__all__列表,避免其与定义脱节。此设计基于atpublic包,旨在无需第三方依赖,提升模块可见性管理的便利性,不改变现有语义。

🔎

延伸解读

为什么选择内置函数而非新语法

PEP 844 选择新增内置函数 public() 和 private(),而非像 PEP 842 那样引入 export 关键字,主要考虑到新语法成本高昂:需要修改语法、无法向后兼容,且会永久限制语法。内置函数则成本低,可轻松在旧版本上模拟,且已有 atpublic 包十年的使用经验。此外,内置函数不会改变现有语义,对不使用的模块无影响,即使设计有误也可按常规方式弃用,无需改动语法。

函数调用形式对静态分析工具的挑战

public() 的函数调用形式(如 public(SEVEN=7))会在调用者帧中绑定名称,但语法树中看不到赋值,导致类型检查器、linter 等工具可能误报 SEVEN 未定义。不过,静态分析工具已有处理类似情况的先例,如识别 namedtuple()、dataclass() 等创建命名空间的调用。工具可专门识别 public() 的调用形式,且 PEP 也提供了显式赋值形式(SEVEN = public(SEVEN=7))作为过渡方案。

与 PEP 842 和 PEP 843 的分工

PEP 844 专注于解决 __all__ 与定义脱节的问题,而 PEP 842 和 PEP 843 则分别提出 export 关键字和 from ... export ... 语法。PEP 844 认为,对于可装饰的类和函数,装饰器是更符合人体工程学的方案;对于常量等无法装饰的名称,函数调用形式也能避免重复。对于 re-export 场景,PEP 844 承认无法优雅解决,而 PEP 843 的语法恰好能弥补这一缺口,两者可协同工作。

Q&A

PEP 844 提议新增哪两个内置函数?它们的主要作用是什么?

PEP 844 提议新增内置函数 public() 和 private(),用于在定义处声明模块的公共接口,并自动维护 __all__ 列表,避免其与定义脱节。

public() 函数有哪两种调用形式?分别适用于什么场景?

public() 有两种调用形式:装饰器形式(@public)用于类和函数定义,函数调用形式(public(SEVEN=7))用于常量、实例等无法装饰的名称。

private() 函数与 public() 在行为上有哪些关键区别?

private() 仅作为装饰器使用,从 __all__ 中移除名称,但不会创建 __all__,以避免改变通配符导入的默认行为。而 public() 会创建或更新 __all__。

PEP 844 如何解决 __all__ 与定义脱节的问题?

通过在定义处使用 @public 或 @private 装饰器,声明名称的可见性,使 __all__ 自动与定义保持同步,无需手动维护列表。

PEP 844 对静态分析工具(如类型检查器)有什么潜在影响?如何缓解?

函数调用形式 public(SEVEN=7) 在语法树中不可见,可能导致静态分析工具报告 SEVEN 未定义。缓解方法包括工具识别该调用,或使用返回值显式赋值(如 SEVEN = public(SEVEN=7))。

PEP 844 与 PEP 842 和 PEP 843 的关系是什么?

PEP 844 是 PEP 842 和 PEP 843 的辅助提案,专注于解决 __all__ 的维护问题,不涉及新语法。PEP 842 提议 export 关键字,PEP 843 提议 from ... export ... 形式,三者可互补。

PEP 844 是否改变 Python 中“公共”名称的定义?

不改变。PEP 844 明确采用 Python 语言参考中对“公共”的定义,不重新定义,也不引入新的可见性概念。

PEP 844 对性能有何影响?

public() 的调用开销小且可接受,C 实现可进一步优化。模块不调用 public() 则无性能损失。

🏷️

标签

➡️

继续阅读