2010-01-05 71 views
0

我想要創建一個新的函數/方法,使得使用對象obj的裝飾。如果裝飾對象是函數,則在創建函數時必須實例化obj。如果裝飾對象是一種方法,則必須實例化新的obj,並將其綁定到方法已裝飾的類的每個實例。我不能把裝修放在__init__中,因爲修飾者修改了功能文檔。我有這樣的事情了,但它只是實例time一次,這不是我想要的:具有實例化時間變量的Python裝飾器?

__all__ = ['dec', 'A'] 

from time import time 
import inspect 

def dec(f): 
    obj = time() # want to set on object instantiation 
    def new(*args, **kwargs): 
     f(*args, **kwargs) # Validate against definition so it doesn't go 
          # out of sync 
     print obj 
     # ... 
    try: 
     d = inspect.getsourcelines(f) 
    except IOError: 
     d = "<unable to fetch definition>" 
    else: 
     d = d[0][1].rstrip('\n').rstrip(':').lstrip(' ').lstrip('def') 
    new.__doc__ = d + "\n" + (f.__doc__ or '') 
    return new 

class A(object): 
    @dec 
    def f(self, x): 
     """something""" 
     print '%s.f(%s)' % (self, x) 

if __name__ == '__main__': 
    A().f(123) 
    A().f(123) 
    A().f(123) 

我的想法來解決這個問題就是要檢查是否傳遞給裝飾對象接受一個參數self如果是這樣,返回結合objself如果它不是已經存在,然後使用self.obj的方法。然後,如果沒有self參數傳遞給裝飾對象,只是實例obj裝飾內部,並返回一個函數,利用這一點。

不過......我說的並沒有真正爲我工作,因爲我真正的裝飾,我返回從list衍生並具有__call__屬性的對象。此外,在實際裝飾,self甚至沒有在得到由它的裝飾,因爲他們沒有利用自己的實例變量的對象定義(什麼,我真的裝飾只是受到外部物體訂閱事件,事件已經記錄了簽名)。

編輯:其實,如果有一種方法,使一list子類的實例獲取綁定到一個實例,以便它__call__屬性隱含接收類實例(如在任何正常的實例方法),這將是一個完美的解決方案,這是我最初試圖找出如何去做的。但也許有更好的解決方案,我不需要用self屬性定義裝飾方法?要麼是完美的。

回答

1

由於裝飾僅僅是說

def func(): 
    ... 
func = decorator(func) 

爲什麼這樣做,在對象的構造函數的語法糖?

class A(object): 
    def __init__(self): 
     # apply decorator at instance creation 
     self.f = dec(self.f) 

    def f(self, x): 
     """something""" 
     print '%s.f(%s)' % (self, x) 
+0

我明確表示這不是我想要的。我的裝飾器更改裝飾函數的文檔,如果將裝飾放入'__init__'中,則不會發生這種情況。 – 2010-01-05 17:52:07

+0

@Longpoke:爲什麼'__doc__'如此重要?爲什麼不使用元類來生成'__doc__'的一部分? – 2010-01-05 18:41:29

+0

裝飾器將函數轉換爲list的子類並向其添加__call__。這會破壞'__doc__',所以我將它複製到新創建的對象。我沒有元類的經驗,但我不確定它會在這裏起作用。我已經在使用abc.ABCMeta了,我能一次使用兩個元類嗎?我真正需要的是'__call__'方法(我添加到列表子類)來接收'self',所以我可以保持狀態。 – 2010-01-05 19:08:17

0

你的寫作風格真的很難閱讀。普通句子的一半,只要你的:P

你要這要什麼?

__all__ = ['dec', 'A'] 

from time import time, sleep 
import inspect 

def dec(f): 
    def new(self, *args, **kwargs): 
     print self.initiated # print the time the objecte was initiated ... 
     return f(self, *args, **kwargs) # Validate against definition so it doesn't go 
          # out of sync 
    try: 
     d = inspect.getsourcelines(f) 
    except IOError: 
     d = "<unable to fetch definition>" 
    else: 
     d = d[0][1].rstrip('\n').rstrip(':').lstrip(' ').lstrip('def') 
    new.__doc__ = d + "\n" + (f.__doc__ or '') 
    return new 

class A(object): 
    def __init__(self): 
     self.initiated = time() # save the time the object was initiated 


    @dec 
    def f(self, x): 
     """something""" 
     print '%s.f(%s)' % (self, x) 

if __name__ == '__main__': 
    A().f(123) 
    sleep(1) 
    A().f(123) 
    sleep(1) 
    A().f(123) 
+0

這將不起作用,因爲隨後對裝飾函數的調用將具有不同的'obj'實例。我只用'time()'來表明在我的例子中引用了同一個對象。我想我應該使用'id()'。 – 2010-01-05 17:53:48

1

這是一個有點難以弄清楚你到底是經過什麼。列表和__call__東西是混淆了我,所以我主要是堅持你的第一段:

__all__ = ['dec', 'A'] 

from types import InstanceType 
from functools import wraps 
import inspect 

def dec(func): 

    #get the sig of the function 
    sig = [] 
    @wraps(func) 
    def wrapper(*args, **kwargs): 
     ret = None 
     #if this is a method belonging to an object... 
     if args and getattr(args[0], func.__name__, None): 
     instance, args = args[0], args[1:] 
     #if sig of object is not already set 
     if not hasattr(instance, "sig"): 
      instance.sig = [] 
     ret = func(instance, *args, **kwargs) 
     print "Sig of %s is %s" % (func.__name__, id(instance.sig)) 
     #else this is a function 
     else: 
     ret = func(*args, **kwargs) 
     print "Sig of %s is %s" % (func.__name__, id(sig)) 
     return ret 

    #modify the doc string 
    try: 
     docs = inspect.getsourcelines(func) 
    except: 
     docs = "<unable to fetch defintion>" 
    else: 
     docs = docs[0][1].rstrip('\n').rstrip(':').lstrip(' ').lstrip('def') 
    wrapper.__doc__ = docs + "\n" + (func.__doc__ or '') 
    return wrapper 

class A(object): 
    def __init__(self): 
     super(A, self).__init__() 

    @dec 
    def f(self, x): 
     """something""" 
     print '%s.f(%s)' % (self, x) 


@dec 
def myfunc(): 
    print "myfunc" 

@dec 
def myfunc2(): 
    print "myfunc2" 

@dec 
def myfunc3(): 
    print "myfunc3" 

if __name__ == "__main__": 
    list = [] 
    for x in xrange(3): 
     list.append(A()) 

    [a.f(123) for a in list] 
    myfunc() 
    myfunc() 
    myfunc2() 
    myfunc2() 
    myfunc3() 
    myfunc3() 

輸出:

<__main__.A object at 0x00B9F2D0>.f(123) 
Sig of f is 11932616 
<__main__.A object at 0x00B9F430>.f(123) 
Sig of f is 11925464 
<__main__.A object at 0x00B9F450>.f(123) 
Sig of f is 11918112 
myfunc 
Sig of myfunc is 11925624 
myfunc 
Sig of myfunc is 11925624 
myfunc2 
Sig of myfunc2 is 11794592 
myfunc2 
Sig of myfunc2 is 11794592 
myfunc3 
Sig of myfunc3 is 11925144 
myfunc3 
Sig of myfunc3 is 11925144 
+0

當使用時間作爲簽名,代碼跑得那麼快,時間總是相同的... 所以我切換到使用ID,但後來我意識到,將無法正常展示是怎麼回事,因爲FUNC的id /實例將是相同的,即使這個代碼沒有做它假設的。 我剛剛創建了一個空白列表並打印出每個「sig」實例的id。 – manifest 2010-01-05 20:38:34

+0

嘿,這幾乎可以工作。然而,第一個參數的比較是相當危險的,而且你的代碼被破壞了,因爲isinstance(x,InstanceType)只對舊式類的實例返回True。刪除該比較可以修復它,因此A的每個實例都將打印一個不同的「sig」標識。我現在試圖用一個嵌套的裝飾器來解決這個問題,我將關閉裝飾方法中的裝飾方法的類的引用,以便它可以使用它進行比較。 此外,自從使用'wraps'後,您不需要複製文檔。感謝那一個,爲我節省了一些代碼:) – 2010-01-05 22:17:24

+0

比較第一個參數並不危險,因爲如果參數args是[],if語句會使if語句短路。注意myfuncs沒有參數,但代碼不會中斷。你是對的新型打破它,很高興知道。我只是複製了文檔,因爲你在做一些有趣的事情。我已經更新了上面的代碼以進行工作演示。 – manifest 2010-01-05 23:35:37

0

好吧基於清單的代碼,我有一個解決方案。

正如你所看到的,是pydoc仍會有體面的文件對裝飾的事情:

class A(__builtin__.object) 
| Methods defined here: 
| 
| f(*args, **kwargs) 
|  f(self, x) 
|  something 

同時,與裝飾方法的類的每個實例都將有不同的obj。每個功能也有自己的obj

<__main__.A object at 0x7c209bee4a50>.f(123) 
obj of <__main__.A object at 0x7c209bee4a50>.f is 136479497243752 
<__main__.A object at 0x7c209bee4a10>.f(123) 
obj of <__main__.A object at 0x7c209bee4a10>.f is 136479497250720 
<__main__.A object at 0x7c209bee4a90>.f(123) 
obj of <__main__.A object at 0x7c209bee4a90>.f is 136479497446824 
myfunc 
obj of myfunc is 136479497243392 
myfunc 
obj of myfunc is 136479497243392 
myfunc2 
obj of myfunc2 is 136479497245688 
myfunc2 
obj of myfunc2 is 136479497245688 
myfunc3 
obj of myfunc3 is 136479497246408 
myfunc3 
obj of myfunc3 is 136479497246408 

下面是代碼:

__all__ = ['dec', 'A'] 

from functools import wraps 
import inspect 

def dec(cls=None): 
    # cls will be closed in subdec 
    def subdec(func): 
     # closed in wrapper, guaranteed to be unique per decorator evaluation 
     obj = [] 

     @wraps(func) 
     def wrapper(*args, **kwargs): 
      if (args and type(args[0]) == cls): 
       instance = args[0] 
       # We will attach to instance a dict _objs of 
       # function_name:obj. This way we don't pollute the namespace 
       # when decorating many functions. 

       # Alternatively, you could make a dict external to instance 
       # of instance:{function_name:obj}, but that takes more work 
       # because you need to make it not prevent garbage collection 
       # of instance. 
       if not hasattr(instance, "_objs"): 
        instance._objs = {} 
       if func.__name__ not in instance._objs: 
        instance._objs[func.__name__] = [] 
       func(*args, **kwargs) # This is only used to check the arity. 
             # My real code is all to do with 
             # manipulating obj. 
       print "obj of %s.%s is %s" % (
        instance, 
        func.__name__, 
        id(instance._objs[func.__name__]) 
       ) 
      else: 
       # Functions are identified by the closed obj 
       func(*args, **kwargs) 
       print "obj of %s is %s" % (func.__name__, id(obj)) 

     # Find function/method signature and prepend it to the new object's doc 
     try: 
      doc = inspect.getsourcelines(func) 
     except IOError: 
      line = "<unable to fetch definition>" 
     else: 
      line = '@' 
      i = 0 
      while line.lstrip(' ').startswith("@"): 
       try: 
        line = doc[0][i] 
       except IndexError: 
        line = "<unable to fetch definition>" 
       i += 1 
     line = line.rstrip('\n').rstrip(':').lstrip(' ').lstrip('def') 
     wrapper.__doc__ = line + "\n" + (func.__doc__ or '') 

     return wrapper 
    return subdec 

class A(object): 
    def f(self, x): 
     """something""" 
     print '%s.f(%s)' % (self, x) 

A.f = dec(A)(A.f) 

@dec() 
def myfunc(): 
    print "myfunc" 

@dec() 
def myfunc2(): 
    print "myfunc2" 

@dec() 
def myfunc3(): 
    print "myfunc3" 

if __name__ == "__main__": 
    a, b, c = A(), A(), A() 
    # a, b, and c each have their own instance of obj: 
    a.f(123) 
    b.f(123) 
    c.f(123) 
    myfunc() 
    myfunc() 
    myfunc2() 
    myfunc2() 
    myfunc3() 
    myfunc3() 

增加的外裝飾,cls只是用來獲取類的身份,所以裝飾功能,可以肯定地告訴天氣它是一種功能或方法。我不確定這會如何繼續[多重]繼承...雖然可能清單的想法,檢查該部分更好。