2010-06-25 115 views
21

我想在Python中實現decorator pattern,我想知道是否有辦法編寫一個裝飾器來實現它想要修改的功能,而無需爲所有的函數只是被轉發到裝飾對象。像這樣:在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() 

我想有轉發給decoratee.f2自動foo_decorator.f2電話。有沒有辦法編寫一個將所有未實現函數調用轉發到decoratee的泛型方法?

+1

你可以舉一個簡單的子類化不會起作用的例子嗎?你也可以動態地進行子類化 - 這種模式看起來像是一種解決不了那些不支持多重繼承的語言的解決方法。 – 2010-06-25 15:26:45

+0

我想在運行時裝飾對象。我想將不同的裝飾器應用於一個對象,並能夠再次移除它們。子類創建後不能更改它,或者它可以嗎? – 2010-06-25 15:50:55

+0

如果你有類'A'並改變'A',即增加一個新方法,'A.foo = lambda self:self',這將反映在A ..的所有實例上,因爲* everything *是在運行時確定的。偉大的方式來產生絕對不可維護的代碼。 – 2010-06-25 17:01:02

回答

30

您可以使用__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() 
+1

這是一個好主意。但是,應該注意的是,如果您使用抽象基類(即導入abc並使用__metaclass__ = abc.ABCMeta來定義抽象方法和屬性),那麼它將不起作用。 – abergou 2012-02-23 18:28:12

+0

這對__str__等函數不起作用。那裏發生了什麼? – 2016-05-02 15:22:19

+0

@JohnKitchin:重定向不適用於雙下劃線方法,因爲這些必須(出於性能原因)在類上定義,而不是在實例上定義。 [(更多信息)](https://docs.python.org/3/reference/datamodel.html#special-lookup)我相信唯一的解決方法是顯式定義和重定向像'__str__'這樣的方法。 – 2016-09-05 09:28:27

2

這可以說是不是最好的做法,但是你能爲實例添加的功能,像我那樣,以幫助過渡從Django的ORM到SQLAlachemy我的代碼,如下所示:

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

我也考慮過這個,但對我來說感覺很不對勁。也許這是因爲我來自C++? – 2010-06-25 14:56:25

+2

對此感覺錯誤是公平的。它可以被認爲是猴子補丁。然而,它在很少代碼的情況下運行良好,當一切都是動態的和通過引用的時候,它是一個非常不同的世界,有更多的可能性。 – andyortlieb 2010-06-25 15:16:28

+0

我喜歡它。對於這個例子,其他任何東西都會引入更復雜的IMO。 – Chomeh 2015-03-06 02:03:07

1

鏈接維基百科文章中的UML圖是錯誤的,您的代碼也是錯誤的。

如果您遵循「裝飾器模式」,裝飾器類將從基礎裝飾類派生。 (在UML圖中,缺少WindowDecorator到Window的繼承箭頭)。

class foo_decorator(foo): 

你不需要未修飾的實現方法。

順便說一句:在強類型語言中,還有一個原因,爲什麼裝飾器必須從裝飾類派生:否則你不能鏈裝飾器。

+0

UML圖顯示了基類的繼承和聚合(裝飾器模式的兩個基本部分)。您從基類繼承,因此您看起來像原始類型,並且您持有對基類實例的引用,您可以在其中隔離對基類的訪問。維基百科文章在步驟1和2中說明了這一點:「(1)子類化原始組件,(2)將組件指針添加爲字段」。所以最初的問題實際上是關於Python鴨子打字,關於裝飾者模式,也不是Python裝飾者! – maxpolk 2014-12-26 19:51:31

8

作爲菲利普答案的附錄;如果你不僅需要裝修,但保留對象的類型,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() 
0

爲了補充@Alec托馬斯答覆。我修改了他的答案以遵循裝飾器模式。這樣你就不需要事先知道你正在裝修的課程。

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() 
0

在我的項目之一,我也需要做一個特別的事情,那就是即使是底層對象應實際執行的方法在裝飾器中被重新實現。如果你知道它的目標是什麼,它確實很容易做到。

用例是:

  • 我有方法A和B.對象X
  • 我創建一個裝飾類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 .:是的我知道它看起來很像繼承,但這是在多個對象的組合意義上完成的。