__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
しばらく前に、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 をご覧ください。
ここで発生している2つの基本的な問題があります。
__xxx__
メソッドはクラスでのみ検索されます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
割り当てが行われたときに失われることに注意してください。
これはハックですが、モジュールをクラスでラップできます:
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__])
通常、そのようなことはしません。
私たちがしているのはこれです。
class A(object):
....
# The implicit global instance
a= A()
def salutation( *arg, **kw ):
a.salutation( *arg, **kw )
どうして?そのため、暗黙のグローバルインスタンスが表示されます。
たとえば、random
モジュールを見てください。これは、暗黙的なグローバルインスタンスを作成し、「単純な」乱数ジェネレーターが必要なユースケースをわずかに単純化します。
@HåvardSが提案したものと同様に、モジュールにマジックを実装する必要がある場合(__getattr__
など)、types.ModuleType
を継承する新しいクラスを定義し、sys.modules
(おそらくカスタムModuleType
が定義されたモジュールを置き換えます)。
これのかなり堅牢な実装については、メインの __init__.py
Werkzeug のファイルを参照してください。
これはハックですが、...
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")
これは、グローバル名前空間内のすべてのオブジェクトを反復処理することで機能します。アイテムがクラスの場合、クラス属性を反復処理します。属性が呼び出し可能であれば、関数としてグローバル名前空間に追加されます。
「__」を含むすべての属性を無視します。
実稼働コードではこれを使用しませんが、開始する必要があります。
私自身の謙虚な貢献は次のとおりです-@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")