web-dev-qa-db-ja.com

モジュールの__getattr__

__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 __== "__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
111
Matt Joiner

しばらく前に、Guidoは、新しいスタイルのクラスでのすべての特別なメソッド検索が___getattr___と___getattribute___ をバイパスすると宣言しました。 Dunderメソッドは以前モジュールで機能していました-たとえば、それらのトリック broke の前に___enter___および___exit___を定義するだけで、モジュールをコンテキストマネージャーとして使用できます。

最近、いくつかの歴史的機能が復活しました。モジュール___getattr___はその中にあるため、既存のハック(インポート時に_sys.modules_のクラスでそれ自体を置き換えるモジュール)はもう必要ありません。

Python 3.7+では、1つの明白な方法を使用します。モジュールの属性アクセスをカスタマイズするには、モジュールレベルで___getattr___関数を定義します。属性)、計算された値を返すか、AttributeErrorを上げます:

_# my_module.py

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

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

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

24
wim

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

  1. __xxx__メソッドはクラスでのみ検索されます
  2. TypeError: can't set attributes of built-in/extension type 'module'

(1)解決策は、どのモジュールが検査されているかを追跡する必要があることを意味します。そうでない場合、everyモジュールはインスタンス置換動作を持ちます。 (2)は(1)は不可能です...少なくとも直接ではありません。

幸いなことに、sys.modulesはそこに何が行き着くのか気にせず、ラッパーが機能しますが、モジュールアクセスに対してのみです(つまりimport somemodule; somemodule.salutation('world');同じモジュールアクセスの場合、置換クラスのメソッドをヤンクする必要がありますクラスのカスタムメソッド(globals()を使用するのが好きです)または汎用関数(既に回答としてリストされている関数など)で.export() eiherに追加します。念頭に置いてください:ラッパーが毎回新しいインスタンスを作成し、グローバルソリューションが毎回作成しない場合、微妙に異なる振る舞いになります。ああ、両方を同時に使用することはできません-それは1つまたはその他。


更新

Guido van Rossum から:

実際には、時々使用され推奨されるハックがあります:モジュールは、目的の機能を備えたクラスを定義し、最後に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__で遊ぶことができます。 。

この機能を使用する場合、モジュール内の他のもの(グローバル、他の関数など)は、sys.modules割り当てが行われたときに失われることに注意してください。

107
Ethan Furman

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

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__])
45
Håvard S

通常、そのようなことはしません。

私たちがしているのはこれです。

class A(object):
....

# The implicit global instance
a= A()

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

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

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

19
S.Lott

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

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

13
Matt Anderson

これはハックですが、...

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 __== "__main__":
    salutation("world")
    farewell("goodbye", "world")

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

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

実稼働コードではこれを使用しませんが、開始する必要があります。

7
grieve

私自身の謙虚な貢献は次のとおりです-@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 __== "__main__":
    _globals.salutation("world")
4
martineau