模块上的 __getattr__

新手上路,请多包涵

如何在类、模块上实现 __getattr__ 的等价物?

例子

当调用一个模块的静态定义属性中不存在的函数时,我希望在该模块中创建一个类的实例,并调用其上的方法,其名称与模块的属性查找中失败的名称相同。

 class A(object):
    def salutation(self, accusative):
        print "hello", accusative

# note this function is intentionally on the module, and not the class above
def __getattr__(mod, name):
    return getattr(A(), name)

if __name__ == "__main__":
    # i hope here to have my __getattr__ function above invoked, since
    # salutation does not exist in the current namespace
    salutation("world")

这使:

 matt@stanley:~/Desktop$ python getattrmod.py
Traceback (most recent call last):
  File "getattrmod.py", line 9, in <module>
    salutation("world")
NameError: name 'salutation' is not defined

原文由 Matt Joiner 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 837
2 个回答

不久前,Guido 宣布所有对新型类的特殊方法查找绕过 __getattr____getattribute__ 。 Dunder 方法以前适用于模块——例如,在这些技巧失效之前,您可以简单地通过定义 __enter____exit__ 模块用作上下文管理器。

最近一些历史特征卷土重来,模块 __getattr__ 其中,因此现有的 hack(一个模块在导入时用 sys.modules 中的类替换自身)应该不再必要的。

在 Python 3.7+ 中,您只需使用一种显而易见的方法。要自定义模块上的属性访问,请在模块级别定义一个 __getattr__ 函数,该函数应接受一个参数(属性名称),并返回计算值或引发 AttributeError

 # my_module.py

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

这也将允许挂钩到“来自”导入,即您可以为语句返回动态生成的对象,例如 from my_module import whatever

在相关说明中,除了模块 getattr 之外,您还可以在模块级别定义 __dir__ 函数以响应 dir(my_module) 。有关详细信息,请参阅 PEP 562

原文由 wim 发布,翻译遵循 CC BY-SA 4.0 许可协议

您在这里遇到两个基本问题:

  1. __xxx__ 方法只在类中查找
  2. TypeError: can't set attributes of built-in/extension type 'module'

(1) 意味着任何解决方案还必须跟踪正在检查的模块,否则 每个 模块都会有实例替换行为;和 (2) 意味着 (1) 甚至不可能……至少不是直接的。

幸运的是,sys.modules 对那里的内容并不挑剔,因此包装器可以工作,但仅适用于模块访问(即 import somemodule; somemodule.salutation('world') ;对于相同的模块访问,您几乎必须从替换类中提取方法并将它们添加到 globals() eiher 在类上使用自定义方法(我喜欢使用 .export() )或使用通用函数(例如那些已经列为答案的函数)。要保留的一件事请记住:如果包装器每次都创建一个新实例,而全局解决方案不是,那么您最终会得到细微不同的行为。哦,您不能同时使用两者——它是一个或其他。


更新

来自 Guido van Rossum

实际上有一个偶尔使用和推荐的 hack:一个模块可以定义一个具有所需功能的类,然后在最后,用该类的实例替换 sys.modules 中的自身(或者用类,如果你坚持,但这通常不太有用)。例如:

 # module foo.py

import sys

class Foo:
    def funct1(self, <args>): <code>
    def funct2(self, <args>): <code>

sys.modules[__name__] = Foo()

这是有效的,因为导入机制正在积极地启用这个 hack,并且作为它的最后一步,在加载它之后将实际模块从 sys.modules 中拉出来。 (这绝非偶然。很久以前就提出了黑客攻击,我们决定我们足够喜欢在导入机器中支持它。)

所以完成你想要的东西的既定方法是在你的模块中创建一个类,并且作为模块的最后一幕替换 sys.modules[__name__] 与你的类的实例 - 现在你可以玩 __getattr__ / __setattr__ / __getattribute__ 根据需要。


注意 1 :如果您使用此功能,那么模块中的任何其他内容,例如全局变量、其他函数等,都会在 sys.modules 赋值时丢失——因此请确保所需的一切都在替换类。

注2 :要支持 from module import * 你必须在类中定义 __all__ ;例如:

 class Foo:
    def funct1(self, <args>): <code>
    def funct2(self, <args>): <code>
    __all__ = list(set(vars().keys()) - {'__module__', '__qualname__'})

根据您的 Python 版本,可能还有其他名称需要从 __all__ 中省略。如果不需要 Python 2 兼容性,则可以省略 set()

原文由 Ethan Furman 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题