python中如何理解裝飾器代碼?

這是python中講裝飾器的一章

有一些基礎概念不是很明白,望大神指點:

1.如何理解return一個函數,它與return一個值得用法區別在哪?

2.在wrapper函數中,為什麼能返回一個在wrapper函數中沒有定義的func函數?

3.怎麼理解在log中作為參數存在的func,在wrapper函數中成了函數?這對log函數本身的使用有哪些影響,或者說當A函數的參數是一個函數時,如何使用A函數?


看你一下你的問題,屬於能正確的提問。讚揚一下,很多人提問,問題描述不清楚。

1、你的全部問題在於,你不理解Python 中函數是第一對象這個概念。

跟黃哥學python之函數是第一類對象

2、在wrapper函數中,為什麼能返回一個在wrapper函數中沒有定義的func函數?

因為log 函數的參數func 函數內部可以使用。這個是閉包的概念

黃哥漫談Python 閉包。

3.怎麼理解在log中作為參數存在的func,在wrapper函數中成了函數?這對log函數本身的使用有哪些影響,或者說當A函數的參數是一個函數時,如何使用A函數?

還是不理解函數是第一類對象,所以你有這樣的問題,函數是第一類對象,函數名是函數的引用,可以作為參數傳遞,可以作為函數的返回值從函數中返回,可以作為賦值語句的右值。


1.如何理解return一個函數,它與return一個值得用法區別在哪?

return 語句就是返回後面的值,作為當前函數的返回值。如果是函數名那就返回函數這個名稱,它的值就是指向的內存中的函數對象。

return 一個表達式,包含return 一個 函數(變數),return一個值,就是返回後面表達式運算的值。所以return 一個 函數(變數)就是執行函數,然後返回函數的返回值。

2.在wrapper函數中,為什麼能返回一個在wrapper函數中沒有定義的func函數?

因為本地符號表找不到func名稱,就到外圍函數的本地符號表中找,再到全局符號表中找,最後到內置符號表中去找。找到就能用了。

3.怎麼理解在log中作為參數存在的func,在wrapper函數中成了函數?這對log函數本身的使用有哪些影響,或者說當A函數的參數是一個函數時,如何使用A函數?

你所看到的名字都叫做符號(symbol)或者說名字,解釋器通過在符號表中查找對應的對象來引用。log中的參數func,在log被調用的時候,創建log函數的本地符號表的時候,加入名稱func。wrapper函數在執行中,在外圍找到這個名稱,直接引用就行了,不care這是個什麼。

對log函數本身使用完全沒有影響。

A函數的參數是一個函數,調用A函數的時候,就傳入函數。

A(1) 這是表達式具有一個表達式的值。

A 這是名稱,具有一個值就是函數對象。

我知道解釋器幹了些什麼。

1 def 寫信(函數):
2 def wrapper(變數):
3 print "親愛的"+ 變數 +"一大堆不拉不拉的讚美之詞"
4 return 函數(變數)
5 return wrapper

6 @ 寫信
7 def 追求(變數)
8 print "請" + 變數 + "吃飯"
9 print "請" + 變數 + "看電影"

10 追求("韓梅梅")

解釋器看到你定義了寫信這個函數,就知道了你想套路,馬上幫你搭一個寫信的架子,這個架子要做的事情就是預先準備好。將要把後面被裝飾的舊函數放到自己返回的函數的定義裡面充當返回的函數,而構成一個新函數。

一旦後面用裝飾器裝飾了(定義了)一個函數比如追求(變數),解釋器就用這個架子來生成一個新的追求函數。也就是把舊的追求函數放到自己返回的函數wrapper的定義裡面,充當返回的函數。

一旦你調用追求函數,並傳入"韓梅梅"參數,你調用新的追求函數,這個新的追求函數只有一個語句,就是返回wrapper的值,解釋器就把"韓梅梅"傳入wrapper執行,這時候先寫給韓梅梅的情書,然後再返回舊的追求的值,解釋器就把"韓梅梅"傳入舊的追求函數,這時候請韓梅梅吃飯,請韓梅梅看電影,返回一個None值,函數默認就會返回None,所以執行到此終結。

裝飾器的概念就是給舊函數,增加新功能。不管你是追求,還是道歉,還是分手,你只要加上注釋,就能構建出具備寫信功能的新函數。

那麼,為什麼能夠實現這種功能呢?

python裡面,一切都是名稱(name),指向一個內存中的對象。符號表就是名稱和對象的一一對應的字典。

完整的執行過程如下:

1 def 寫信(函數):
# 解釋器把寫信這個名稱加入到當前本地符號表中。

6 @ 寫信
7 def 追求(變數):
# 解釋器把追求這個名稱加入到當前本地符號表,並創建寫信函數的本地符號表,在其中加入函數這個名稱,參數傳遞進入的追求函數體的完整內容來,賦值函數這個名稱。

2 def wrapper(變數):
# 解釋器把wrapper名稱放入到寫信函數的本地符號表。

5 return wrapper
# 解釋器返回wrapper函數,離開寫信函數的本地符號表,當前本地符號表中的追求名稱指向返回出的wrapper函數體。

10 追求("韓梅梅")
# 解釋器調用追求函數,傳入位置參數"韓梅梅"。此時會選擇實際調用wrapper函數,傳入參數"韓梅梅",賦值到wrapper函數本地符號表中的變數參數。

3 print "親愛的"+ 變數 +"一大堆不拉不拉的讚美之詞"
# 解釋器創建wrapper函數的本地符號表,添加名稱變數,把"韓梅梅"傳遞進來賦值變數,輸出。

4 return 函數(變數)
# 解釋器要返回執行語句,就會先用變數調用函數,但是在wrapper函數的本地符號表中,沒有函數這個名稱,按照慣例,會自動向包含它的函數的本地符號表中去找,發現包含它的寫信函數的本地符號表中,有函數這個名稱,就調用它。函數這個名稱(不在自己本地符號表中,在外圍函數的本地符號表中的時候)就被叫做自由變數。使用自由變數的wrapper函數體叫做閉包。這就是給他們起了個名字,學術討論時候好說而已。並將 函數(變數)的返回值,返回。

8 print "請" + 變數 + "吃飯"
9 print "請" + 變數 + "看電影"
# 解釋器調用寫信函數的本地符號表中函數這個名稱,執行原來追求指向的對象的語句。追求名稱現在指向wrapper。結束函數體,所以默認返回None。導致上面的wrapper函數返回None。

解釋器回到當前本地符號表。


新手答題僅供參考。

我想從另外一個角度回答一下這個問題。

1:假設已經寫好了幾千行乃至幾萬行的代碼,且到處在調用系統已經定義好的函數如max,sum等

2:假設我們想給定義好的函數max和sum等環境自帶的函數增加日誌——就是如題主問題里的那樣增加一條信息:

print("call %s()"%函數.__name__)

基於上面的假設問題,如何實現呢?

分析如下:

我們的目標是擴展原有函數的功能還不改變原有函數的定義,且我們的目標函數名稱、形參都不改變。這樣才能滿足用在幾萬行代碼里到處調用的max,sum等自然地(不必修改函數名,不必修改實參)帶有了新功能。

第一步,如何得到這樣的新函數:

print("call %s()"%函數.__name__)
調用函數(如max,sum等)得到返回值

把上面的代碼塊寫成函數的形式:那就需要將add,sum等函數提取處來,一般化為函數的形參,姑且叫做func吧。(因為題主問題里就是這麼取的名字。)

def new_func(func):
print("call %s()"%func.__name__)
return func(參數)

會發現無法確定這個「參數」。至於函數名也不是我們先要的可以在後面使用賦值語句修改成想要的。這裡就要解決如何提供參數的問題。如果寫成如下:

def new_func(func,*args,**kwargs):
print("call %s()"%func.__name__)
return func(*args,**kwargs)

就會多出func這個絕對不可少的參數。

就def new_func(func,*args,**kwargs)這一部分來說,func必須被分離出來。
如下:

func=或者max,或者sum等等
def new_func(func,*args,**kwargs):
print("call %s()"%func.__name__)
return func(*args,**kwargs)

可以看出,func應該處在一個new_func可以夠得著,而且,func又不是具體值的境地。那就可以利用closure來實現。也就是加個外層函數,使得func成為外層函數的形參,這樣就能滿足要求。

如下:

def another_new_func(func):
def new_func(*args,**kwargs):
print("call %s()"%func.__name__)
return func(*args,**kwargs)

這樣一來,通過外層函數,可以給內層函數提供一個新環境。要使用內層函數,就可以通過外層函數將內層函數吐出來以供調用。如下:

def another_new_func(func):
def new_func(*args,**kwargs):
print("call %s()"%func.__name__)
return func(*args,**kwargs)
return new_func

這樣一來,下面只要將突出的內層函數通過賦值語句重新命名成max,sum等等原函數的名字就可以實現不必修改其它地方的函數名了。如下:

def another_new_func(func):
def new_func(*args,**kwargs):
print("call %s()"%func.__name__)
return func(*args,**kwargs)
return new_func

sum=another_new_func(sum)
#實驗一下
print(sum([1,2]))

這樣問題就解決了。

然後就是給another_new_func 和new_func起個切題的名字。log,wrapper。

————————

寫完以後,又想了想,雖然這樣想,但是,似乎還是要有嵌套函數的知識才行。

closure的知識還是要補一下。而不僅僅是這個例子。


變數可以賦值一個函數給它:

既然函數可以賦值給變數, 那麼它一樣可以作為參數或return值. 理解這個再來理解裝飾器. 對於

它等價於:

cpython也是這樣的機制來實現裝飾器的.


1.Python中函數也是對象,因此返回一個函數和返回一個對象沒有任何本質區別,只不過是這個返回值可以當做函數來調用而已。

2.內部函數是一個閉包,它捕獲了func對象。這麼說可能有點難以理解,考慮一下,普通的函數是不是可以使用在它外部定義的全局變數,在這裡,由於wrapper函數定義在log函數內部,因此你可以理解為,wrapper將所有在log函數內部定義局部變數都視為全局變數看待,因此wrapper可以正常的使用func

3.上面已經說了函數也是對象的一種,函數調用其實就是調用了函數對象上的一個特殊的方法而已(你可以在自定義類裡面添加__call__方法,這樣一來該類的實例就可以像函數一樣通過()調用了),所以調用func函數其實和調用對象的方法是一樣


本來寫了答案又刪了

各位好心人不要上來就要人家讀什麼什麼的,新手心裡你們不太了解,再說讀什麼也要看進度。

第一二問答案看 @王很水

第三問,func為什麼是個函數,因為你在@log下面要定義一個函數,你傳入的func本來就是一個函數。


沒人回答,我先來誤人子弟吧

1.如何理解return一個函數,它與return一個值得用法區別在哪?

沒區別 return 1與return lambda x:1與def f:return f() 沒啥區別其實

2.在wrapper函數中,為什麼能返回一個在wrapper函數中沒有定義的func函數?

func不是入參嗎,定義了啊。

3.怎麼理解在log中作為參數存在的func,在wrapper函數中成了函數?這對log函數本身的使用有哪些影響,或者說當A函數的參數是一個函數時,如何使用A函數?

入參為啥叫這個名你琢磨琢磨


函數也是對象 可以象參數一樣傳遞 也可以返回


題主還是對函數有偏見。

試著把函數看成一個可調用的(即可以被()運算符操作的)變數。


推薦閱讀:

哪些庫是 Python2 獨有而 Python3 暫時不支持的?
python進階中,關於買書的問題!?
如何將多維list降到一維,使用環境為python3?
請問用python3寫貪吃蛇時,怎麼只用左右兩個鍵控制蛇上下左右四個方向轉彎?
python 使用 threading.thread(target="")指明函數入口,如果函數有返回值,如何得到這個返回值?

TAG:Python | Python3x | Python入門 |