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

Реализация шаблона декоратора в Python

Я хочу реализовать шаблон decorator в Python, и я задавался вопросом, есть ли способ написать декоратор, который просто реализует функцию it хочет изменить, без написания котельной плиты для всех функций, которые только что переданы украшенному объекту. Например:

class foo(object):
    def f1(self):
        print "original f1"
    def f2(self):
        print "original f2"

class foo_decorator(object):
    def __init__(self, decoratee):
        self._decoratee = decoratee
    def f1(self):
        print "decorated f1"
        self._decoratee.f1()
    def f2(self):              # I would like to leave that part out
        self._decoratee.f2()

Я бы хотел, чтобы звонки в foo_decorator.f2 были перенаправлены на decoratee.f2 автоматически. Есть ли способ написать общий метод, который перенаправляет все нереализованные вызовы функций на decoratee?

4b9b3361

Ответ 1

Вы можете использовать __getattr__:

class foo(object):
    def f1(self):
        print "original f1"
    def f2(self):
        print "original f2"

class foo_decorator(object):
    def __init__(self, decoratee):
        self._decoratee = decoratee
    def f1(self):
        print "decorated f1"
        self._decoratee.f1()
    def __getattr__(self, name):
        return getattr(self._decoratee, name)

u = foo()
v = foo_decorator(u)
v.f1()
v.f2()

Ответ 2

Как дополнение к ответу Филиппа; если вам нужно не только украшать, но и сохранять тип объекта, Python позволяет подклассифицировать экземпляр во время выполнения:

class foo(object):
    def f1(self):
        print "original f1"

    def f2(self):
        print "original f2"


class foo_decorator(object):
    def __new__(cls, decoratee):
        cls = type('decorated',
                   (foo_decorator, decoratee.__class__),
                   decoratee.__dict__)
        return object.__new__(cls)

    def f1(self):
        print "decorated f1"
        super(foo_decorator, self).f1()


u = foo()
v = foo_decorator(u)
v.f1()
v.f2()
print 'isinstance(v, foo) ==', isinstance(v, foo)

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

Этого может быть достаточно:

class foo_decorator(foo):
    def __init__(self, decoratee):
        self.__dict__.update(decoratee.__dict__)

    def f1(self):
        print "decorated f1"
        super(foo_decorator, self).f1()

Ответ 3

Это, возможно, не самая лучшая практика, но вы можете добавить функциональность к экземплярам, ​​как я сделал, чтобы помочь перевести мой код из Django ORM в SQLAlachemy следующим образом:

def _save(self):
    session.add(self)
    session.commit()
setattr(Base,'save',_save)

Ответ 4

Диаграмма UML в связанной статье Wikipedia неверна, и ваш код тоже.

Если вы следуете "рисунку декоратора", класс декоратора происходит от базового украшенного класса. (В UML-диаграмме отсутствует стрелка наследования от WindowDecorator до Window).

с

class foo_decorator(foo):

вам не нужно реализовывать неразделенные методы.

BTW: У сильных типизированных языков есть еще одна причина, почему декоратор должен быть выведен из декорированного класса: иначе вы не смогли бы нарисовать декораторы.

Ответ 5

В дополнение к @Alec Thomas ответ. Я изменил его ответ, чтобы следовать шаблону декоратора. Таким образом, вам не нужно знать класс, который вы украшаете заранее.

class Decorator(object):
    def __new__(cls, decoratee):
        cls = type('decorated',
                   (cls, decoratee.__class__),
                   decoratee.__dict__)
        return object.__new__(cls)

Затем вы можете использовать его как:

class SpecificDecorator(Decorator):
    def f1(self):
        print "decorated f1"
        super(foo_decorator, self).f1()

class Decorated(object):
    def f1(self):
        print "original f1"


d = SpecificDecorator(Decorated())
d.f1()

Ответ 6

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

Вариант использования:

  • У меня есть объект X с методами A и B.
  • Я создаю класс декоратора Y, который переопределяет A.
  • Если я создаю экземпляр Y (X) и звоню A, он будет использовать украшенный A, как и ожидалось.
  • Если B вызывает A, то, если я создаю Y (X) и назову B на декораторе, вызов изнутри B затем переходит к старому A на исходном объекте, который был нежелательным. Я хочу, чтобы старый B также вызвал новый A.

Это поведение можно выполнить следующим образом:

import inspect
import six      # for handling 2-3 compatibility

class MyBaseDecorator(object):
    def __init__(self, decorated):
        self.decorated = decorated

    def __getattr__(self, attr):
       value = getattr(self.decorated, attr)
       if inspect.ismethod(value):
           function = six.get_method_function(value)
           value = function.__get__(self, type(self))
       return value

class SomeObject(object):
    def a(self):
        pass

    def b(self):
        pass

class MyDecorator(MyBaseDecorator):
    def a(self):
        pass

decorated = MyDecorator(SomeObject())

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

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

Эффект от этого заключается в том, что если b когда-либо вызывает a на исходном объекте, тогда, когда у вас есть украшенный объект и есть какой-либо вызов метода, поступающий от декоратора, декоратор гарантирует, что все доступные методы доступа связанный с декоратором, поэтому, глядя на вещи, используя декоратор, а не на оригинальный объект, поэтому методы, указанные в декораторе, имеют приоритет.

P.S.: Да, я знаю, что это похоже на наследование, но это сделано в смысле состава нескольких объектов.