可调用模块

为什么 Python 不允许模块使用 __call__方法?(除了显然不容易直接进口之外。)具体来说,为什么使用 a(b)语法不能像查找函数、类和对象那样查找 __call__属性?(对于模块来说,查找是否只是不兼容而已?)

>>> print(open("mod_call.py").read())
def __call__():
return 42


>>> import mod_call
>>> mod_call()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'module' object is not callable
>>> mod_call.__call__()
42
23628 次浏览

只有在类型上而不是在实例上定义特殊方法时,才能保证隐式调用这些方法。(__call__是模块实例 mod_call的属性,而不是 <type 'module'>的属性。)不能向内置类型添加方法。

Https://docs.python.org/reference/datamodel.html#special-lookup

Python 不允许模块覆盖或添加 任何魔法方法,因为考虑到很少出现可以使用魔法方法的强大用例,保持模块对象简单、规则和轻量级是非常有利的。

当这样的用例 出现时,解决方案是将类实例伪装成模块。具体来说,将 mod_call.py编码如下:

import sys


class mod_call:
def __call__(self):
return 42


sys.modules[__name__] = mod_call()

现在,导入和调用 mod_call的代码工作正常。

正如 Miles 所说,您需要在类级别上定义调用。 因此,Alex post 的一个替代方案是将 sys.modules[__name__]的类改为 sys.modules[__name__]类型的子类(它应该是 types.ModuleType)。

这样做的好处是,在保持模块的所有其他属性(如访问函数、变量、 ...)的同时,模块是可调用的。

import sys


class MyModule(sys.modules[__name__].__class__):
def __call__(self):  # module callable
return 42


sys.modules[__name__].__class__ = MyModule

注意: 使用 python3.6进行了测试。

Christoph Böddeker 的回答 似乎是创建可调用模块的最佳方法,但正如 评论所说,它只能在 Python 3.5及以上版本中使用。

这样做的好处是,您可以像往常一样编写模块,只需在最后添加类重新分配即可。

# coolmodule.py
import stuff


var = 33
class MyClass:
...
def function(x, y):
...


class CoolModule(types.ModuleType):
def __call__(self):
return 42
sys.modules[__name__].__class__ = CoolModule

一切正常,包括所有预期的模块属性,如定义的 __file__。(这是因为您实际上根本没有更改导入产生的模块对象,只是用 __call__方法将其“强制转换”为一个子类,这正是我们想要的。)

为了让它在3.5以下的 Python 版本中以类似的方式工作,您可以对 Alex Martelli 的回答进行调整,使您的新类成为 ModuleType 的子类,并将所有模块的属性复制到您的新模块实例中:

#(all your module stuff here)


class CoolModule(types.ModuleType):
def __init__(self):
types.ModuleType.__init__(self, __name__)
# or super().__init__(__name__) for Python 3
self.__dict__.update(sys.modules[__name__].__dict__)
def __call__(self):
return 42


sys.modules[__name__] = CoolModule()

现在定义了 __file____name__和其他模块属性(如果仅仅遵循 Alex 的回答,则不存在这些属性) ,并且您导入的模块对象仍然是“ a”模块。

所有答案只对 import mod_call有效。为了让 from mod_call import *同时工作,可以按照以下方式对@Alex Martelli 的解决方案进行增强

import sys


class mod_call:
def __call__(self):
return 42
mod_call = __call__
__all__ = list(set(vars().keys()) - {'__qualname__'})   # for python 2 and 3


sys.modules[__name__] = mod_call()

这个解是通过对类似问题的 回答的讨论得到的。

将解决方案转化为方便的可重用功能:

def set_module(cls, __name__):
import sys


class cls2(sys.modules[__name__].__class__, cls):
pass


sys.modules[__name__].__class__ = cls2

保存到,比如说 util.py,然后在你的模块中,

import util


class MyModule:
def __call__(self):  # module callable
return 42


util.set_module(MyModule, __name__)

万岁!

我写这篇文章是因为我需要用这个技巧增强许多模块。


附注: 在我写完这个答案几天后,我从代码中删除了这个技巧,因为对于像 Pylint 这样的工具来说,理解这个技巧是非常棘手的。

使用 Python 3.10.8版,我可以按照下面的格式设置代码:

  • 使我的模块可调用(谢谢 Alex)
  • 保持模块的所有属性可访问(例如方法)(谢谢 Nick)
  • 允许模块在作为 from CallableModule import x, y, z使用时工作,尽管不是作为 from CallableModule import *(我试图使用 弗里德里希的解决方案)
    from types import ModuleType
    

class CallableModule(ModuleType):
def __init__(self):
ModuleType.__init__(self, __name__)
self.__dict__.update(modules[__name__].__dict__)
    

def __call__(self):
print("You just called a module UwU")
      

mod_call= __call__
__all__= list(set(vars().keys()) - {'__qualname__'})
modules[__name__]= CallableModule()