2013-01-02 170 views
4

我很難想出一種好的python方法,並且符合oop原則,因爲我已經被教會了解如何在python中創建一個相關的方法裝飾器族。python中的子類方法裝飾器

互不一致的目標似乎是我想能夠訪問裝飾器屬性和裝飾方法綁定的實例的屬性。這就是我的意思是:

from functools import wraps 

class AbstractDecorator(object): 
    """ 
    This seems like the more natural way, but won't work 
    because the instance to which the wrapped function 
    is attached will never be in scope. 
    """ 
    def __new__(cls,f,*args,**kwargs): 
     return wraps(f)(object.__new__(cls,*args,**kwargs)) 

    def __init__(decorator_self, f): 
     decorator_self.f = f 
     decorator_self.punctuation = "..." 

    def __call__(decorator_self, *args, **kwargs): 
     decorator_self.very_important_prep() 
     return decorator_self.f(decorator_self, *args, **kwargs) 

class SillyDecorator(AbstractDecorator): 
    def very_important_prep(decorator_self): 
     print "My apartment was infested with koalas%s"%(decorator_self.punctuation) 

class UsefulObject(object): 
    def __init__(useful_object_self, noun): 
     useful_object_self.noun = noun 

    @SillyDecorator 
    def red(useful_object_self): 
     print "red %s"%(useful_object_self.noun) 

if __name__ == "__main__": 
    u = UsefulObject("balloons") 
    u.red() 

這當然產生

My apartment was infested with koalas... 
AttributeError: 'SillyDecorator' object has no attribute 'noun' 

注意,當然總是有辦法讓這個工作。例如,一個擁有足夠參數的工廠會讓我將方法附加到一些創建的SillyDecorator實例上,但我有點想知道是否有合理的方法來繼承。

回答

2

@miku得到了使用描述符協議的關鍵思想。這是一個改進,它使裝飾器對象與「有用的對象」分離 - 它不會將裝飾器信息存儲在底層對象上。

class AbstractDecorator(object): 
    """ 
    This seems like the more natural way, but won't work 
    because the instance to which the wrapped function 
    is attached will never be in scope. 
    """ 
    def __new__(cls,f,*args,**kwargs): 
     return wraps(f)(object.__new__(cls,*args,**kwargs)) 

    def __init__(decorator_self, f): 
     decorator_self.f = f 
     decorator_self.punctuation = "..." 

    def __call__(decorator_self, obj_self, *args, **kwargs): 
     decorator_self.very_important_prep() 
     return decorator_self.f(obj_self, *args, **kwargs) 

    def __get__(decorator_self, obj_self, objtype): 
     return functools.partial(decorator_self.__call__, obj_self)  

class SillyDecorator(AbstractDecorator): 
    def very_important_prep(decorator_self): 
     print "My apartment was infested with koalas%s"%(decorator_self.punctuation) 

class UsefulObject(object): 
    def __init__(useful_object_self, noun): 
     useful_object_self.noun = noun 

    @SillyDecorator 
    def red(useful_object_self): 
     print "red %s"%(useful_object_self.noun) 

>>> u = UsefulObject("balloons") 
... u.red() 
My apartment was infested with koalas... 
red balloons 

描述符協議是這裏的關鍵,因爲它是讓你訪問都裝飾方法,並在其上綁定的對象的東西。在__get__內部,您可以提取有用的對象標識(obj_self)並將其傳遞給__call__方法。

注意,重要的是要使用functools.partial(或類似機構),而不是簡單地存儲obj_self作爲decorator_self屬性。由於裝飾方法在類上,因此只存在SillyDecorator的一個實例。你不能使用這個SillyDecorator實例來存儲有用的對象實例特定的信息---如果你創建了多個有用的對象並訪問它們的裝飾方法而不立即調用它們,那將導致奇怪的錯誤。

但值得指出的是,可能有一個更簡單的方法。在你的例子中,你只是在裝飾器中存儲少量的信息,你不需要稍後改變它。如果是這樣,使用裝飾器製造者函數可能會更簡單:一個函數接受一個參數(或參數)並返回一個裝飾器,其行爲可以依賴於這些參數。這裏有一個例子:

def decoMaker(msg): 
    def deco(func): 
     @wraps(func) 
     def wrapper(*args, **kwargs): 
      print msg 
      return func(*args, **kwargs) 
     return wrapper 
    return deco 

class UsefulObject(object): 
    def __init__(useful_object_self, noun): 
     useful_object_self.noun = noun 

    @decoMaker('koalas...') 
    def red(useful_object_self): 
     print "red %s"%(useful_object_self.noun) 

>>> u = UsefulObject("balloons") 
... u.red() 
koalas... 
red balloons 

可以使用decoMaker提前做出裝飾後重新使用,如果你不想每次都重新輸入信息,你做裝飾:

sillyDecorator = decoMaker("Some really long message about koalas that you don't want to type over and over") 

class UsefulObject(object): 
    def __init__(useful_object_self, noun): 
     useful_object_self.noun = noun 

    @sillyDecorator 
    def red(useful_object_self): 
     print "red %s"%(useful_object_self.noun) 

>>> u = UsefulObject("balloons") 
... u.red() 
Some really long message about koalas that you don't want to type over and over 
red balloons 

你可以看到,這比爲不同類型的decoratorts編寫整個類繼承樹要簡單得多。除非你正在編寫存儲各種內部狀態的超級複雜裝飾器(這可能會讓人困惑),這個裝飾器製造者的方法可能是一個更簡單的方法。

+0

這太棒了!我認爲我需要更好地圍繞描述符進行包裝,這確實是這樣。至於裝飾製造商:是的,這就是我最終做的。問題在於,我作爲參數傳遞的是函數;每個版本的裝飾器都有多個函數定義。因此,在頂層定義的構造函數方法定義並將它們附加在構造函數中時,感覺有點冒險,因爲邏輯上我所做的是非常多的繼承。 –

2

改編自http://metapython.blogspot.de/2010/11/python-instance-methods-how-are-they.html。請注意,此變體設置目標實例上的屬性,因此,如果沒有檢查,則可能會覆蓋目標實例屬性。下面的代碼不包含對這種情況的任何檢查。

另請注意,此示例顯式設置punctuation屬性;更普遍的類可以自動發現它的屬性。

from types import MethodType 

class AbstractDecorator(object): 
    """Designed to work as function or method decorator """ 
    def __init__(self, function): 
     self.func = function 
     self.punctuation = '...' 
    def __call__(self, *args, **kw): 
     self.setup() 
     return self.func(*args, **kw) 
    def __get__(self, instance, owner): 
     # TODO: protect against 'overwrites' 
     setattr(instance, 'punctuation', self.punctuation) 
     return MethodType(self, instance, owner) 

class SillyDecorator(AbstractDecorator): 
    def setup(self): 
     print('[setup] silly init %s' % self.punctuation) 

class UsefulObject(object): 
    def __init__(self, noun='cat'): 
     self.noun = noun 

    @SillyDecorator 
    def d(self): 
     print('Hello %s %s' % (self.noun, self.punctuation)) 

obj = UsefulObject() 
obj.d() 

# [setup] silly init ... 
# Hello cat ... 
+0

這不完全相同,因爲它在實例上設置了屬性(因此可能會覆蓋該名稱的現有屬性)。但描述符協議絕對是一種可行的方式。 – BrenBarn

+0

@BrenBarn,是的,這是真的,謝謝你指出。我在帖子中添加了一個簡短的註釋。 – miku

+0

有可能避免這種限制,請參閱我的答案。 – BrenBarn