如何在模块上的类上实现等效的 __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
9 回答
A while ago, Guido declared that all special method lookups on new-style classes bypass getattr and getattribute . Dunder方法以前曾在模块上工作 - 例如,您可以通过在这些技巧broke之前定义
__enter__
和__exit__
来使用模块作为上下文管理器 .最近一些历史特征卷土重来,其中包括模块
__getattr__
,因此不再需要现有的hack(在导入时用sys.modules
中的类替换自己的模块) .在Python 3.7中,您只需使用一种显而易见的方法 . 要自定义模块上的属性访问权限,请在模块级别定义一个
__getattr__
函数,该函数应接受一个参数(属性名称),并返回计算值或引发AttributeError
:这也将允许挂钩进入"from"导入,即您可以返回动态生成的对象,例如
from my_module import whatever
.在相关说明中,与模块getattr一起,您还可以在模块级别定义
__dir__
函数以响应dir(my_module) . 有关详细信息,请参阅PEP 562 .您遇到两个基本问题:
__xxx__
方法只在类上查找TypeError: can't set attributes of built-in/extension type 'module'
(1)意味着任何解决方案都必须跟踪正在检查的模块,否则每个模块都将具有实例替换行为; (2)意味着(1)甚至不可能......至少不是直接的 .
幸运的是,sys.modules对于那里的内容并不挑剔,所以包装器可以工作,但仅用于模块访问(即
import somemodule; somemodule.salutation('world')
;对于相同模块访问,你几乎必须从替换类中抽取方法并将它们添加到globals()
eiher使用类上的自定义方法(我喜欢使用.export()
)或使用泛型函数(例如已经列为答案的那些) . 要记住一件事:如果包装器每次都创建一个新实例,那么全局解决方案不是,你最终会产生微妙的不同行为 . 哦,你不是一个人或者另一个人 .Update
来自Guido van Rossum:
因此,实现所需内容的既定方法是在模块中创建单个类,并且作为模块的最后一个行为将
sys.modules[__name__]
替换为您的类的实例 - 现在您可以根据需要使用__getattr__
/__setattr__
/__getattribute__
.这是一个hack,但你可以用一个类包装模块:
我们通常不这样做 .
我们做的是这个 .
为什么?这样隐式全局实例是可见的 .
例如,查看
random
模块,该模块创建一个隐式全局实例,以略微简化您想要"simple"随机数生成器的用例 .类似于@HåvardS提出的,在我需要在模块上实现一些魔法的情况下(如
__getattr__
),我将定义一个继承自types.ModuleType
的新类并将其放在sys.modules
中(可能替换我自定义ModuleType
的模块)已定义) .请参阅Werkzeug的主init.py文件,以获得相当强大的实现 .
这是hackish,但......
这通过迭代全局命名空间中的所有对象来工作 . 如果该项是一个类,它将迭代类属性 . 如果属性是可调用的,则将其作为函数添加到全局命名空间 .
它忽略包含“__”的所有属性 .
我不会在 生产环境 代码中使用它,但它应该让你开始 .
这是我自己的谦逊贡献 - @HåvardS的轻微修饰额定答案,但更明确一点(所以@ S.Lott可能会接受,尽管对OP来说可能不够好):
创建包含类的模块文件 . 导入模块 . 在刚刚导入的模块上运行
getattr
. 您可以使用__import__
进行动态导入,并从sys.modules中提取模块 .这是你的模块
some_module.py
:在另一个模块中:
动态执行此操作:
在某些情况下,
globals()
字典就足够了,例如,您可以从全局范围中按名称实例化类: