Реализация шаблона декоратора в 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
?
Ответы
Ответ 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.: Да, я знаю, что это похоже на наследование, но это сделано в смысле состава нескольких объектов.