71

为什么 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
4

6 回答 6

106

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

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

import sys

class mod_call:
    def __call__(self):
        return 42

sys.modules[__name__] = mod_call()

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

于 2009-06-29T22:20:06.047 回答
44

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

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

于 2009-06-29T22:18:56.673 回答
20

正如 Miles 所说,您需要在类级别定义调用。因此,Alex post 的替代方法是将 of 类更改为(It should be )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 测试。

于 2018-01-04T17:22:43.067 回答
10

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__定义了其他模块属性(如果只是按照亚历克斯的回答则不存在),并且您导入的模块对象仍然是“是”模块。

于 2019-03-14T18:55:24.153 回答
4

所有答案仅适用于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()

该解决方案是通过对类似问题的答案的讨论得出的。

于 2020-05-05T16:53:37.817 回答
2

要将解决方案转换为方便的可重用函数:

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__)

欢呼!

我写这个是因为我需要用这个技巧来增强很多模块。


PS 在我写完这个答案几天后,我从我的代码中删除了这个技巧,因为像 Pylint 这样的工具很难理解。

于 2021-08-03T11:07:22.820 回答