一個筆試經歷,是一道關于裝飾器的題目,那時候的感覺應該是這樣:
- " 美鋁,我們是不是認識? "
俗話說得好:一回生二回熟。但學習裝飾器時只是了解下,同時久不使用這方面的知識,好刀都要變鈍呀,況且自己拿的還是把破刀。被刺激的不要不要的 ~
首先,遇到一個云里霧里的問題:
1. 什么是裝飾器?
第一要明確知道的概念:裝飾器屬于Python的高階函數(聽說還是最常用到的高階函數)。兩個重點:
- 它是一個函數
- 它是一個感覺起來很高級的函數
所以,【劃重點】裝飾器是一個語法糖,它以一個函數為參數,并返回一個加強版的原函數(方法或者類)對象。當然,這個裝飾器要設計正確。
有點口語化,那就摘抄一段正式點的概念:
裝飾器本質上是一個Python函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外功能,裝飾器的返回值也是一個函數對象。它經常用于有切面需求的場景,比如:插入日志、性能測試、事務處理、緩存、權限校驗等場景。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量與函數功能本身無關的雷同代碼并繼續重用。
enmm~那裝飾器的作用就是
在不改動函數的情況下,給函數或者對象(其實函數也是一個對象)在運行時動態地額外添加其他的功能。
那么就進入第二內容,寫一點代碼出來溜溜,來理解下令人無趣的概念
2. 入門級別的裝飾器(面試題就是入門級的,我好想哭)
別嗶嗶,直接上代碼:
# 有個單純要去看電影的函數,就叫 movie() 吧,但你又想邊看電影邊吃爆米花。用裝飾器如何寫呢?
def extra(func):
"""給 movie()添加額外的功能"""
def wrapper():
print("我要先把爆米花和可樂買了!")
return func() # 得在加強版原函數里調用原函數。給自己穿漂亮的衣服可不要把自己本身忘記了。
return wrapper
@extra
def movie():
print("我要看電影。")
movie()
-----------------------------這是運行結果線----------------------------------------
我要先把爆米花和可樂買了!
我要看電影。
extra()
函數就是一個裝飾器。(兩點分析)
- 在 extra 函數中傳入的
func
參數是一個函數,即movie()
這個函數。符合高階函數的身份。在以前沒有加入語法糖時,@extra
等價于movie = extra( movie )
。這里涉及到一個知識點:
函數是一個對象,函數對象還可以賦值給變量。所以可以通過變量調用這個函數。
def cat():
print('This is a ugly cat!')
c = cat # 將函數對象賦值給變量c
c()
其實,Python 里所有數據——布爾值、整數、浮點數、字符串,甚至大型數據結構、函數以及程序——都是以對象(object)的形式存在的。
扯遠了,回歸正題——
-
return wrapper
中的wrapper
就是加強版的movie
函數。這個加強版的movie函數除了有看電影還有了要買爆米花和可樂的功能。符合返回一個加強版的原函數的概念。
但是——
如果這個被裝飾器裝飾的函數 moive
需要傳入一個電影名name
的參數。那么這個加強版函數wrapper
和其里面的函數func
也要做相應的更改。但 return wrapper
中的wrapper
不需要更改,因裝飾器返回的是一個對象。是不需要添加參數的。
def extra(func):
"""給 movie()添加額外的功能"""
def wrapper(name): # 一毛一樣的參數
print("我要先把爆米花和可樂買了!")
return func(name)
return wrapper
@extra
def movie(name):
print("我要看《{}》電影。".format(name))
movie('蜘蛛俠 英雄歸來')
----------------迷人的結果線------------------
我要先把爆米花和可樂買了!
我要看《蜘蛛俠 英雄歸來》電影。
雖然把裝飾器的里的加強版函數的參數弄的與原函數一毛一樣,但這裝飾器是一種封裝,如果萬一你想看一部,而其他人想看兩部呢?難道又給另個人寫個相似的裝飾器嗎?所以讓裝飾器同步更改參數的做法是不可取的。好辦法就是給裝飾器一個無敵的參數,一個可變參數*args
和關鍵字參數**kwargs
。
def extra(func):
"""給 movie()添加額外的功能"""
def wrapper(*args, **kwagrs): # 無敵的參數了吧
print("我要先把爆米花和可樂買了!")
return func(*args, **kwagrs)
return wrapper
@extra
def movie1(name):
print("我要看《{}》電影。".format(name))
@extra
def movie2(name1, name2):
print("我要看兩部電影《{0}》和《{1}》".format(name1, name2))
movie1('蜘蛛俠 英雄歸來')
print('\n')
movie2('蜘蛛俠 英雄歸來','猩球崛起')
嘻嘻,好累呀!那么入門的就結束了,進入進階版本的裝飾器了。發車了~~~吱吱吱~~~
還是下一篇再寫吧。