特定のクラスのすべてのメソッドをPythonでラップしたいのですが、クラスのコードを最小限に編集することでラップしたいと思います。これについてはどうすればよいですか?
3 に答える
これを行うためのエレガントな方法は、Michael FoordのVoidspaceブログのメタクラスとは何か、およびメタクラスを 装飾するメソッドというタイトルのセクションでそれらを使用する方法についてのエントリで説明されています。少し単純化して状況に適用すると、次のようになります。
from functools import wraps
from types import FunctionType
def wrapper(method):
@wraps(method)
def wrapped(*args, **kwargs):
# ... <do something to/with "method" or the result of calling it>
return wrapped
class MetaClass(type):
def __new__(meta, classname, bases, classDict):
newClassDict = {}
for attributeName, attribute in classDict.items():
if isinstance(attribute, FunctionType):
# replace it with a wrapped version
attribute = wrapper(attribute)
newClassDict[attributeName] = attribute
return type.__new__(meta, classname, bases, newClassDict)
class MyClass(object):
__metaclass__ = MetaClass # wrap all the methods
def method1(self, ...):
# ...etc ...
Pythonでは、関数/メソッドデコレータは、関数ラッパーに加えて、それらを簡単に(そしてよりきれいに)使用できるようにするための構文糖衣です。
Python3互換性アップデート
前のコードはPython2.xメタクラス構文を使用しており、Python 3.xで使用するには変換する必要がありますが、以前のバージョンでは機能しなくなります。これは、以下を使用する必要があることを意味します。
class MyClass(metaclass=MetaClass) # apply method-wrapping metaclass
...
それ以外の:
class MyClass(object):
__metaclass__ = MetaClass # wrap all the methods
...
必要に応じて、Python 2.xと3.xの両方と互換性のあるコードを記述できますが、そのためには、目的のメタクラスを継承する新しい基本クラスを動的に作成する少し複雑な手法を使用する必要があります。 Pythonの2つのバージョン間の構文の違い。これは基本的に、ベンジャミンピーターソンの6つのモジュールのwith_metaclass()
機能が行うことです。
from types import FunctionType
from functools import wraps
def wrapper(method):
@wraps(method)
def wrapped(*args, **kwargs):
print('{!r} executing'.format(method.__name__))
return method(*args, **kwargs)
return wrapped
class MetaClass(type):
def __new__(meta, classname, bases, classDict):
newClassDict = {}
for attributeName, attribute in classDict.items():
if isinstance(attribute, FunctionType):
# replace it with a wrapped version
attribute = wrapper(attribute)
newClassDict[attributeName] = attribute
return type.__new__(meta, classname, bases, newClassDict)
def with_metaclass(meta):
""" Create an empty class with the supplied bases and metaclass. """
return type.__new__(meta, "TempBaseClass", (object,), {})
if __name__ == '__main__':
# Inherit metaclass from a dynamically-created base class.
class MyClass(with_metaclass(MetaClass)):
@staticmethod
def a_static_method():
pass
@classmethod
def a_class_method(cls):
pass
def a_method(self):
pass
instance = MyClass()
instance.a_static_method() # Not decorated.
instance.a_class_method() # Not decorated.
instance.a_method() # -> 'a_method' executing
プログラムでクラスのメソッドにラッパーを設定するという意味ですか?まあ、これはおそらく本当に悪い習慣ですが、これがあなたがそれをするかもしれない方法です:
def wrap_methods( cls, wrapper ):
for key, value in cls.__dict__.items( ):
if hasattr( value, '__call__' ):
setattr( cls, key, wrapper( value ) )
たとえば、クラスがある場合
class Test( ):
def fire( self ):
return True
def fire2( self ):
return True
とラッパー
def wrapper( fn ):
def result( *args, **kwargs ):
print 'TEST'
return fn( *args, **kwargs )
return result
その後、呼び出します
wrap_methods( Test, wrapper )
クラスで定義されたすべてのメソッドに適用wrapper
されます。注意して使用してください!実際には、まったく使用しないでください。Test
デフォルトのクラスの動作を大幅に変更する必要がある場合は、MetaClassesが最適です。別のアプローチがあります。
__getattribute__
ユースケースがクラスのインスタンスメソッドのラップのみに限定されている場合は、マジックメソッドをオーバーライドしてみてください。
from functools import wraps
def wrapper(func):
@wraps(func)
def wrapped(*args, **kwargs):
print "Inside Wrapper. calling method %s now..."%(func.__name__)
return func(*args, **kwargs)
return wrapped
ラッパーを作成するときは必ず使用してください。functools.wraps
ラッパーが適切なトレースバックを提供するため、ラッパーがデバッグ用である場合はさらに使用してください。
import types
class MyClass(object): # works only for new-style classes
def method1(self):
return "Inside method1"
def __getattribute__(self, name):
attr = super(MyClass, self).__getattribute__(name)
if type(attr) == types.MethodType:
attr = wrapper(attr)
return attr