Украшение метода класса после @property - PullRequest
8 голосов
/ 07 февраля 2011

Я хочу обернуть каждый метод различных объектов, кроме __init__, используя декоратор.

class MyObject(object):

    def method(self):
        print "method called on %s" % str(self)

    @property
    def result(self):
        return "Some derived property"

def my_decorator(func):
    def _wrapped(*args, **kwargs):
        print "Calling decorated function %s" % func
        return func(*args, **kwargs)
    return _wrapped


class WrappedObject(object):

    def __init__(self, cls):
        for attr, item in cls.__dict__.items():
            if attr != '__init__' and (callable(item) or isinstance(item, property)):
                setattr(cls, attr, my_decorator(item))
        self._cls = cls

    def __call__(self, *args, **kwargs):
        return self._cls(*args, **kwargs)

inst = WrappedObject(MyObject)()

Однако перенос результатов экземпляра свойства эквивалентен следующему:

@my_decorator
@property
def result(self):
    return "Some derived property"

Когда желаемый результат эквивалентен этому:

@property
@my_decorator
def result(self):
    return "Some derived property"

Кажется, что атрибуты объекта свойства доступны только для чтения и не позволяют изменять функцию после того, как свойство обернуло ее. Я не слишком доволен уже требуемым уровнем взлома, и в любом случае я бы не стал углубляться в объект свойства.

Единственное другое решение, которое я вижу, - это создание метакласса на лету, которого я надеялся избежать. Я что-то упускаю из виду?

Ответы [ 3 ]

5 голосов
/ 07 февраля 2011

В этом образце есть несколько других проблем, но, чтобы ответить на вопрос, все, что вам нужно сделать, это когда вы оборачиваете свойство

Когда вы оборачиваете свойство, вместо этого оборачивайте его метод __get__:

class MyObject(object):

    def method(self):
        print "method called on %s" % str(self)

    @property
    def result(self):
        return "Some derived property"

    def common(self, a=None):
        print self

def my_decorator(func):
    def _wrapped(*args, **kwargs):
        print "Calling decorated function %s" % func
        return func(*args, **kwargs)
    return _wrapped


class WrappedObject(object):

    def __init__(self, cls):
        for attr, item in cls.__dict__.items():
            if attr != '__init__' and callable(item):
                setattr(cls, attr, my_decorator(item))
            elif  isinstance(item, property):
                new_property = property(my_decorator(item.__get__), item.__set__, item.__delattr__)
                setattr(cls, attr, new_property)
        self._cls = cls

    def __call__(self, *args, **kwargs):
        return self._cls(*args, **kwargs)

inst = WrappedObject(MyObject)()

Это самая простая модификация вашего кода, которая выполняет эту работу.Однако я бы изменил его на динамически подкласс класса, который он оборачивает, чтобы избежать перезаписи его атрибутов.Вы можете создать подкласс программным способом, просто вызвав тип с именем, кортежем с базами и dict в качестве параметров.

edit - изменение кода на класс с подклассом

Фактически, подклассданный класс почти не требует модификации данного кода, но для указанного мной вызова type.Я только что проверил это здесь - измените ваш класс WrappedObject на:

class WrappedObject(object):

    def __init__(self, cls):
        dct = cls.__dict__.copy()
        for attr, item in dct.items():
            if attr != '__init__' and callable(item):
                dct[attr] =  my_decorator(item)
            elif  isinstance(item, property):
                new_property = property(my_decorator(item.__get__), item.__set__, item.__delattr__)
                dct[attr] = new_property
        self._cls = type("wrapped_" + cls.__name__, (cls,), dct)

    def __call__(self, *args, **kwargs):
        return self._cls(*args, **kwargs)
1 голос
/ 07 февраля 2011

После небольшого количества попыток я нашел следующее решение.Сначала создайте вспомогательный класс, который будет эмулировать оформленный дескриптор:

class DecoratedDescriptor(object):

    def __init__(self, descriptor, decorator):
        self.funcs = {}
        for attrname in '__get__', '__set__', '__delete__':
            self.funcs[attrname] = decorator(getattr(descriptor, attrname))

    def __get__(self, *args, **kwargs):
        return self.funcs['__get__'](*args, **kwargs)

    def __set__(self, *args, **kwargs):
        return self.funcs['__set__'](*args, **kwargs)

    def __delete__(self, *args, **kwargs):
        return self.funcs['__delete__'](*args, **kwargs)

Затем, если вы видите свойство, оберните его в него:

# Fragment of your WrappedObject.__init__ method:
if attr != '__init__' and callable(item):
    setattr(cls, attr, my_decorator(item))
elif isinstance(item, property):
    setattr(cls, attr, DecoratedDescriptor(item, my_decorator))

Это работает так:

>>> inst = WrappedObject(MyObject)()
>>> print inst.result
Calling decorated function <method-wrapper '__get__' of property object at 0x00BB6930>
Some derived property

Есть!Нет метаклассов:)

0 голосов
/ 07 февраля 2011

Вы можете ввести «ленивые» декораторы, которые применяются после вашего собственного декоратора, например:

class Lazy(object):
    def __init__(self, decorator):
        self.decorator = decorator
    def __call__(self, method):
        self.method = method
        return self

def my_decorator(func):
    def _wrapped(*args, **kwargs):
        print "Calling decorated function %s" % func
        return func(*args, **kwargs)
    if isinstance(func, Lazy):
        lazy = func
        func = lazy.method
        return lazy.decorator(_wrapped)
    return _wrapped

lazy_property = Lazy(property)

.. и затем используйте @lazy_property вместо @property. (Предупреждение: непроверенный код, но я надеюсь, что вы поняли идею ...)

...