python裝飾器的問題?

跟著廖雪峰網站在學,謝謝廖大,這是代碼:

def log(func):

def wrapper(*args, **kw):

print(call %s(): % func.__name__)

return func(*args, **kw)

return wrapper

@log

def now():

print(2015-3-25)

輸出:call now():

2015-3-25

把@log放到now()函數的定義處,相當於執行了語句:

now = log(now)

由於log()是一個decorator,返回一個函數,所以,原來的now()函數仍然存在,只是現在同名的now變數指向了新的函數,於是調用now()將執行新函數,即在log()函數中返回的wrapper()函數。"

始終理解不清楚,特別是「同名的now變數指向了新的函數,於是調用now()將執行新函數」, 這是為什麼呢?這裡的變數now到底和函數now()存在什麼關係呢?


名稱和值的關係。Python中函數也是變數,就跟 a = 0 似的。

當你def now之後,now就是一個變數名稱,綁定某個 __name__為 now 的函數。當然你也可以給它重新綁定。

log 是個高階函數,輸入是函數,輸出也是函數,輸出重新綁定到了 now 上面。

你要習慣這種做法。


這篇「Python裝飾器」對Python裝飾器講得很清楚啦。


先忽略@語法糖和變數名覆蓋來看待這問題,我重寫之後就變成這個樣子:

def log(func):
def wrapper(*args, **kw):
print(call %s(): % func.__name__)
return func(*args, **kw)
return wrapper

def now():
print(2015-3-25)

# 想想這裡發生了什麼?
new_now = log(now)

# 想想這裡又發生了什麼?
new_now()

上面兩個函數定義應該都不會有什麼疑問吧,重點是後兩個語句,讓我一句一句來解釋吧。

new_now = log(now)

這裡是把now函數作為調用參數來調用log函數,然後把結果賦值給new_now變數。

  1. 把now賦值給log函數中的變數func。
  2. 在log函數定義一個新函數wrapper。
  3. 把wrapper函數返回,賦值給全局變數new_now。

這就是上面的語句要做的所有事情,再來看下一句。

new_now()

這裡的new_now實際上已經指向的是wrapper函數

  1. 調用wrapper函數
  2. 列印出func變數指向的函數的名字(func指向的是now函數)
  3. 再調用func函數,實際就是調用了now函數,最後把結果返回。

只要有函數的基礎應該不會太難理解我寫的答案,至於func變數作用域,這裡涉及的就是閉包的知識點了。


謝邀。在詳細理解一下,可能這樣會更好理解

#第一種方式
#來把你的提的問題分開來講,先定義一個now簡單函數
def now():
print(2015-3-25)
#這個都沒有問題,接下來我們在寫wrapper函數,來調用now
def wrapper(func,*args, **kw):
print(call %s(): % func.__name__)
return func(*args, **kw)
if __name__ == __main__:
wrapper(now)
#這種方法成功修改了函數 now 的行為,每一個調用now 的地方,都要給成調用wrapper

#第二種方式
#自定義decorator 函數,為了不改變對 now的調用。我們需要得到一個新的函數對象,它修改 now的行為,並用這個對象對 now賦值。
def log(func):
def wrapper(*args, **kw): #重新包裝func,其參數列表與func一致
print(call %s(): % func.__name__)
return func(*args, **kw)
return wrapper #調用 log 能返回一個函數對象,用於給func重新賦值
now = log(now)
if __name__ == __main__:
now()
#這樣,只要now被log 賦值一次,以後再調用now時,就調用的是包裝後的函數

#第三種方式
def log(func):
def wrapper(*args, **kw): #重新包裝func,其參數列表與func一致
print(call %s(): % func.__name__)
return func(*args, **kw)
return wrapper #調用 log 能返回一個函數對象,用於給func重新賦值
@log
def now():
print(2015-3-25)
now()


瀉藥,

建議找個簡單點的裝飾器理解,

直白點就是用來裝飾函數的函數,

也可以通過類實現,

或者說,理解:萬法皆對象!

log傳遞的now是一個函數,

等號前面的now只是個返回值(變數)。

最後,搜索一下:

python 裝飾器 閉包 上下文管理器 類

搞定這幾個關係,

你就晉陞到面向對象了~


我用專欄里的一篇文章來答題。

專欄鏈接:給妹子講python,歡迎大家關注,提意見!

【要點搶先看】

1.神奇的裝飾器到底是什麼
2.裝飾器的用法和語法糖
3.裝飾器如何添加參數

裝飾器是python里的一個非常有意思的部分,他用於封裝函數代碼,顯式的將封裝器應用到被封裝的函數上,從而使得他們選擇加入到裝飾器指定的功能中。對於在函數運行前處理常見前置條件(例如確認授權),或在函數運行後確保清理(輸出清除或異常處理),裝飾器都非常有用。

是不是感覺聽不明白,太繞了!

簡單來說,裝飾器就是實現了一個通用的功能,然後將這個通用的功能應用到不同的、需要使用這個功能的函數上,從而避免每次都在不同函數上反覆寫相同的功能代碼。

裝飾器的本質是一個函數,他接受被裝飾的函數作為位置參數,裝飾器通過使用該參數來執行某些操作,然後返回一個函數引用,這個函數可以是原始函數,或者是另外一個函數。

我們舉例子說明,裝飾器是這樣的函數,他們接受被裝飾的可調用函數作為唯一的參數,並且返回一個可調用函數,

registry = []
def register(decorated):
registry.append(decorated)
return decorated

def foo():
return 3

foo = register(foo)
print(registry[0])

&

register方法是一個簡單的裝飾器,它把被裝飾的函數添加到一個列表中,然後這裡是將未改變的被裝飾函數返回,可以看出,裝飾器一般是傳入被裝飾函數的引用,然後經過一些指定的處理,最後返回值也是一個函數引用。

還有一種更簡單的語法形式:

裝飾器的語法糖:我們這裡看到的對foo進行裝飾的方法是運用

foo = register(foo)語句,還有一種簡單的用法是在聲明函數的位置應用裝飾器,從而使得代碼更容易閱讀,並且讓人立刻意識到使用了裝飾器

registry = []
def register(decorated):
registry.append(decorated)
return decorated

@register
def foo(x=3):
return x

@register
def bar(x=5):
return 5

for func in registry:
print(func())

3
5

再看一個更複雜、更一般化的裝飾器函數。裝飾器的本質是在執行原有函數(被裝飾的函數)的同時,再加上一些額外的功能。

def requires_ints(decorated):
def inner(*args, **kwargs):
kwarg_values = [i for i in kwargs.values()]
for arg in list(args) + kwarg_values:
if not isinstance(arg, int):
raise TypeError({} only accepts integers as arguments.format(decorated.__name__))
return decorated(*args, **kwargs)
return inner

在這個裝飾器函數requires_ints我們可以看到,他定義了一個內嵌函數inner,這個內嵌函數的參數首先收集被裝飾函數的所有參數,然後對其進行判斷,判斷其是否為整數類型(這就是裝飾器添加的額外功能),然後再調用被裝飾的函數本身,最後將這個內嵌函數返回。因此當我們再用原函數名進行調用的時候,原來的被裝飾函數的引用就能指向這個新的內嵌函數,就能在實現原函數功能的基礎上,加上附加的功能了。

同時,我們再提煉一下這裡面的幾個重難點:

第一,requires_ints中,decorated這個變數是內嵌作用域的變數,在他調用退出後,返回的inner函數是可以記住這個變數的。

第二,python不支持函數的參數列表的多態,即一個函數名只能對應唯一的參數列表形式。

第三,在內嵌函數內部調用被裝飾函數的時候,使用了解包參數,關於這*args, **kwargs,的參數形式,前面章節中細講過。

那我們也用這個裝飾器來裝飾一個函數。

@requires_ints
def foo(x,y):
print(x+y)

foo(3,5)

8

這裡將名稱foo賦給inner函數,而不是賦給原來被定義的函數,如果運行foo(3,5),將利用傳入的這兩個參數運行inner函數,inner函數執行類型檢查,然後運行被裝飾方法,如果傳入的不是整形數,例如下面這個例子,那麼裝飾器的附加功能就會進行類型檢查:

@requires_ints
def foo(x,y):
print(x+y)

foo(a,5)

Traceback (most recent call last):
File "E:/12homework/12homework.py", line 15, in &
foo(a,5)
File "E:/12homework/12homework.py", line 7, in inner
raise TypeError({} only accepts integers as arguments.format(decorated.__name__))
TypeError: foo only accepts integers as arguments

其次內嵌的函數和被裝飾的函數的參數形式必須完全一樣,這裡用的*args, **kwargs概況函數參數的一般形式,因此也是完全對應的。

最後說說裝飾器參數

最後來介紹這個複雜一些的話題,裝飾器參數。之前我們列舉的常規例子里,裝飾器只有一個參數,就是被裝飾的方法。但是,有時讓裝飾器自身帶有一些需要的信息,從而使裝飾器可以用恰當的方式裝飾方法十分有用。

這些參數並不是和被裝飾的函數並列作為參數簽名,而是在原有裝飾器的基礎上額外再增加一層封裝,那麼,實質是這個接受其他參數的裝飾器並不是一個實際的裝飾器,而是一個返回裝飾器的函數。

最終返回的內嵌函數inner是最終使用indent和sort_keys參數的函數,這沒有問題

import json

def json_output(indent=None, sort_keys=False):
def actual_decorator(decorated):
def inner(*args, **kwargs):
result = decorated(*args, **kwargs)
return json.dumps(result, indent=indent, sort_keys=sort_keys)
return inner
return actual_decorator

@json_output(indent=8)
def do_nothing():
return {status:done,func:yes}

print(do_nothing())

{
"status": "done",
"func": "yes"
}

我們在這裡詳細解釋說明的是操作順序,看上去我們使用的是@json_output(indent=8),作這和之前的裝飾器語法糖看上去有些不同,實際上這個不是最終的裝飾器函數,通過調用json_output(indent=8),返回函數指針actual_decorator,這個函數才是真正放在@後的裝飾器函數,原始的被裝飾函數最終獲得了內涵更豐富的inner函數對象,完成了裝飾過程,值得一提的是,所謂的裝飾器參數最終傳給了最內層的inner函數。

記住,在定義裝飾器函數後,真正的裝飾器函數只有一個參數,那就是被裝飾的函數指針,而有其他參數的函數實質上只是裝飾器的外圍函數,他可以依據參數對裝飾器進行進一步的定製。一句話:一個函數不可能接受被裝飾的方法,又接受其他參數

在語法糖中@func這種不帶括弧的,就是直接使用裝飾器函數進行裝飾,如果是@func()帶括弧的,實質上是先調用func()函數返回真正的裝飾器,然後再用@進行調用。

@佈道 @飛龍 @2gua @yea yee @灰手


廖雪峰的教程是個好教程,但他省略了很多東西。如果有不明白的點,就看官方文檔好了。裝飾器里的代碼是在被裝飾的函數定義的時候執行的,也就是被裝飾的函數 被import那一刻執行的。仔細看上面的解釋你就會明白,裝飾器不過就只是一種語法而已,類似於C語言行位必加分號之類的,沒有什麼指向新函數老函數什麼亂七八糟的一說。


同初學者,嘗試寫一下,望指正。

先說用途,多個裝飾器可以一次性注釋掉,原函數now照常運行。於是裝飾器可以在對應的函數運行時,執行一些附加的功能,比如可以讓他每運作一次就列印一個標記,但當你不需要的時候可以快速注釋掉,且不改動程序主體。

基於這個原因,一旦函數運行,裝飾器就會:

把原函數封進一個新函數wrapper裡面,並返回這個這個新函數,相當於給機器加個外殼,你知道機器仍在運作,而外殼可以提供一些信息。注釋掉裝飾器,機器裸奔,一切照舊。

log(now),就是把原函數now本身作為參數引入log,加殼返回新函數,新函數參與後續運行。

所以書寫順序應該是

def now:

pass

然後now = log(now)

現在now = wrapper,這一步是為了偽裝,新函數必須改名為now,程序主體的才可以照常調用。教程後文提供的functools.wraps,就是為了進一步偽裝,把裝飾後的now.__name__由『wrapper』改回『now』。

不用裝飾器,你也可以直接原函數叫now 新定義一個包含now的now_2,然後把程序主體所有now改成now_2,顯然這不夠程序員。


我猜想你有這個疑問可能是沒搞清楚一點:

從解釋器的角度來講,並沒有 now 函數這個東西,所謂函數就是內存中的一塊代碼,恰巧有一個叫 now 的變數指向它。但 now 也可以再指向新的函數,新函數內部可以調用原函數,裝飾器就是這麼工作的。用 Javascript 的寫法可能容易理解一點:

var now = function old_func() { ... };

now = function new_func() { old_func(); }

now();


bing搜索the code ship decorator, 寫得極其清楚。

另外結合with語法的實現一起看,這兩個很相似。


這個是函數裝飾器,用log裝飾now()函數,實現既定義的功能。相當於把now作為log的參數


推薦閱讀:

Python中 a < b < c之類的語句到底是怎麼回事?
哪裡有免費的python3教程啊?最好是有例子的視頻教學
自學 Python 用記事本呢?還是有別的編輯器?
編程零基礎,如何學習Python?

TAG:Python | Python框架 | Python入門 | 裝飾器 |