標籤:

如何理解Python裝飾器?

盡量有中文的資料,淺顯一些,好理解的,謝謝


先來個形象比方

內褲可以用來遮羞,但是到了冬天它沒法為我們防風禦寒,聰明的人們發明了長褲,有了長褲後寶寶再也不冷了,裝飾器就像我們這裡說的長褲,在不影響內褲作用的前提下,給我們的身子提供了保暖的功效。

再回到我們的主題

裝飾器本質上是一個Python函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外功能,裝飾器的返回值也是一個函數對象。它經常用於有切面需求的場景,比如:插入日誌、性能測試、事務處理、緩存、許可權校驗等場景。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼並繼續重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。

先來看一個簡單例子:

def foo():
print(i am foo)

現在有一個新的需求,希望可以記錄下函數的執行日誌,於是在代碼中添加日誌代碼:

def foo():
print(i am foo)
logging.info("foo is running")

bar()、bar2()也有類似的需求,怎麼做?再寫一個logging在bar函數里?這樣就造成大量雷同的代碼,為了減少重複寫代碼,我們可以這樣做,重新定義一個函數:專門處理日誌 ,日誌處理完之後再執行真正的業務代碼

def use_logging(func):
logging.warn("%s is running" % func.__name__)
func()

def bar():
print(i am bar)

use_logging(bar)

邏輯上不難理解, 但是這樣的話,我們每次都要將一個函數作為參數傳遞給use_logging函數。而且這種方式已經破壞了原有的代碼邏輯結構,之前執行業務邏輯時,執行運行bar(),但是現在不得不改成use_logging(bar)。那麼有沒有更好的方式的呢?當然有,答案就是裝飾器。

簡單裝飾器

def use_logging(func):

def wrapper(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper

def bar():
print(i am bar)

bar = use_logging(bar)
bar()

函數use_logging就是裝飾器,它把執行真正業務方法的func包裹在函數裡面,看起來像bar被use_logging裝飾了。在這個例子中,函數進入和退出時 ,被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。

@符號是裝飾器的語法糖,在定義函數的時候使用,避免再一次賦值操作

def use_logging(func):

def wrapper(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper

@use_logging
def foo():
print("i am foo")

@use_logging
def bar():
print("i am bar")

bar()

如上所示,這樣我們就可以省去bar = use_logging(bar)這一句了,直接調用bar()即可得到想要的結果。如果我們有其他的類似函數,我們可以繼續調用裝飾器來修飾函數,而不用重複修改函數或者增加新的封裝。這樣,我們就提高了程序的可重複利用性,並增加了程序的可讀性。

裝飾器在Python使用如此方便都要歸因於Python的函數能像普通的對象一樣能作為參數傳遞給其他函數,可以被賦值給其他變數,可以作為返回值,可以被定義在另外一個函數內。

帶參數的裝飾器

裝飾器還有更大的靈活性,例如帶參數的裝飾器:在上面的裝飾器調用中,比如@use_logging,該裝飾器唯一的參數就是執行業務的函數。裝飾器的語法允許我們在調用時,提供其它參數,比如@decorator(a)。這樣,就為裝飾器的編寫和使用提供了更大的靈活性。

def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper

return decorator

@use_logging(level="warn")
def foo(name=foo):
print("i am %s" % name)

foo()

上面的use_logging是允許帶參數的裝飾器。它實際上是對原有裝飾器的一個函數封裝,並返回一個裝飾器。我們可以將它理解為一個含有參數的閉包。當我 們使用@use_logging(level="warn")調用的時候,Python能夠發現這一層的封裝,並把參數傳遞到裝飾器的環境中。

類裝飾器

再來看看類裝飾器,相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優點。使用類裝飾器還可以依靠類內部的\_\_call\_\_方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。

class Foo(object):
def __init__(self, func):
self._func = func

def __call__(self):
print (class decorator runing)
self._func()
print (class decorator ending)

@Foo
def bar():
print (bar)

bar()

functools.wraps

使用裝飾器極大地復用了代碼,但是他有一個缺點就是原函數的元信息不見了,比如函數的docstring、__name__、參數列表,先看例子:

裝飾器

def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging

函數

@logged
def f(x):
"""does some math"""
return x + x * x

該函數完成等價於:

def f(x):
"""does some math"""
return x + x * x
f = logged(f)

不難發現,函數f被with_logging取代了,當然它的docstring,__name__就是變成了with_logging函數的信息了。

print f.__name__ # prints with_logging
print f.__doc__ # prints None

這個問題就比較嚴重的,好在我們有functools.wraps,wraps本身也是一個裝飾器,它能把原函數的元信息拷貝到裝飾器函數中,這使得裝飾器函數也有和原函數一樣的元信息了。

from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging

@logged
def f(x):
"""does some math"""
return x + x * x

print f.__name__ # prints f
print f.__doc__ # prints does some math

內置裝飾器

@staticmathod、@classmethod、@property

裝飾器的順序

@a
@b
@c
def f ():

等效於

f = a(b(c(f)))


之前寫了一個1.0版,現在把更加完整的2.0版奉上。

1.0版已經被我殺掉了。

--------------------------------------2.0版-------------------------------------------------------------------

Attention:為了解釋得通俗易懂,所以很多概念沒有描述的很準確,很多只是「意思意思」,所以大家重在意會哈。如果各位知友有更好的解釋和講解的方法,還請在評論區不吝賜教。

學習python有一段時間了,對於裝飾器的理解又多了一些,現在我重新再寫一次對於裝飾器的理解。

在講之前我需要先鋪墊一下基礎知識,如果你已經掌握了就請跳過。

--------------------------------------基礎知識分割線-----------------------------------------------------

1、萬物皆對象

在python中,不管什麼東西都是對象。對象是什麼東西呢?

對象就是你可以用來隨意使用的模型。當你需要的時候就拿一個,不需要就讓它放在那,垃圾回收機制會自動將你拋棄掉的對象回收。

可能對這個理解有一點雲里霧裡的感覺,甚至還覺得對這個概念很陌生。其實如果你都學到裝飾器這裡了,你已經使用過不少對象啦。

比如,我寫了一個函數:

def cal(x, y):
result = x + y
return result

這時,你可以說,你創造了一個叫做cal的函數對象。

然後,你這樣使用了它:

cal(1,2)

或者,你這樣使用了它:

calculate = cal
calculate(1,2)

在第一種方式下,你直接使用了cal這個函數對象;

在第二種方式下,你把一個名為calculate的變數指向了cal這個函數對象。如果各位對類的使用很熟悉的話,可以把這個過程看作「實例化」。

也就是說,對象,就像是一個模子,當你需要的時候,就用它倒一個模型出來,每一個模型可以有自己不同的名字。在上面的例子中,calculate是一個模型,而你寫的cal函數就是一個模子。

2、請理解函數帶括弧和不帶括弧時分別代表什麼意思。

在上一個例子中,如果你只是寫一個cal(也就是沒有括弧),那麼此時的cal僅僅是代表一個函數對象;當你這樣寫cal(1, 2)時,就是在告訴編譯器「執行cal這個函數」。

3、請確保能夠理解帶星號的參數是什麼意思。

這個屬於函數基礎,要是你還沒有聽說過,那麼就該回去好好複習一下了。具體講解我就略過了。

----------------------------------------------正文分割線---------------------------------------------------

1、裝飾器是什麼?

裝飾器,顧名思義,就是用來「裝飾」的。

它長這個樣:

@xxx

其中"xxx"是你的裝飾器的名字。

它能裝飾的東西有:函數、類

2、為什麼我需要裝飾器?

有一句名言說的好(其實是我自己說的):

「每一個輪子都有自己的用處」

所以,每一個裝飾器也有自己的用處。

裝飾器主要用來「偷懶」(輪子亦是如此)。

比如:

你寫了很多個簡單的函數,你想知道在運行的時候是哪些函數在執行,並且你又覺得這個沒有必要寫測試,只是想要很簡單的在執行完畢之前給它列印上一句「Start」,那該怎麼辦呢?你可以這樣:

def func_name(arg):
print Start func_name
sentences

這樣做沒有錯,but, 你想過沒有,難道你真的就想給每一個函數後面都加上那麼一句嗎?等你都運行一遍確定沒有問題了,再回來一個一個的刪掉print不覺得麻煩嗎?什麼?你覺得寫一個還是不麻煩的,那你有十個需要添加的函數呢?二十個?三十個?(請自行將次數加到超過你的忍耐閾值)……

如果你知道了裝飾器,情況就開始漸漸變得好一些了,你知道可以這樣寫了:

def log(func):
def wrapper(*arg, **kw):
print Start %s % func
return func(*arg, **kw)
return wrapper

@log
def func_a(arg):
pass

@log
def func_b(arg):
pass

@log
def func_c(arg):
pass

其中,log函數是裝飾器。

把裝飾器寫好了之後,只需要把需要裝飾的函數前面都加上@log就可以了。在這個例子中,我們一次性就給三個函數加上了print語句。

可以看出,裝飾器在這裡為我們節省了代碼量,並且在你的函數不需要裝飾的時候直接把@log去掉就可以了,只需要用編輯器全局查找然後刪除即可,快捷又方便,不需要自己手工的去尋找和刪除print的語句在哪一行。

-----------------------------------------------重點分割線--------------------------------------------------

3、裝飾器原理

在上一段中,或許你已經注意到了"log函數是裝飾器"這句話。沒錯,裝飾器是函數。

接下來,我將帶大家探索一下,裝飾器是怎麼被造出來的,來直觀的感受一下裝飾器的原理。

先回到剛才的那個添加Start問題。

假設你此時還不知道裝飾器。

將會以Solution的方式呈現。

S1 我有比在函數中直接添加print語句更好的解決方案!

於是你這樣做了:

def a():
pass
def b():
pass
def c():
pass

def main():
print Start a
a()
print Start b
b()
print Start c
c()

感覺這樣做好像沒什麼錯,並且還避免了修改原來的函數,如果要手工刪改print語句的話也更方便了。嗯,有點進步了,很不錯。

S2 我覺得剛剛那個代碼太丑了,還可以再優化一下!

於是你這樣寫了:

def a():
pass
def b():
pass
def c():
pass

def decorator(func):
print Start %s% func
func()

def main():
decorator(a)
decorator(b)
decorator(c)

你現在寫了一個函數來代替你為每一個函數寫上print語句,好像又節省了不少時間。你欣喜的喝了一口coffee,對自己又一次做出了進步感到很滿意。

嗯,確實是這樣。

於是你選擇出去上了個廁所,把剛剛憋的尿全部都排空(或許還有你敲代碼時喝的coffee)。

回來之後,頓時感覺神清氣爽!你定了定神,看了看自己剛才的「成果」,似乎又感到有一些不滿意了。

因為你想到了會出現這樣的情況:

def main():
decorator(a)
m = decorator(b)
n = decorator(c) + m
for i in decorator(d):
i = i + n
......

來,就說你看到滿篇的decorator你暈不暈!大聲說出來!

S3 你又想了一個更好的辦法。

於是你這樣寫了:

def a():
pass
def b():
pass
def c():
pass

def decorator(func):
print Start %s % func
return func

a = decorator(a)
b = decorator(b)
c = decorator(c)

def main():
a()
b()
c()

這下總算是把名字給弄回來了,這樣就不會暈了。你的嘴角又一次露出了欣慰的笑容(內心OS:哈哈哈,爺果然很6!)。於是你的手習慣性的端起在桌上的coffee,滿意的抿了一口。

coffee的香味縈繞在唇齒之間,你滿意的看著屏幕上的代碼,突然!腦中彷彿划過一道閃電!要是a、b、c 三個函數帶參數我該怎麼辦?!

你放下coffee,手托著下巴開始思考了起來,眉頭緊鎖。

像這樣寫肯定不行:

a = decorator(a(arg))

此時的本應該在decorator中做為一個參數對象的a加上了括弧,也就是說,a在括弧中被執行了!你只是想要a以函數對象的形式存在,乖乖的跑到decorator中當參數就好了。執行它並不是你的本意。

那該怎麼辦呢?

你扶了扶眼鏡,嘴裡開始念念有詞「萬物皆對象萬物皆對象……」

你的額頭上開始漸漸的滲出汗珠。

突然,你的身後的背景暗了下來,一道光反射在眼鏡上!不自覺的說了句

「真相はひとつだけ」!

S4 你飛速的寫下如下代碼。

def a(arg):
pass
def b(arg):
pass
def c(arg):
pass

def decorator(func):
def wrapper(*arg, **kw)
print Start %s % func
return func(*arg, **kw)
return wrapper

a = decorator(a)
b = decorator(b)
c = decorator(c)

def main():
a(arg)
b(arg)
c(arg)

decorator函數返回的是wrapper, wrapper是一個函數對象。而a = decorator(a)就相當於是把 a 指向了 wrapper, 由於wrapper可以有參數,於是變數 a 也可以有參數了!

終於!你從焦灼中解脫了出來!

不過, 有了前幾次的經驗,你這一次沒有笑。

你又仔細想了想,能不能將a = decorator(a)這個過程給自動化呢?

於是你的手又開始在鍵盤上飛快的敲打,一會兒過後,你終於完成了你的「作品」。

你在python中添加了一個語法規則,取名為「@」,曰之「裝飾器」。

你此時感覺有些累了, 起身打開門, 慢步走出去,深吸一口氣,感覺陽光格外新鮮。

你的臉上終於露出了一個大大的笑容。

-------------------------------亂侃結束分割線------------------------------------------------------------

講到這裡,我想大家應該差不多都明白了裝飾器的原理。

在評論中有知友問到,要是我的裝飾器中也有參數該怎麼辦呢?

要是看懂了剛才添加參數的解決方案,也就不覺得難了。

再加一層就解決了。

def decorator(arg_of_decorator):
def log(func):
def wrapper(*arg, **kw):
print Start %s % func
#TODO Add here sentences which use arg_of_decorator
return func(*arg, **kw)
return wrapper
return log

感謝閱讀 : )

----------------------------------------------------------------------------------------------------------------

請大家著重理解思路。

答主是一名在校大學生,主修英語專業,目前大二,希望能夠和各位知友多多交流。要是有同道中人關注我就更好了!

我先撤退了。

撒花!??ヽ(?▽?)ノ?


先理解一下閉包的概念吧,之前回答過一個有關閉包和裝飾器的問題,可以參考一下:

Python 里函數里返回一個函數內部定義的函數? - 知乎用戶的回答


首先,本垃圾文檔工程師又來了。開始日常的水文寫作。起因是看到這個問題如何理解Python裝飾器?,正好不久前給人講過這些,本垃圾於是又開始新的一輪辣雞文章寫作行為了。

預備知識

首先要理解裝飾器,首先要先理解在 Python 中很重要的一個概念就是:「函數是 First Class Member」 。這句話再翻譯一下,函數是一種特殊類型的變數,可以和其餘變數一樣,作為參數傳遞給函數,也可以作為返回值返回。

def abc():
print("abc")

def abc1(func):
func()

abc1(abc)

這段代碼的輸出就是我們在函數 abc 中輸出的 abc 字元串。過程很簡單,我們將函數 abc 作為一個參數傳遞給 abc1 ,然後,在 abc1 中調用傳入的函數

再來看一段代碼

def abc1():
def abc():
print("abc")
return abc
abc1()()

這段代碼輸出和之前的一樣,這裡我們將在 abc1 內部定義的函數 abc 作為一個變數返回,然後我們在調用 abc1 獲取到返回值後,繼續調用返回的函數。

好了,我們再來做一個思考題,實現一個函數 add ,達到 add(m)(n) 等價於 m+n 的效果。這題如果把之前的 First-Class Member 這一概念理清楚後,我們便能很清楚的寫出來了

def add(m):
def temp(n):
return m+n
return temp
print(add(1)(2))

嗯,這裡輸出就是 3 。

正文

看了前面的預備知識後,我們便可以開始今天的主題了

先來看一個需求吧

現在我們有一個函數

def range_loop(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result

現在我們要給這個函數加上一些代碼,來計算這個函數的運行時間。

我們大概一想,寫出了這樣的代碼

import time
def range_loop(a,b):
time_flag=time.time()
for i in range(a,b):
temp_result=a+b
print(time.time()-time_flag)
return temp_result

先且不論,這樣計算時間是不是準確的,現在我們要給如下很多函數加上一個時間計算的功能

import time
def range_loop(a,b):
time_flag=time.time()
for i in range(a,b):
temp_result=a+b
print(time.time()-time_flag)
return temp_result
def range_loop1(a,b):
time_flag=time.time()
for i in range(a,b):
temp_result=a+b
print(time.time()-time_flag)
return temp_result
def range_loop2(a,b):
time_flag=time.time()
for i in range(a,b):
temp_result=a+b
print(time.time()-time_flag)
return temp_result

我們初略一想,嗯,Ctrl+C,Ctrl+V。emmmm 好了,現在你們不覺得這段代碼特別臟么?我們想讓他變得乾淨點怎麼辦?

我們想了想,按照之前說的 First-Class Member 的概念。然後寫出了如下的代碼

import time
def time_count(func,a,b):
time_flag=time.time()
temp_result=func(a,b)
print(time.time()-time_flag)
return temp_result

def range_loop(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result
def range_loop1(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result
def range_loop2(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result
time_count(range_loop,a,b)
time_count(range_loop1,a,b)
time_count(range_loop2,a,b)

嗯,看起來像那麼回事,好了好了,我們現在新的問題又來了,我們現在是假設,我們所有函數都只有兩個參數傳入,那麼現在如果想支持任意參數的傳入怎麼辦?我們眉頭一皺,寫下了如下的代碼

import time
def time_count(func,*args,**kwargs):
time_flag=time.time()
temp_result=func(*args,**kwargs)
print(time.time()-time_flag)
return temp_result

def range_loop(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result
def range_loop1(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result
def range_loop2(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result
time_count(range_loop,a,b)
time_count(range_loop1,a,b)
time_count(range_loop2,a,b)

好了,現在看起來,有點像模像樣了,但是我們再想想,這段代碼實際上改變了我們的函數調用方式,比如我們直接運行 range_loop(a,b) 還是沒有辦法獲取到函數執行時間。那麼現在我們如果不想改變函數的調用方式,又想獲取到函數的運行時間怎麼辦?

很簡單嘛,替換一下不就好了

import time
def time_count(func):
def wrap(*args,**kwargs):
time_flag=time.time()
temp_result=func(*args,**kwargs)
print(time.time()-time_flag)
return temp_result
return wrap

def range_loop(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result
def range_loop1(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result
def range_loop2(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result
range_loop=time_count(range_loop)
range_loop1=time_count(range_loop1)
range_loop2=time_count(range_loop2)
range_loop(1,2)
range_loop1(1,2)
range_loop2(1,2)

emmmm,這樣看起來感覺舒服多了?既沒有改變原有的運行方式,也輸出了函數運行時間。

但是。。。你們不覺得手動替換太噁心了么???喵喵喵???還有什麼可以簡化下的么??

好了,Python 知道我們是愛吃糖的孩子,給我們提供了一個新的語法糖,這也是今天的男一號,Decorator 裝飾器

說說 Decorator

我們前面已經實現了,在不改變函數特性的情況下,給原有的代碼新增一點功能,但是我們也覺得這樣手動的替換,太噁心了,是的 Python 官方也覺得這樣很噁心,所以新的語法糖來了

我們上面的代碼可以寫成這樣了

import time
def time_count(func):
def wrap(*args,**kwargs):
time_flag=time.time()
temp_result=func(*args,**kwargs)
print(time.time()-time_flag)
return temp_result
return wrap
@time_count
def range_loop(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result
@time_count
def range_loop1(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result
@time_count
def range_loop2(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result
range_loop(1,2)
range_loop1(1,2)
range_loop2(1,2)

哇,寫到這裡,你是不是恍然大悟!まさか???是的,其實 @ 符號其實是一個語法糖,他將我們之前的手動替換的過程交給了環境執行。好了用人話描述下,@ 的作用是將被包裹的函數作為一個變數傳遞給裝飾函數/類,將裝飾函數/類返回的值替換原本的函數。

@decorator
def abc():
pass

如同前面所講的一樣,實際上是發生了一個特殊的替換過程 abc=decorator(abc) ,好了我們來做幾個題來練習下吧?

def decorator(func):
return 1
@decorator
def abc():
pass
abc()

這段代碼會發生什麼?答:會拋出異常。為啥啊?答:因為裝飾的時候發生了替換,abc=decorator(abc) ,替換後 abc 的值為 1 。整數默認不能作為一個函數進行調用。

def time_count(func):
def wrap(*args,**kwargs):
time_flag=time.time()
temp_result=func(*args,**kwargs)
print(time.time()-time_flag)
return temp_result
return wrap

def decorator(func):
def wrap(*args,**kwargs):
temp_result=func(*args,**kwargs)
return temp_result
return wrap

def decorator1(func):
def wrap(*args,**kwargs):
temp_result=func(*args,**kwargs)
return temp_result
return wrap

@time_count
@decorator
@decorator1
def range_loop(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result

這段代碼怎麼替換的?答:time_count(decorator(decorator1(range_loop)))

嗯,現在是不是對裝飾器什麼的有了基本的了解?

擴展一下

現在,我想修改下前面寫的 time_count 函數,讓他支持傳入一個 flag 參數,當 flag 為 True 的時候,輸出函數運行時間,為 False 的時候不輸出時間

我們一步步來,我們先假設新的函數叫做 time_count_plus

我們想實現的效果是這樣的

@time_count_plus(flag=True)
def range_loop(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result

嗯,我們看了下,首先我們調用了 time_count_plus(flag=True) 一次,將它返回的值作為一個裝飾函數來替換 range_loop ,OK 那麼我們首先 time_count_plus 要接收一個參數,返回一個函數對吧

def time_count_plus(flag=True):
def wrap1(func):
pass
return wrap1

好了,現在返回了一個函數來作為裝飾函數,然後我們說了 @ 其實觸發了一次替換過程,好那麼我們現在的替換是不是 range_loop=time_count_plus(flag=True)(range_loop) 好了,現在大家應該很清楚了,我們在 wrap1 裡面是不是還應該有一個函數並返回?

嗯,最終的代碼如下

def time_count_plus(flag=True):
def wrap1(func):
def wrap2(*args,**kwargs):
if flag:
time_flag=time.time()
temp_result=func(*args,**kwargs)
print(time.time()-time_flag)
else:
temp_result=func(*args,**kwargs)
return temp_result
return wrap2
return wrap1
@time_count_plus(flag=True)
def range_loop(a,b):
for i in range(a,b):
temp_result=a+b
return temp_result

是不是這樣就清楚多啦!

擴展兩下

好了,我們現在有新的需求來了

m=3
n=2
def add(a,b):
return a+b

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

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

def div(a,b):
return a/b

現在我們有字元串 a , a 的值可能為 +、-、*、/ 那麼現在,我們想根據 a 的值來調用對應的函數怎麼辦?

我們煎蛋一想,嗯,邏輯判斷嘛

m=3
n=2
def add(a,b):
return a+b

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

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

def div(a,b):
return a/b
a=input(請輸入 + - * / 中的任意一個
)
if a==+:
print(add(m,n))
elif a==-:
print(sub(m-n))
elif a==*:
print(mul(m,n))
elif a==/:
print(div(m,n))

但是這段代碼,if else 是不是太多了點?我們仔細一想,用一下 First-Class Member 的特性,然後配合 dict 實現操作符和函數之間的關聯。

m=3
n=2
def add(a,b):
return a+b

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

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

def div(a,b):
return a/b
func_dict={"+":add,"-":sub,"*":mul,"/":div}
a=input(請輸入 + - * / 中的任意一個
)
func_dict[a](m,n)

emmmm,看起來不錯啊,但是我們註冊的過程能不能再簡化一點? 嗯,這個時候裝飾器語法特性就能用上了

m=3
n=2
func_dict={}
def register(operator):
def wrap(func):
func_dict[operator]=func
return func
return wrap
@register(operator="+")
def add(a,b):
return a+b
@register(operator="-")
def sub(a,b):
return a-b
@register(operator="*")
def mul(a,b):
return a*b
@register(operator="/")
def div(a,b):
return a/b

a=input(請輸入 + - * / 中的任意一個
)
func_dict[a](m,n)

嗯,還記得我們前面說的使用 @ 語法的時候,實際上是觸發了一個替換的過程么?這裡就是利用這一特性,在裝飾器觸發的時候,註冊函數映射,這樣我們直接根據 a 的值來獲取函數處理數據。另外請注意一點,我們這裡沒有必要修改原函數,所以我們沒有必要寫第三層的函數。

如果有熟悉 Flask 同學就知道,在調用 route 方法註冊路由的時候,也是使用了這一特性 ,可以參考另外一篇很久前寫的垃圾水文 菜鳥閱讀 Flask 源碼系列(1):Flask的router初探

總結

其實全文下來,大家應該能知道這樣一點東西。Python 中的裝飾器其實是 First-Class Member 概念的更進一層應用,我們將函數傳遞給其餘函數,包裹上新的功能後再行返回。@ 其實只是將這樣一個過程進行了簡化而已。在 Python 中,裝飾器無處不在,很多官方庫中的實現也依賴於裝飾器,比如很久之前寫過這樣一篇垃圾水文 Python 描述符入門指北。

嗯,今天就先寫到這裡吧!


打個廣告

Python 碼坊

個人專欄。。沒事寫點水文什麼的


Python的裝飾器的英文名叫Decorator,當你看到這個英文名的時候,你可能會把其跟Design Pattern里的Decorator搞混了,其實這是完全不同的兩個東西。

雖然好像,他們要乾的事都很相似——都是想要對一個已有的模塊做一些「修飾工作」,所謂修飾工作就是想給現有的模塊加上一些小裝飾(一些小功能,這些小功能可能好多模塊都會用到),但又不讓這個小裝飾(小功能)侵入到原有的模塊中的代碼里去。

你想更深入了解學習Python知識體系,你可以看一下我們花費了一個多月整理了上百小時的幾百個知識點體系內容:

【超全整理】《Python自動化全能開發從入門到精通》筆記全放送


內置語法糖 格式優美 逼格較高

在代碼中使用洽到好處的裝飾器瞬間讓代碼優美很多

寫起來也很簡單 無參的裝飾器參數是要裝飾的函數 有參裝飾器參數是需要的參數 最後返回的是內部函數 參考http://m.blog.csdn.net/blog/yueguanghaidao/10089181


`func = decorator(func)` 的語法糖


我覺得我這篇講的還算可以理解,供參考:https://zhuanlan.zhihu.com/p/21696291

這一篇 https://zhuanlan.zhihu.com/p/23064109 也有涉及,但主要還是上一篇。


之前給出一個鏈接, 現在嘗試用自己方式梳理一下 # 有時愛用蹩腳的英文注釋, 唔.

&>&>

# 先從函數說起

def foo1():
print(this is a function)

foo1() # print ... in console.

這是一種最為簡單的函數-- 不涉及到任何變數, 參數 只是做了一件不以為然的事兒

# 函數的域 作用範圍 以及一個局部變數

global_string = this is a global string, my name is global_string

def foo2():
# lets see its locals variable
# local variable in foo2
x = 3
print(foo2 locals:, locals())

foo2() # foo2 locals: {x: 3}
print(-*40)

# check global variable # gets a dictionary, and global_string inside.
# print(globals: , globals())

# 一個變數的生存的周期

def foo3():
x = 3
print("x value:" , x)
foo3()

try to run x += 3
# x += 5, uncomment when you run.
# get NameError -- x is not defined = x is dead.

# 下面來看帶有參數的函數

def foo4(s):
print(&, s)

foo4(foobar)

## 或者可以多個參數, 甚至是默認的

def foo5(s, repeat= 3):
print(&, s*repeat)

foo5(foobar)

if call a function with un-matched arguments, error comes

# foo5(1,2,3) # TypeError: foo5() takes from 1 to 2 positional arguments but 3 were given
foo5 能接收1-2個參數, 大哥你給了3個. typeerror

# 嵌套函數

def outer():
x = 71
def inner():
print(hello, i am inner)
print(outer variable x ,x)
inner()

outer()

可以看到 內部函數能夠訪問外部函數的變數

# 把函數當作一個對象

查看類的屬性 __class__ built-in function
i = 3
print(i.__class__) # &
print(outer.__class__) # &

# ==&> 所以 既然 一個整數i 可以當作某函數的參數,

那麼 這裡的 函數 outer 當然也可以作為某個函數的參數!

def applyfunc(func,args,repeat=3):
i = 0
repeat = 3 if repeat &<= 1 else repeat while i &< repeat: func(args) i += 1 def test(s): print(a test function, s) applyfunc(test, love is important, repeat=3)

可以看到 通過調用一個函數 applyfunc -- 讓一個簡單函數運行至少3次

# Closures 不想翻譯成閉包

def outer2():
x = 127
def inner2():
print(x)
return inner2

foobar = outer2()
foobar # print nothing

print(foobar.__closure__) # (&,)

可以看到 foobar中封存了一個整數對象 int object at 0x......

foobar() # print

x 是outer2中的局部變數, 只有當outer2運行時 x才開始出現.

## Closures-2

def outer(x):
def inner():
print(inner just print x:, x)
return inner

print1 = outer(1)
print2 = outer(2)

print(print1.__closure__) # int object at 0x5C3EC010&>
print(print2.__closure__) # int object at 0x5C3EC020&>,

print1()
print2()

#== closure 是Python中一種機制, 對enclosing variables 的一個儲藏櫃

# Decorators 終於到了裝飾器

def outer(somefunc):
def inner():
print(have not run , somefunc.__name__, end=
)
result = somefunc()
print(result + finished )
return inner

def foo6():
return i am foo6

decorator = outer(foo6)

decorator()

上例演示了decorator 基本作用 - 以函數作參數, 並且輸出一個裝飾後的新函數

就字面理解下, 原來的函數是個毛坯房 只有一床板湊合睡, 找2裝修小工後,爽了.

# decorator - 2 , look like more useful

class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y

def __repr__(self):
return I am a {name}, my attributes: {dict}.format(name = self.__class__.__name__, dict=self.__dict__)

## definition calculation
def add(a, b):
return Point(a.x + b.x, a.y+b.y)

p1 = Point(500, 10)
print(p1)
p2 = Point(30, -100)
print(add(p1,p2))

Now we want to do some value-check, to make sure the x, y validation

比如我們現在只想要點運算時 作數值的檢查: 要求點是 100*100這個範圍的 (0-100 這個正方形內) ,對於異常作邊界處理

def wrapper(func):
def checker(a, b):
a. x = max(0, a.x) ; a. y = max(0, a.y)
a. x = min(100, a.x);a. y = min(100, a.y)
b. x = max(0, b.x) ; b. y = max(0, b.y)
b. x = min(100, b.x);b. y = min(100, b.y)
result = Point(a.x +b.x, a.y+b.y)
return result
return checker

add_decorator = wrapper(add)

p1 = Point(500, 10)
p2 = Point(30, -100)
# print(add(p1,p2))
print(decorator !)
print(add_decorator(p1, p2))
#=&> after check, it becomes 100+30, 10+0

# 最後 @ 符號

因為裝飾會經常使用, 為了避免上述麻煩的裝飾方法 就想到一個簡寫

@wrapper
def add_checked(a, b):
return Point(a.x +b.x, a.y+b.y)
print(skilled decorator using @)
print(add_checked(p1, p2))

evernote 文字版, 習慣用這個存了.

一步步理解Python中的Decorator

原文參考:

# refer: simeonfranklin.com

推薦閱讀:

& chapter 9 - Metaprogramming, 9.1 9.2 ...

以及一些類似的中文博客資料, 都比較相似, 如果了解更多還是希望廣泛地去看一些函數編程(FP)的內容, 筆者之前在學Scala, 後來又去了解了一些Haskell的基礎知識,

Python: 會打扮的裝飾器 · FunHacks


來個乾貨,我的總結:Python Decorators 裝飾器總結Decorators-for-Functions-and-Methods-Python。獻醜了,歡迎探討。


StackOverflow 上有一個關於 python decorators 的問題,這個回答做了很詳細的解釋,可以好好看看。

How can I make a chain of function decorators in Python?


簡單來講,可以不嚴謹地把Python的裝飾器看做一個包裝函數的函數。

比如,有一個函數:

def func():
print func() run.

if __main__ == __name__:
func()

運行後將輸出:

func() run.

現在需要在函數運行前後列印一條日誌, 但是又不希望或者沒有許可權修改函數內部的結構, 就可以用到裝飾器(decorator):

def log(function):
def wrapper(*args, **kwargs):
print before function [%s()] run. % function.__name__
rst = function(*args, **kwargs)
print after function [%s()] run. % function.__name__
return rst
return wrapper

@log
def func():
print func() run.

if __main__ == __name__:
func()

對於原來的函數"func()"並沒有做修改,而是給其使用了裝飾器log,運行後的輸出為:

before function [func()] run.
func() run.
after function [func()] run.

把"@log"放到func()函數定義的地方,相當於執行了如下語句:

func = log(func)

因為log()返回了一個函數, 所以原本的func指向了log()返回的函數wrapper。wrapper的參數列表為(*args, **kwargs), 所以其可以接受所有的參數調用, 在wrapper中,先列印了一行

before function [%s()] run. % function.__name__

(在Python中函數也是對象,函數的__name__是它的名字),然後執行了原來的函數並記錄了返回值,在輸出

after function [%s()] run. % function.__name__

後返回了函數的執行結果。

如果decorator本身需要傳入參數,那就需要編寫一個返回decorator的decorator。比如在Flask中:

@app.route(/)
def index():
return hello, world!

實現如下:

import functools

def log(text=):
def decorator(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
print before function [%s()] run, text: [%s]. % (function.__name__, text)
rst = function(*args, **kwargs)
print after function [%s()] run, text: [%s]. % (function.__name__, text)
return rst
return wrapper
return decorator

@log(log text)
def func():
print func() run.

if __main__ == __name__:
func()

輸出如下:

before function [func()] run, text: [log text].
func() run.
after function [func()] run, text: [log text].

最後腦洞小開一下, 有沒有辦法實現既支持不帶參數(如log), 又支持帶參數(如log(text))的decorator嗎?

import functools

def log(argument):
if not callable(argument):
def decorator(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
print before function [%s()] run, text: [%s]. % (function.__name__, text)
rst = function(*args, **kwargs)
print after function [%s()] run, text: [%s]. % (function.__name__, text)
return rst
return wrapper
return decorator
def wrapper(*args, **kwargs):
print before function [%s()] run. % function.__name__
rst = argument(*args, **kwargs)
print after function [%s()] run. % function.__name__
return rst
return wrapper

如上~~~


我初學,感覺就是把一個函數包裝成另一個函數的一部分,能用.attributes訪問。


初學者又遇到難題了吧?聽都沒聽說過,裝飾器……英文:decorator,啥意思?室內裝飾師,油漆匠,適於室內裝飾的……

聽不懂,更不知道啥意思,怎麼用了吧?這不可怕,可怕的是那些大神,把這個東西寫的巨複雜,你要看懂這個概念,還先要去學10個其它的概念,回來再看還不一定能看懂。

日常生活中有這種情況,商場剛裝修完,都是大玻璃門,怕有人撞上,就要在上面用油漆寫「小心琉璃」……但是玻璃太多,都寫上字太費勁,乾脆就用油漆畫個叉就達到目的了。用油漆簡單裝飾一下,完成省得有人撞上和指路的功能。

「裝飾器」就是做個裝修標記,並且有它的功能,比如「小心玻璃」和「指路」,看到這個標記你就知道它要表達什麼意思了,按著它的指示來肯定沒錯。

在Python里裝飾器的定義:在程序運行時,增加動態功能的方式,稱之為「裝飾器」,裝飾器本質上也是一個Python函數。

那麼問題來了,有可能初學者對這個定義里的函數不理解,不得不多講一點。

函數,「數」好理解,「函」本意就是一種平級之間的信,比如兩個單位傳達或者反饋信息。

假設有這樣一種情況,你要出去旅遊購物,又怕被黑,在出發前,找到好朋友諸葛不亮出主意,他給了你一個密函,告訴你到地方再打開。等你到地方打開一看,上寫5個大字「報價砍一半」。接下來買什麼東西,都按這個原則來,這個就是最基本的函數了。

寫成公式:購買價格=對方報價乘0.5,這個公式就是函數公式。用標準定義來說「設在某變化過程中有兩個變數x、y,如果對於x在某一範圍內的每一個確定的值,y都有唯一確定的值與它對應,那麼就稱y是x的函數,x叫做自變數。」來段簡單的代碼感受一下這個「講價函數」。

為了方便初學者,本文所有函數名稱都使用全拼。

def Jianjia(x):
y =0.5
x = x * y
return x
print(Hanshu(100))
print(Hanshu(50))

輸出結果,你猜是不是50和25?我猜你猜對了。

def是固定格式,Hanshu就是函數的名稱,(x)就是參數。

接下來,就要旅遊購物了,好在有個機器人替我購物,它只能聽懂Python語言,我要告訴它購買什麼和詳細的購買程序。

我要買三樣東西:豬、大象、長頸鹿,流程是詢價,對應東西,再購買。

有三個購買函數(Goumai_1,Goumai_2,Goumai_3),寫出來的流程是這樣:

def Goumai_1():
print(詢價)
print(豬)
print(購買成功)

def Goumai_2():
print(詢價)
print(大象)
print(購買成功)

def Goumai_3():
print(詢價)
print(長頸鹿)
print(購買成功)

Goumai_1()
Goumai_2()
Goumai_3()

執行結果是這樣:

詢價

購買成功
詢價
大象
購買成功
詢價
長頸鹿
購買成功

現在我發現,這樣寫太複雜了。因為「詢價」和「購買成功」動作是一樣的,而且我要想在「購買成功」上加一個感嘆號,需要加三次才能成功,如果買1000個東西,就要加1000次感嘆號了,想想就要累死了。

於是我優化了一下程序,把購買東西Goumai(Dongxi)定義成了一個函數,執行的結果和上一個程序還是一樣的,而且「購買成功」還加了一個感嘆號,只操作了一次,可以顯示三次啊。

def Goumai(Dongxi):
print(詢價)
Dongxi()
print(購買成功!)

def Zhu():
print(豬)

def Daxiang():
print(大象)

def Changjinglu():
print(長頸鹿)

Goumai(Zhu)
Goumai(Daxiang)
Goumai(Changjinglu)

但是問題又來了,每次我還要告訴機器人「購買(豬)」,「購買(大象)」,「購買(長頸鹿)」,能不能幹脆點「購買」兩個字都不說?直接在「豬、大象、長頸鹿」上做個標記,機器人看到標記就執行購買動作呢?答案當然是可以了。

這時「購買」就變成了一個固定的動作,而不是三個步驟,為了讓機器人理解,我在「購買」函數里直接定義了這個動作函數。

def Goumai(Dongxi):
def Dongzuo():
print(詢價)
Dongxi()
print(購買成功!)
return Dongzuo

這個時候裝飾器才正式出場,裝飾器就是用@來表示,加上動作函數名稱。

比如下文中的,漂亮嗎?

@Goumai

可以理解成用@符號把「購買」這個兩個字像用口香糖粘在了物品名稱上一樣,這回有點像「裝飾」的意思了吧。

機器人看到@的標籤,就會按@里的動作來執行。

代碼如下:

def Goumai(Dongxi):
def Dongzuo():
print(詢價)
Dongxi()
print(購買成功!)
return Dongzuo

@Goumai
def Zhu():
print(豬)

@Goumai
def Daxiang():
print(大象)

@Goumai
def Changjinglu():
print(長頸鹿)

Zhu()
Daxiang()
Changjinglu()

再結合一下剛才的講價函數,先詢價,再講價,再購買,就可以寫成這樣:

def Goumai(Dongxi):
def Dongzuo():
print(詢價)
Dongxi()
print(購買成功!)
return Dongxi
return Dongzuo

def JangJia(x):
y =0.5
x = int(x) * y
return x

@Goumai
def Zhu():
Baojia = input(請輸入對方報價:)
Huanjia = JangJia(Baojia)
print(豬: + str(Huanjia))

@Goumai
def Daxiang():
Baojia = input(請輸入對方報價:)
Huanjia = JangJia(Baojia)
print(大象: + str(Huanjia))

@Goumai
def Changjinglu():
Baojia = input(請輸入對方報價:)
Huanjia = JangJia(Baojia)
print(長頸鹿: + str(Huanjia))

Zhu()
Daxiang()
Changjinglu()

執行後的結果:

詢價
請輸入對方報價:100
豬:50.0
購買成功!
詢價
請輸入對方報價:50
大象:25.0
購買成功!
詢價
請輸入對方報價:25
長頸鹿:12.5
購買成功!

最後一個常式的代碼其實還可以寫的更簡單,因為可以用多個裝飾器,希望有高人幫忙再指導一下。

總之,先從感性上理解「裝飾器」,後續才能進階學習啊!


裝飾器展開

@deco
def func():
pass

&>&>&> func()
# 相當於 deco(func)()

@deco
def func(f):
pass

&>&>&> func(9)
# 相當於 deco(func)(9)

@deco_with_arg(d)
def func(f):
pass
&>&>&> func(9)
# 相當於 deco_with_arg(d)(func)(9)


裝飾器定義

每一層 def 獲得一個 (xxx)

def deco_with_arg(第一個括弧內的參數): # 一般是裝飾器的參數
def deco(第二個括弧內的參數): # 一般是 func 本身
def wrapper(第三個括弧內的參數): # 一般是 func 調用時的參數
# 調用 func
return wrapper
return deco


就像數學上的複合函數


分頁阅读: 1 2