2011-11-29 141 views
4

我想演示Python中的裝飾器對一些人的用處,並且以一個簡單的例子失敗:考慮兩個函數(爲了簡單起見,沒有參數)fg。 可以將它們的和f + g定義爲返回f()+ g()的函數。當然,函數的加,減等等通常沒有定義。但是編寫一個將每個函數轉換爲可添加函數的裝飾器很容易。爲什麼這個「可操作函數」的python實現失敗?

現在我想要有一個裝飾器,可以將任何函數轉換爲「可操作」的函數,也就是說,對於標準模塊operator中的任何操作符,所描述的函數都具有相同的功能。我的實現看起來如下:

import operator 

class function(object): 
    def __init__(self, f): 
     self.f = f 
    def __call__(self): 
     return self.f() 

def op_to_function_op(op): 
    def function_op(self, operand): 
     def f(): 
      return op(self(), operand()) 
     return function(f) 
    return function_op 
binary_op_names = ['__add__', '__and__', '__div__', '__eq__', '__floordiv__', '__ge__', '__gt__', '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__or__', '__pow__', '__sub__', '__truediv__', '__xor__'] 
for name in binary_op_names: 
    type.__setattr__(function, name, op_to_function_op(getattr(operator, name))) 

讓我們進行一個小測試,看看它的工作原理:

@function 
def a(): 
    return 4 

def b(): 
    return 7 

c = a + b 
print c() 
print c() == operator.__add__(4, 7) 

輸出:

11 
True 

這是我的一些試驗後得到的最終版本。 現在讓我們做兩個小的,不相關的修改來看看有什麼我嘗試過:

首先:在binary_op_names定義中,方括號改爲圓括號。突然,(對我來說)完全無關的錯誤消息傳出:

Traceback (most recent call last): 
    File "example.py", line 30, in <module> 
    c = a + b 
TypeError: unsupported operand type(s) for +: 'function' and 'function' 

哪裏這個來自?

:寫op_to_function_op作爲lambda表達式:

op = getattr(operator, name) 
type.__setattr__(function, name, lambda self, other: function(lambda: op(self(), other()))) 

執行稍微複雜的測試案例:

@function 
def a(): 
    return 4 

def b(): 
    return 7 

c = a + b 
print c() 
print c() == operator.__add__(4, 7) 
print c() == operator.__xor__(4, 7) 

輸出:

3 
False 
True 

這看起來對我像範圍泄漏,但我再次不不明白爲什麼會發生這種情況。

+0

型.__ setattr__似乎做邪惡的東西..不要不知道是什麼。元組列表的東西真的很奇怪。如果你改變類型。__setattr__ setattr一切似乎工作。 – mkorpela

+0

應用於斐波納契序列和其他緩存友好問題的典型「memoize」裝飾器可能是更好的應用程序來演示裝飾器。 – Daenyth

+0

這個元組問題似乎是一個令人髮指的bug。我看到了同樣的行爲。關於'name'的所有內容都會檢查出來,但是'type .__ setattr__'不起作用。 – Nate

回答

2

對於第一個問題,我沒有看到任何問題,從list改變binary_op_namestuple的時候,不知道爲什麼你看到這一點。

至於第二個問題,所有操作都將執行XOR,因爲__xor__op設置的最後一個項目。由於op在創建時未傳遞到lambda中,因此每次調用lambda時,都會在全局範圍內查找op,並始終看到__xor__

您可以通過創建一個lambda充當閉合,類似於當前的op_tofunction_op防止這種情況,可能最終會看起來像這樣:

op_to_function_op = lambda f: lambda self, other: function(lambda: f(self(), other())) 
for name in binary_op_names: 
    op = getattr(operator, name) 
    type.__setattr__(function, name, op_to_function_op(op)) 

>>> (function(lambda: 4) + (lambda: 7))() 
11 
>>> (function(lambda: 4) - (lambda: 7))() 
-3 
>>> (function(lambda: 4)^(lambda: 7))() 
3 
+0

你使用過類型.__ setattr__還是setattr? (請參閱主要評論主題中的討論) 感謝您對lambda的澄清! – Turion

+0

我使用了'type .__ setattr__',它似乎可以工作,但我不會使用'setattr'或'type .__ setattr__',所以我不能很好地評論哪一個更好。 –