モジュールに対する__getattr__


127

__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

2
それはすべての状況で機能するので、私はおそらく悲しみの答えで行くでしょう(それは少し厄介であり、より良く行うことができますが)。Harvard SとS Lottには明確な答えがありますが、実際的な解決策ではありません。
マットジョイナー、

1
あなたはあなたのケースでは属性にアクセスすることさえしていませんので、あなたは一度に2つの異なることを求めています。したがって、主要な問題は、どちらが必要かということです。あなたがしたいですかsalutationグローバルまたはローカルの名前空間に存在する(上記のコードがやろうとしているものである)か、モジュール上のドットのアクセスを行う際には、名前の動的な検索をしたいですか?それは2つの異なるものです。
Lennart Regebro、2011年

興味深い質問ですが、どうやってこれを思いついたのですか?
クリスウェッセリング2010年


1
__getattr__上のモジュールはPython 3.7からサポートされています
浜村史人

回答:


41

少し前に、Guidoは、新しいスタイルのクラスでのすべての特別なメソッドルックアップは__getattr__およびをバイパスすることを宣言しました__getattribute__。Dunder方法は、以前のモジュールで働いていた-あなたは、例えば、単純に定義することで、コンテキストマネージャとしてモジュールを使用することができます__enter__し、__exit__これらのトリックを前に、破りました

最近、いくつかの歴史的な機能が復活し、__getattr__それらの中でモジュールが登場しました。そのため、既存のハック(sys.modulesインポート時にモジュールをそれ自体をクラスに置き換えるモジュール)は必要なくなりました。

Python 3.7以降では、1つの明白な方法を使用するだけです。モジュールの属性アクセスをカスタマイズするに__getattr__は、1つの引数(属性の名前)を受け入れる関数をモジュールレベルで定義し、計算された値を返すか、AttributeError

# my_module.py

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

これにより、「from」インポートへのフックも可能になります。つまり、などのステートメントに対して動的に生成されたオブジェクトを返すことができますfrom my_module import whatever

関連する注記として、モジュールgetattrとともに、__dir__モジュールレベルで関数を定義してに応答することもできますdir(my_module)。詳細については、PEP 562を参照してください。


1
私が動的にモジュールを作成してm = ModuleType("mod")設定した場合m.__getattr__ = lambda attr: return "foo"、ただし、実行するfrom mod import fooとが表示されますTypeError: 'module' object is not iterable
weberc2

@ weberc2:それを作成しm.__getattr__ = lambda attr: "foo"、さらにでモジュールのエントリを定義する必要がありますsys.modules['mod'] = m。その後、でエラーは発生しませんfrom mod import foo
マルティノー

wim:動的に計算された値(モジュールレベルのプロパティを持つなど)を取得してmy_module.whatever、それを(の後にimport my_module)呼び出すこともできます。
マルティノー

115

ここで発生している2つの基本的な問題があります。

  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()それぞれに追加する必要があります)クラスのカスタムメソッド(私はを使用するのが好き.export())またはジェネリック関数(すでに回答としてリストされているものなど)を使用します。注意すべき点の1つは、ラッパーが毎回新しいインスタンスを作成していて、グローバルソリューションがそうでない場合、ああ、同時に両方を使用することはできません-どちらか一方です。


更新

グイド・ファン・ロッサムから:

実際に時々使用され、推奨されるハックがあります。モジュールは、必要な機能を備えたクラスを定義し、最後に、sys.modules内の自身をそのクラスのインスタンス(または主張する場合はクラス)で置き換えることができます。 、しかしそれは一般的にあまり役に立たない)。例えば:

# module foo.py

import sys

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

sys.modules[__name__] = Foo()

これが機能するのは、インポート機構がこのハックを積極的に有効にしているためです。最後のステップとして、実際のモジュールをロードした後、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__set()Pythonの2の互換性が必要とされていない場合は省略することができます。


3
インポート機構がこのハックを積極的に有効にしているため、これは機能します。最後のステップとして、実際のモジュールをsys.modulesからプルします。ロードした後、ドキュメントのどこかに記載されていますか?
Piotr Dobrogost 2012

4
このハックを「準認定」されていると考えると、このハックを使用することで安心できるようになりました:)
Mark Nunberg

3
これは、ギブフォーギブのような愚かなことimport sysNoneしていsysます。このハッキングはPython 2では許可されていないと
思います

3
@asmeurer:その理由(および解決策)を理解するには、sys_modules [__ name__]への割り当て後に__name__の値が変化する理由を参照してください
martineau 14

1
@qarma:Pythonモジュールがクラス継承モデルに直接参加できるようになると言われているいくつかの拡張機能を思い出したようですが、それでもこのメソッドは機能し、サポートされています。
イーサンファーマン2015年

48

これはハックですが、クラスでモジュールをラップすることができます:

class Wrapper(object):
  def __init__(self, wrapped):
    self.wrapped = wrapped
  def __getattr__(self, name):
    # Perform custom logic here
    try:
      return getattr(self.wrapped, name)
    except AttributeError:
      return 'default' # Some sensible default

sys.modules[__name__] = Wrapper(sys.modules[__name__])

10
nice and dirty:D
Matt Joiner

それはうまくいくかもしれませんが、おそらく著者の本当の問題の解決策ではありません。
DasIch

12
「動作する可能性があります」と「おそらく動作しない」はあまり役に立ちません。それはハック/トリックですが、機能し、質問によって提起された問題を解決します。
–HåvardS 2010

6
これは、モジュールをインポートし、モジュールに存在しない属性にアクセスする他のモジュールで機能しますが、ここでの実際のコード例では機能しません。globals()へのアクセスはsys.modulesを経由しません。
マリウスジェドミナス

5
残念ながら、これは現在のモジュールでは機能しませんimport *
マットジョイナー、

19

私たちは通常そのようにはしません。

私たちがすることはこれです。

class A(object):
....

# The implicit global instance
a= A()

def salutation( *arg, **kw ):
    a.salutation( *arg, **kw )

どうして?したがって、暗黙的なグローバルインスタンスが表示されます。

例として、randomモジュールを見てください。これは、暗黙的なグローバルインスタンスを作成して、「単純な」乱数ジェネレータが必要なユースケースをわずかに簡略化します。


あなたがしている場合は本当に野心的な、あなたはクラスを作成し、反復そのすべてのメソッドを介して、各メソッドのモジュールレベル関数を作成することができます。
ポールフィッシャー

@Paul Fisher:問題ごとに、クラスはすでに存在しています。クラスのすべてのメソッドを公開するのは良い考えではありません。通常、これらの公開メソッドは「便利な」メソッドです。すべてが暗黙的なグローバルインスタンスに適しているわけではありません。
S.Lott

13

@HåvardSが提案したのと同様に、モジュール(など__getattr__)に魔法を実装する必要がある場合は、それを継承しtypes.ModuleTypeて配置する新しいクラスを定義しますsys.modules(おそらく、カスタムModuleTypeが定義されたモジュールを置き換えます)。

これのかなり堅牢な実装については__init__.pyWerkzeugのメインファイルを参照してください。


8

これはハッカですが...

import types

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

    def farewell(self, greeting, accusative):
         print greeting, accusative

def AddGlobalAttribute(classname, methodname):
    print "Adding " + classname + "." + methodname + "()"
    def genericFunction(*args):
        return globals()[classname]().__getattribute__(methodname)(*args)
    globals()[methodname] = genericFunction

# set up the global namespace

x = 0   # X and Y are here to add them implicitly to globals, so
y = 0   # globals does not change as we iterate over it.

toAdd = []

def isCallableMethod(classname, methodname):
    someclass = globals()[classname]()
    something = someclass.__getattribute__(methodname)
    return callable(something)


for x in globals():
    print "Looking at", x
    if isinstance(globals()[x], (types.ClassType, type)):
        print "Found Class:", x
        for y in dir(globals()[x]):
            if y.find("__") == -1: # hack to ignore default methods
                if isCallableMethod(x,y):
                    if y not in globals(): # don't override existing global names
                        toAdd.append((x,y))


for x in toAdd:
    AddGlobalAttribute(*x)


if __name__ == "__main__":
    salutation("world")
    farewell("goodbye", "world")

これは、グローバル名前空間のすべてのオブジェクトを反復処理することで機能します。アイテムがクラスの場合、クラス属性を反復処理します。属性が呼び出し可能である場合、それは関数としてグローバル名前空間に追加されます。

「__」を含むすべての属性を無視します。

私はこれを量産コードでは使用しませんが、それであなたは始められるはずです。


2
見た目がすっきりしているので、私はHåvardSの答えを好みますが、これは質問どおりに直接答えます。
2010

これは私が最終的に行ったものに非常に近いものです。少し面倒ですが、同じモジュール内でglobals()を正しく処理します。
マットジョイナー、

1
この答えは、「モジュールの静的に定義された属性に存在しない関数を呼び出すとき」が無条件に機能し、可能なすべてのクラスメソッドを追加しているため、この答えが求められたものではないようです。これは、@HåvardSのロジックの逆のようなAddGlobalAttribute()、モジュールレベルがある場合にのみ実行するモジュールラッパーを使用して修正できますAttributeError。OPが既にこの回答を受け入れている場合でも、機会があればこれをテストし、独自のハイブリッド回答を追加します。
martineau

以前のコメントを更新します。私は今NameError、グローバル(モジュール)ネームスペースの例外をインターセプトすることが非常に難しい(不可能ですか?)
martineau

5

ここに私自身の控えめな貢献があります-@HåvardSの高評価の回答を少し装飾したものですが、もう少し明示的です(そのため、おそらくOPには十分ではありませんが、@ S.Lottには受け入れられるかもしれません)。

import sys

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

class Wrapper(object):
    def __init__(self, wrapped):
        self.wrapped = wrapped

    def __getattr__(self, name):
        try:
            return getattr(self.wrapped, name)
        except AttributeError:
            return getattr(A(), name)

_globals = sys.modules[__name__] = Wrapper(sys.modules[__name__])

if __name__ == "__main__":
    _globals.salutation("world")

-3

クラスを含むモジュールファイルを作成します。モジュールをインポートします。getattrインポートしたモジュールで実行します。を使用して動的インポートを実行し、__import__sys.modulesからモジュールをプルできます。

ここにあなたのモジュールがありますsome_module.py

class Foo(object):
    pass

class Bar(object):
    pass

そして別のモジュールで:

import some_module

Foo = getattr(some_module, 'Foo')

これを動的に行う:

import sys

__import__('some_module')
mod = sys.modules['some_module']
Foo = getattr(mod, 'Foo')

1
ここで別の質問に答えています。
Marius Gedminas
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.