2012-07-02 116 views
5

有人能詳細解釋下面的代碼的最後一行:Python的方法定義使用裝飾

def myMethod(self): 
    # do something 

myMethod = transformMethod(myMethod) 

你爲什麼想要通過定義爲通過其他方法的方法?那又如何呢?提前致謝!

回答

2

這是一個函數環繞的例子,當你有一個函數接受一個函數作爲參數,並且返回一個新的函數來修改原始函數的行爲。

下面是如何這可能是使用的例子,這是一個簡單的包裝它只是打印「確認」,並在每次調用「退出」:

def wrapper(func): 
    def wrapped(): 
     print 'Enter' 
     result = func() 
     print 'Exit' 
     return result 
    return wrapped 

,這裏是你怎麼可能一個例子使用此:

>>> def say_hello(): 
...  print 'Hello' 
... 
>>> say_hello() # behavior before wrapping 
Hello 
>>> say_hello = wrapper(say_hello) 
>>> say_hello() # behavior after wrapping 
Enter 
Hello 
Exit 

爲方便起見,Python提供了decorator syntax這僅僅是函數封裝的簡寫版本做同樣的事情,在函數定義的時候,這裏是如何可以用:

>>> @wrapper 
... def say_hello(): 
...  print 'Hello' 
... 
>>> say_hello() 
Enter 
Hello 
Exit 
+0

非常感謝您的幫助!我非常感謝它有多清楚。 –

2

爲什麼要通過另一種方法傳遞方法的定義?

因爲你想修改它的行爲。

那該怎麼辦?

完美的是,因爲函數是Python中的第一類。

def decorator(f): 
    def wrapper(*args, **kwargs): 
    print 'Before!' 
    res = f(*args, **kwargs) 
    print 'After!' 
    return res 
    return wrapper 

def somemethod(): 
    print 'During...' 

somemethod = decorator(somemethod) 

somemethod() 
+0

爲什麼不修改方法的實際源代碼?你能舉一個例子,通過方法的定義會非常有益嗎?最後,如何編寫transformMethod來修改另一個方法?對於所有的問題抱歉,我從來沒有見過這種類型的語法,所以我很困惑。 –

+2

「爲什麼不修改方法的實際源代碼?」因爲您可能沒有*源代碼,或者您需要將其應用於多個功能。 –

+1

@ user1495015或者,也許所做的修改總是相同的,只有實際修改的部分是不同的。爲此,您可以愉快地使用裝飾器。 – glglgl

1

你描述的是一個裝飾,方法/函數修改的一種形式,可以實現與decorators特殊的語法容易得多。

你描述的是在@staticmethod形式,@classmethod@functools.wraps(),非常廣泛地使用,例如相當於

@transformMethod 
def myMethod(self): 
    # do something 

裝飾@contextlib.contextmanager等等,等等等等

由於有一定的Python版本(我認爲它是2.6),課程也可以進行裝飾。

這兩種裝飾愉快地允許返回甚至不是函數或類的對象。例如,你可以用一種將它變成字典,集合或其他的方式來裝飾生成器函數。

apply = lambda tobeapplied: lambda f: tobeapplied(f()) 

@apply(dict) 
def mydict(): 
    yield 'key1', 'value1' 
    yield 'key2', 'value2' 
print mydict 

@apply(set) 
def myset(): 
    yield 1 
    yield 2 
    yield 1 
    yield 4 
    yield 2 
    yield 7 
print myset 

我在這裏做什麼?

我創建了一個函數,它接受一個「要應用的東西」,然後返回另一個函數。

這個「內部」函數需要對函數進行修飾,調用它並將其結果放入外部函數並返回此結果。

f()返回一個發生器對象,然後將其放入dict()set()

+0

不,它*是一個裝飾器。對於完全相同的操作,「@」只是語法糖。 –

+0

@ IgnacioVazquez-Abrams你是對的。我對Python的定義太接近了。 – glglgl

+0

@ IgnacioVazquez-Abrams是的,但大多數人看到裝飾相反。不一定作爲一種通用的方法轉換。 –

0

你需要明白,在Python中,一切都是一個對象。函數是一個對象。你可以用一個函數對象來做同樣的事情,你可以用其他類型的對象來做:存儲在列表中,存儲在字典中,從函數調用中返回等等。

像你這樣的代碼的常見原因顯示是「包裝」其他功能對象。例如,這裏是一個打印由函數返回的值的包裝器。

def print_wrapper(fn): 
    def new_wrapped_fn(*args): 
     x = fn(*args) 
     print("return value of %s is: %s" % (fn.__name__, repr(x))) 
     return x 
    return new_wrapped_fn 

def test(a, b): 
    return a * b 

test = print_wrapper(test) 

test(2, 3) # prints: return value of test is: 6 

這是一個非常有用的任務,也是一個很普遍的任務,Python對它有特別的支持。 Google搜索「Python裝飾器」。

0

在您的原始問題中,您問「爲什麼要通過其他方法傳遞方法的定義?」然後,在評論中,您問「爲什麼不修改方法的實際源代碼?」實際上,我認爲這是一個非常好的問題,如果不用手揮手就很難回答,因爲裝飾者只有在代碼達到一定的複雜程度時纔會變得非常有用。但是,我認爲如果你考慮以下兩個功能裝飾的點將變得更加清晰:

def add_x_to_sequence(x, seq): 
    result = [] 
    for i in seq: 
     result.append(i + x) 
    return result 

def subtract_x_from_sequence(x, seq): 
    result = [] 
    for i in seq: 
     result.append(i - x) 
    return result 

現在,這兩個例子功能有一定的缺陷 - 在現實生活中,例如,你可能只是重寫他們作爲列表解析 - 但讓我們忽略目前的明顯缺陷,並假裝我們必須這樣寫,因爲for循環遍歷序列。我們現在面臨的問題是,我們的兩個功能差不多是一樣的,只在一個關鍵時刻有所不同。這意味着我們在這裏重複自己!這是一個問題。現在我們必須維護更多的代碼行,爲bug出現留出更多空間,並且在bug出現後隱藏更多空間。

一個經典方法這個問題可能是創建一個函數,需要的功能,和整個序列應用它,就像這樣:

def my_map(func, x, seq): 
    result = [] 
    for i in seq: 
     result.append(func(i, x)) 
    return result 

現在我們需要做的就是定義特定funcs中傳遞給my_map(這實際上只是內置的map函數的專用版本)。

def sub(a, b): 
    return a - b 

def add(a, b): 
    return a + b 

而且我們可以使用它們像這樣:

added = my_map(sub, x, seq) 

但這種方法有它的問題。例如,閱讀比我們最初的獨立功能要難一些;每次我們想從項目列表中加上或減去x,我們必須指定函數和值作爲參數。如果我們這麼做的話,我們寧願有一個單一的函數名稱,它總是指向同一個動作 - 這將提高可讀性,並使我們更容易理解代碼中發生的事情。我們可以包裹上面另一功能...

def add_x_to_sequence(x, seq): 
    return my_map(add, x, seq) 

但現在我們再次重複自己!而且我們也在創造一個功能繁多的混亂的名字空間。

裝飾者提供了一個解決這些問題的方法。而不是每次將函數傳遞給另一個函數,我們可以傳遞一次。首先,我們定義了一個包裝函數:

def vectorize(func): 
    def wrapper(x, seq): 
     result = [] 
     for i in seq: 
      result.append(func(i, x)) 
     return result 
    return wrapper 

現在我們需要做的就是定義一個函數,並把它傳遞給上面,它環繞:

def add_x_to_sequence(a, b): 
    return a + b 
add_x_to_sequence = vectorize(add_x_to_sequence) 

或者,使用修飾語法:

@vectorize 
def add_x_to_sequence(a, b): 
    return a + b 

現在我們可以寫出許多不同vectorize d功能,並且我們for爲所有這些的邏輯在短短一個發生PL高手。現在我們不必分別修復或優化許多不同的功能;我們所有的循環相關的錯誤和循環相關的優化發生在同一個地方;而且我們仍然可以獲得特定功能的所有可讀性優勢。