内容提要
该PEP提议在Python模块中引入`__export__`变量,用于限制模块外部对属性的访问和可见性。模块可定义`__export__`列表,未列出的名称在外部访问时引发ImportError,且`dir()`和通配符导入会相应调整。此机制旨在提升模块维护性,明确公共API边界,但非安全措施,可被绕过。
延伸解读
与 __all__ 的关系及差异
PEP 842 引入的 __export__ 与现有的 __all__ 有相似之处,但定位不同。__all__ 主要影响 from module import * 的通配符导入,而 __export__ 还限制模块属性的直接访问(如 spam.Private 会引发 ImportError)。当模块定义了 __export__ 而未定义 __all__ 时,__all__ 会被隐式设置为 __export__,但两者可以不同,例如 __export__ 可包含类型别名等不应被通配符导入的名称。
非安全机制,可被绕过
PEP 明确表示 __export__ 并非安全机制,不能防止对私有属性的访问。用户仍可通过 mod.__dict__['attr_name'] 绕过限制。其设计目的是提升模块运行时检查的清晰度,帮助维护者明确公共 API 边界,而非提供真正的访问控制。Python 语言本身不倾向于引入访问修饰符,因此该机制更侧重于改善开发体验。
对现有代码的潜在影响
该 PEP 可能会破坏已定义全局变量 __export__ 的代码,但语言参考已明确禁止用户定义此类名称。为促进采用,建议开发者同时定义 __all__ 和 __export__,以便在 Python 3.16+ 获得导出行为,同时保持旧版本的兼容性。此外,自定义 __dir__ 方法时若包含未导出的名称,可能导致 help() 等工具出错,需注意实现。
Q&A
PEP 842 提出的 __export__ 变量有什么作用?
PEP 842 提出在 Python 模块中定义 __export__ 变量,用于限制模块外部对属性的访问和可见性。未列出的名称在外部访问时会引发 ImportError,并且 dir() 和通配符导入会相应调整,从而明确公共 API 边界,提升模块维护性。
在 Python 中,如何定义 __export__ 变量?
在模块的全局作用域中,将 __export__ 赋值为一个支持 in 运算符的对象,通常为列表、元组或集合,例如:__export__ = ['name1', 'name2']。该对象必须能对字符串进行包含检查。
当模块定义了 __export__ 后,访问未导出的属性会发生什么?
当模块定义了 __export__ 后,访问未在 __export__ 中列出的属性会引发 ImportError,例如 spam.Private 会报错。但 dunder 名称(如 __dict__、__file__)始终可访问。
__export__ 对 dir() 和通配符导入有什么影响?
定义了 __export__ 后,dir() 会排除未导出的名称(但 dunder 名称仍会包含)。如果模块没有定义 __all__,则 __all__ 会被隐式设置为 __export__,因此通配符导入(from module import *)只会导入 __export__ 中列出的名称。
__export__ 和 __all__ 有什么区别?
__export__ 控制外部属性访问和可见性,而 __all__ 控制通配符导入。__export__ 可以包含不在 __all__ 中的名称,例如类型别名,这些名称可能不希望被通配符导入,但静态类型检查需要访问。
PEP 842 是否提供安全机制来防止访问私有属性?
不,PEP 842 明确表示这不是安全机制,可以通过访问 mod.__dict__['attr_name'] 绕过。其目的是提高运行时检查模块的清晰度,而非提供真正的访问控制。
PEP 842 为什么没有采用 export 关键字?
因为添加新语法需要大量需求和社区反馈,目前需求不明确。因此先通过 __export__ 变量实现,并期望第三方库能基于此构建类似 export 语法的 API,以验证需求。