web-dev-qa-db-ja.com

呼び出し可能なモジュール

なぜ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
_
49
Roger Pate

特別なメソッドは、インスタンスではなく、型で定義されている場合にのみ暗黙的に呼び出されることが保証されています。 (__call__はモジュールインスタンスmod_callの属性であり、<type 'module'>の属性ではありません。)組み込み型にメソッドを追加することはできません。

http://docs.python.org/reference/datamodel.html#special-method-lookup-for-new-style-classes

34
Miles

Pythonでは、モジュールがany magicメソッドをオーバーライドまたは追加することはできません。モジュールオブジェクトをシンプル、規則的、軽量に保つことは、魔法のメソッドを使用できる強力なユースケースがめったに表示されないことを考えると、あまりにも有利だからです。

このようなユースケースdoが表示された場合の解決策は、クラスインスタンスをモジュールに見せかけることです。具体的には、mod_call.pyを次のようにコーディングします。

import sys
class mod_call(object):
  def __call__(self):
    return 42
sys.modules[__name__] = mod_call()

これで、コードのインポートとmod_callの呼び出しが正常に機能します。

81
Alex Martelli

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でテスト済み。

9

ChristophBö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__メソッドを使用してサブクラスに「キャスト」するだけであるためです。これはまさに私たちが望んでいることです。)

これをPythonバージョン3.5未満で同様に機能させるには、 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の回答に従っている場合は存在しません)、インポートされたモジュールオブジェクトは引き続き「モジュール」です。

1
Nick Matteo