Я хотел бы обернуть каждый метод определенного класса в python, и я хотел бы сделать это, минимально отредактировав код класса. Как мне это сделать?
Как обернуть все методы класса?
Ответ 1
Элегантный способ сделать это описан в блоге Michael Foord Voidspace в статье о том, что такое метаклассы и как их использовать, в разделе, озаглавленном Метод декорирования метакласса. Небольшое упрощение и применение к вашей ситуации привело к следующему:
from types import FunctionType
from functools import wraps
def wrapper(method):
@wraps(method)
def wrapped(*args, **kwrds):
# ... <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 декораторы функций/методов - это просто обертки функций плюс некоторый синтаксический сахар, облегчающий их использование (и более привлекательный).
Обновление совместимости с Python 3
В предыдущем коде используется синтаксис метакласса Python 2.x, который необходимо будет перевести для использования в Python 3.x, однако он больше не будет работать в предыдущей версии. Это значит, что нужно будет использовать:
class MyBase(metaclass=MetaClass)
...
вместо:
class MyBase(object):
__metaclass__ = MetaClass"
...
При желании можно написать код, который совместим как с Python 2.x, так и с 3.x, но для этого необходимо использовать немного более сложную технику, которая динамически создает новый базовый класс, который наследует требуемый метакласс, тем самым избегая ошибки из-за синтаксических различий между двумя версиями Python. Это в основном то, что делает функция Бенджамина Петерсона шесть module with_metaclass()
.
from types import FunctionType
from functools import wraps
def wrapper(method):
@wraps(method)
def wrapped(*args, **kwrds):
print('{!r} executing'.format(method.__name__))
return method(*args, **kwrds)
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
Ответ 2
Вы имеете в виду программно установить оболочку методам класса? Ну, это, пожалуй, очень плохая практика, но вот как вы можете это сделать:
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
to all, определенные в классе Test
. Используйте с осторожностью! На самом деле, не используйте его вообще!
Ответ 3
Если требуется широкомасштабное изменение поведения класса по умолчанию, 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
при создании оберток, тем более, если оболочка предназначена для отладки, поскольку она обеспечивает разумные TraceBacks.
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
Ответ 4
Использование декораторов python - самый чистый способ сделать это, поскольку похоже, что вы хотите отлаживать или, по крайней мере, отслеживать код, который он появляется.