Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 562 – 模块 __getattr__ 和 __dir__

作者:
Ivan Levkivskyi <levkivskyi at gmail.com>
状态:
最终
类型:
标准跟踪
创建:
2017-09-09
Python 版本:
3.7
历史记录:
2017-09-09
决议:
Python-Dev 消息

目录

摘要

建议支持在模块上定义的 __getattr____dir__ 函数,以提供模块属性访问的基本自定义。

基本原理

有时需要自定义或以其他方式控制对模块属性的访问。一个典型的例子是管理弃用警告。典型的解决方法是将模块对象的 __class__ 分配给 types.ModuleType 的自定义子类,或用自定义包装器实例替换 sys.modules 项目。通过识别直接在模块中定义的 __getattr__ 来简化此过程会很方便,该函数的行为类似于普通的 __getattr__ 方法,但它将在模块实例上定义。例如

# lib.py

from warnings import warn

deprecated_names = ["old_function", ...]

def _deprecated_old_function(arg, other):
    ...

def __getattr__(name):
    if name in deprecated_names:
        warn(f"{name} is deprecated", DeprecationWarning)
        return globals()[f"_deprecated_{name}"]
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

# main.py

from lib import old_function  # Works, but emits the warning

另一个广泛使用 __getattr__ 的用例是延迟子模块导入。考虑一个简单的例子

# lib/__init__.py

import importlib

__all__ = ['submod', ...]

def __getattr__(name):
    if name in __all__:
        return importlib.import_module("." + name, __name__)
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

# lib/submod.py

print("Submodule loaded")
class HeavyClass:
    ...

# main.py

import lib
lib.submod.HeavyClass  # prints "Submodule loaded"

有一个相关的提案 PEP 549,建议支持实例属性以实现类似的功能。区别在于,本 PEP 提出了一个更快、更简单的机制,但提供了更基本的自定义。此提案的另一个动机是 PEP 484 已经在 Python 存根文件中为此目的定义了模块 __getattr__ 的用法,请参阅 PEP 484

此外,为了允许修改对模块调用 dir() 的结果,以显示已弃用和其他动态生成的属性,建议支持模块级别的 __dir__ 函数。例如

# lib.py

deprecated_names = ["old_function", ...]
__all__ = ["new_function_one", "new_function_two", ...]

def new_function_one(arg, other):
   ...
def new_function_two(arg, other):
    ...

def __dir__():
    return sorted(__all__ + deprecated_names)

# main.py

import lib

dir(lib)  # prints ["new_function_one", "new_function_two", "old_function", ...]

规范

模块级别的 __getattr__ 函数应接受一个参数,该参数是属性的名称,并返回计算出的值或引发 AttributeError

def __getattr__(name: str) -> Any: ...

如果通过正常查找(即 object.__getattribute__)未在模块对象上找到属性,则在引发 AttributeError 之前,将在模块 __dict__ 中搜索 __getattr__。如果找到,则使用属性名称调用它,并返回结果。将名称查找为模块全局变量将绕过模块 __getattr__。这是故意的,否则对于内置函数调用 __getattr__ 将显着损害性能。

__dir__ 函数不应接受任何参数,并返回一个字符串列表,表示模块上可访问的名称

def __dir__() -> List[str]: ...

如果存在,此函数将覆盖模块上的标准 dir() 搜索。

此 PEP 的参考实现可以在 [2] 中找到。

向后兼容性和对性能的影响

此 PEP 可能会破坏使用模块级(全局)名称 __getattr____dir__ 的代码。(但语言参考明确保留了所有未记录的 dunder 名称,并允许“无需警告即可破坏”;请参阅 [3]。)此 PEP 的性能影响最小,因为 __getattr__ 仅在缺少属性时才会被调用。

一些执行模块属性发现的工具可能不会期望 __getattr__。但是,这个问题并不新鲜,因为已经可以将模块替换为具有覆盖的 __getattr____dir__ 的模块子类,但是使用此 PEP,此类问题可能会更频繁地发生。

讨论

请注意,使用模块 __getattr__ 需要小心,以保持引用的对象可腌制。例如,函数的 __name__ 属性应与可以通过 __getattr__ 访问它的名称相对应。

def keep_pickleable(func):
    func.__name__ = func.__name__.replace('_deprecated_', '')
    func.__qualname__ = func.__qualname__.replace('_deprecated_', '')
    return func

@keep_pickleable
def _deprecated_old_function(arg, other):
    ...

还应该注意避免递归,就像使用类级 __getattr__ 一样。

要使用触发 __getattr__ 的模块全局变量(例如,如果要使用延迟加载的子模块),可以将其访问为

sys.modules[__name__].some_global

或作为

from . import some_global

请注意,后者设置了模块属性,因此 __getattr__ 将仅被调用一次。

参考文献


来源:https://github.com/python/peps/blob/main/peps/pep-0562.rst

上次修改时间:2023-09-09 17:39:29 GMT