Подтвердить что ты не робот

Как обернуть все методы класса?

Я хотел бы обернуть каждый метод определенного класса в python, и я хотел бы сделать это, минимально отредактировав код класса. Как мне это сделать?

4b9b3361

Ответ 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 - самый чистый способ сделать это, поскольку похоже, что вы хотите отлаживать или, по крайней мере, отслеживать код, который он появляется.