Реализация шаблона декоратора в Python - PullRequest
25 голосов
/ 25 июня 2010

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

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?

Ответы [ 6 ]

32 голосов
/ 25 июня 2010

Вы можете использовать __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()
9 голосов
/ 30 июля 2010

В качестве дополнения к ответу Филиппа;если вам нужно не только украшать, но и сохранять тип объекта, 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()
2 голосов
/ 30 июля 2010

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

Если вы следуете «шаблону декоратора», класс декоратора получен из базового декорированного класса.(На диаграмме UML отсутствует стрелка наследования от WindowDecorator к Window).

с

class foo_decorator(foo):

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

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

2 голосов
/ 25 июня 2010

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

def _save(self):
    session.add(self)
    session.commit()
setattr(Base,'save',_save)
0 голосов
/ 06 декабря 2017

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

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

  • У меня есть объект X с методами A и B.
  • Я создаю декоратор класса Y, который переопределяет A.
  • Если я создам экземпляр Y (X) и вызову A, он будет использовать декорированный A, как и ожидалось.
  • Если B вызывает A, то если я создаю экземпляр Y (X) и вызываю B на декораторе, то вызов изнутри 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 на исходном объекте, то когда у вас оформлен объект и есть какой-либо вызов метода, исходящий от декоратора, декоратор гарантирует, что все методы, к которым получен доступ, вместо этого он привязан к декоратору, поэтому ищет вещи, используя декоратор, а не исходный объект, поэтому методы, указанные в декораторе, имеют приоритет.

П.С .: Да, я знаю, это выглядит как наследование, но это сделано в смысле объединения нескольких объектов.

0 голосов
/ 28 марта 2015

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

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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...