修飾符(有的也翻譯成裝飾符、裝飾器,英文叫decorator)是Python中一個很有意思的feature,它可以向已經寫好的代碼中添加功能。這其實也叫元程式設計,因為程式的一部分在編譯的時候嘗試修改程式的另一部分。
高階函數
在學習Python的修飾符前,我們要知道幾個a概念,首先是Python中的所有東西都是對象,所有我們定義的變量、類甚至與于函數,都是對象。函數是對象是個什麼概念呢?就是函數之間可以互相指派:
def first(msg):
pritn(msg)
first("Hello")
>>> Hello
second = first
second("Hello")
>>> Hello
上面的
first
和
second
都是指向同一個函數對象的。這種直接函數名之間的直接指派,在C++中是不支援的。
當然,函數也可以當作參數傳遞給其他函數,最常見的就是
map
、
filter
和
reduce
這三個函數了。
def func(x):
return x*2
list(map(func, [1,2,3]))
>>>[2,4,6]
list(map(lambda x: x * 2, range(1,4)))
>>>[2,4,6]
下面我們自定義一個函數,其參數也是函數:
def add(x):
return x + 1
def test(func, x):
return add(x)
執行
test(add, 2)
>>> 3
在Python中,把其他函數當做參數的函數,叫做高階函數。
嵌套函數
就是nested function,在函數中定義函數,這個我們之前寫過,直接放上之前的連結:Python嵌套函數 閉包 詳解
Python修飾符
下面回歸正題,來講Python修飾符。在Python中,使用
@
來表示修飾符,但這裡我們先看下不使用
@
來完成一個修飾函數。
def make_pretty(func):
def inner():
print("I got decorated")
func()
return inner
def ordinary():
print("I am ordinary")
pretty = make_pretty(ordinary)
pretty()
>>> I got decorated
I am ordinary
在這個例子中,
make_pretty()
就是一個修飾器,在
pretty = make_pretty(ordinary)
語句中,
ordinary
函數被修飾,傳回的函數指派給了
pretty
。是以修飾器就像對函數又進行了一層的包裝,下面來看使用修飾符@來對函數進行修飾,隻需在定義函數的上一行加上
@func
即可。
def make_pretty(func):
def inner():
print("I got decorated")
func()
return inner
@make_pretty
def ordinary():
print("I am ordinary")
ordinary()
>>> I got decorated
I am ordinary
修飾符必須出現在函數定義前一行,不允許和函數定義在同一行。隻可以在子產品或類定義層内對函數進行修飾,不允許修飾一個類。一個修飾符就是一個函數,它将被修飾的函數做為參數,并傳回修飾後的同名函數或其它可調用的東西。
本質上講,修飾符@類似于回調函數,把其它的函數作為自己的入口參數,在目的函數執行前,執行一些自己的操作,然後傳回目的函數。當Python解釋器讀取到修飾符時,會調用修飾符的函數,來看下面的例子(這個例子隻是為了解釋Python讀取到修飾符時會直接調用,這個修飾函數并沒有傳回目的函數)
# test.py
def test(func):
print('This is test function step1')
func()
print('This is test function step2')
@test
def func():
print('This is func...')
代碼列印如下:
This is test function step1
This is func...
This is test function step2
這段代碼中隻是定義了兩個函數,并沒有調用它們,但仍然會有結果列印出來。
當被修飾的函數中含有參數時,應該怎麼來寫呢?
我們定義一個除法函數,參數a和b,傳回a/b的結果。
def divide(a,b)
return a / b
我們知道除法的除數不能是0,是以當我們令b=0時,就會報錯。
>>> divide(2,5)
0.4
>>> divide(2,0)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
是以我們需要增加判斷機制,當除數為0時,需要告訴使用者不能執行。但
divide()
函數中我們就隻想完成除法的功能,判斷機制就可以通過修飾符來完成。
def smart_divide(func):
def inner(a, b):
print("I am going to divide", a, "and", b)
if b == 0:
print("Whoops! cannot divide")
return
return func(a, b)
return inner
@smart_divide
def divide(a, b):
print(a/b)
可以看到,在修飾符函數内容的嵌套函數
inner()
中,嵌套函數的參數依然的(a,b),傳回值也同樣是
func(a,b)
,這就是包含參數的函數被修飾時的寫法。
divide(2,5)
>>> I am going to divide 2 and 5
0.4
divide(2,0)
>>> I am going to divide 2 and 0
Whoops! cannot divide
一般的,我們定義函數的參數可以設為
*args, **kwargs
,其中*args表示位置參數,位置很重要,是以清單形式呈現;**kwargs:關鍵字參數,位置不重要。字典形式呈現。
def works_for_all(func):
def inner(*args, **kwargs):
print("I can decorate any function")
return func(*args, **kwargs)
return inner
@works_for_all
def ff(*args, **kwargs):
print(args)
print(kwargs)
調用示例如下:
ff(1,2,a=3,b=4)
>>> I can decorate any function
(1, 2)
{'a': 3, 'b': 4}
一個函數的多個修飾符
Python中,一個函數可以有多個修飾符。
def star(func):
def inner(*args, **kwargs):
print("*" * 30)
func(*args, **kwargs)
print("*" * 30)
return inner
def percent(func):
def inner(*args, **kwargs):
print("%" * 30)
func(*args, **kwargs)
print("%" * 30)
return inner
@star
@percent
def printer(msg):
print(msg)
printer("Hello")
列印結果如下:
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
其實上面的:
@star
@percent
def printer(msg):
print(msg)
就等效于下面的寫法:
def printer(msg):
print(msg)
printer = star(percent(printer))
是以執行的步驟為:
- 将
函數傳入到printer
函數中,percent
函數直接傳回percent
函數給inner
函數star
- 在
函數中,傳入的參數star
是func
的内嵌函數percent
,此時在inner
的star
中,先執行inner
,再執行print("*" * 30)
,也就是會進入到func()
中執行percent->inner
- 此時将執行
,再執行print("%" * 30)
,此時的func()
是func
printer
- 再執行
中的percent->inner
,傳回後又到了print("%" * 30)
中,執行star->inner
print("*" * 30)
寫的有點繞,看下面的圖會更加清晰,從左到右執行。
總結
下面來總結一下修飾符的要點:
- 修飾符必須出現在函數定義前一行,不允許和函數定義在同一行;
- 修飾符的内嵌函數的參數跟被修飾的函數參數相同,内嵌函數的傳回值跟被修飾函數的定義表達式一樣
- 一個函數可以用多個修飾符,執行順序是參考上圖。
微信公衆号: