筆記1:python 裝飾器@
python 裝飾器@:
1 函式修飾符@的作用 首先介紹函式裝飾器。通過裝飾器函式,在不修改原函式的前提下,對函式的功能進行合理擴充。 例如:有一個函式func(a, b),它的功能是求a,b的差值,我現在有一個需求,就是想對函式功能再裝飾下,求完差值後再取絕對值,但是不能在func函式內部實現,這時候就需要裝飾器函數了,比如func = decorate(func)函式,將func函式作為引數傳遞給decorate函式,由decorate來豐富func函式,豐富完成後再返回給func,此時func的功能就豐富
用@裝飾器的格式來寫的目的就是為了書寫簡單方便
2 函式修飾符@的工作原理
假設用 funA() 函式裝飾器去裝飾 funB() 函式,如下所示:
#funA 作為裝飾器函式
def funA(fn):
#...
fn() # 執行傳入的fn引數
#...
return '...'
通過比對以上 2 段程式不難發現,使用函式裝飾器 A() 去裝飾另一個函式 B(),其底層執行了如下 2 步操作:
-
將 B 作為引數傳給 A() 函式;
-
將 A() 函式執行完成的返回值反饋回 B。
示例如下:
#funA 作為裝飾器函式
def funA(fn):
print("C語言中文網")
fn() # 執行傳入的fn引數
print("http://c.biancheng.net")
return "裝飾器函式的返回值"
得到的是:
C語言中文網
學習 Python
http://c.biancheng.net在此基礎上,如果在程式末尾新增如下語句:
print(funB)
其輸出結果為:
裝飾器函式的返回值
顯然,被“@函式”修飾的函式不再是原來的函式,而是被替換成一個新的東西(取決於裝飾器的返回值),即如果裝飾器函式的返回值為普通變數,那麼被修飾的函式名就變成了變數名;同樣,如果裝飾器返回的是一個函式的名稱,那麼被修飾的函式名依然表示一個函式。
實際上,所謂函式裝飾器,就是通過裝飾器函式,在不修改原函式的前提下,來對函式的功能進行合理的擴充。
帶引數的函式裝飾器
在分析 funA() 函式裝飾器和 funB() 函式的關係時,細心的讀者可能會發現一個問題,即當 funB() 函式無引數時,可以直接將 funB 作為 funA() 的引數傳入。但是,如果被修飾的函式本身帶有引數,那應該如何傳值呢?
比較簡單的解決方法就是在函式裝飾器中巢狀一個函式,該函式帶有的引數個數和被裝飾器修飾的函式相同。例如:
def funA(fn):
# 定義一個巢狀函式
def say(arc):
print("Python教程:",arc)
return say
程式執行結果為:
Python教程: http://c.biancheng.net/python
其實和下面的函式相同
def funA(fn):
# 定義一個巢狀函式
def say(arc):
print("Python教程:",arc)
return say
def funB(arc):
print("funB():", a)
funB = funA(funB)
funB("http://c.biancheng.net/python")
顯然,通過 funB() 函式被裝飾器 funA() 修飾,funB 就被賦值為 say。這意味著,雖然我們在程式顯式呼叫的是 funB() 函式,但其實執行的是裝飾器巢狀的 say() 函式。
但還有一個問題需要解決,即如果當前程式中,有多個(≥ 2)函式被同一個裝飾器函式修飾,這些函式帶有的引數個數並不相等,怎麼辦呢?
最簡單的解決方式是用 args 和 **kwargs 作為裝飾器內部巢狀函式的引數,args 和 **kwargs 表示接受任意數量和型別的引數。舉個例子:
def funA(fn):
# 定義一個巢狀函式
def say(*args,**kwargs):
fn(*args,**kwargs)
return say
返回:
C語言中文網: http://c.biancheng.net Python教程: http://c.biancheng.net/python
函式裝飾器可以巢狀
上面示例中,都是使用一個裝飾器的情況,但實際上,Python 也支援多個裝飾器,比如:
上面程式的執行順序是裡到外,所以它等效於下面這行程式碼:
fun = funA( funB ( funC (fun) ) )
帶引數:
def funA(c): # 多一層包裝 c=2
def bbb(fn):
def aaa(n): #n=1
n = n + 3 + c
print("a:",n)
fn(n)
# print("Python教程:",n)
return aaa
return bbb
python內建裝飾符函式:
-
@staticmethod:
使用場景:當某個方法不需要用到物件中的任何資源時,將這個方法改為一個靜態方法
也就是沒有用到任何外面的資源,此時加上@staticmethod,方法變為靜態方法,同事不需要常常要加的self。
比如:
·
-
classmethod :
使用場景:修飾符對應的函式不需要例項化,不需要 self 引數,但第一個引數需要是表示自身類的 cls 引數,可以來呼叫類的屬性,類的方法,例項化物件等。
# 初始類:
class Data_test(object):
day=0
month=0
year=0
def __init__(self,year=0,month=0,day=0):
self.day=day
self.month=month
self.year=year
def out_date(self):
print "year :"
print self.year
print "month :"
print self.month
print "day :"
print self.day
# 新增功能:
class Str2IntParam(Data_test):
可以擴充類的方法。易於維護。
@property
-
1.只讀屬性
-
2.可以做方法來修改屬性,也就是變成屬性了,而不是方法,最直觀沒有括號
-
3.@方法名.setter,可以做到修改不用引數,或者引數的預處理。
-
4.因為property是類,所以可以用其內部的 age = property(get_age, set_age,del_age)來修改,順序不能改變順序。 (property函式內建方法:屬性=property(get,set,del))
其功能1是可定義只讀屬性,也就是真正意義上的私有屬性(屬性前雙下劃線的私有屬性也是可以訪問的)。
class Person(object):
def __init__(self, name, age=18):
self.name = name
self.__age = 18
@property真正強大的是可以限制屬性的定義。往往我們定義類,希望其中的屬性必須符合實際,但因為在__init__裡定義的屬性可以隨意的修改,導致很難實現。如我想實現Person類,規定每個人(即建立的例項)的年齡必須大於18歲,正常實現的話,則必須將屬性age設為只讀屬性,然後通過方法來賦值,程式碼如下:
class Person(object):
def __init__(self, name, age):
self.name = name
self.__age = 18
@property方法
class Person(object):
def __init__(self, name, age):
self.name = name
self.__age = 18
結果和上圖一致。兩段程式碼變化的內容:將set_age修改為age,並且在上方加入裝飾器@age.setter。這就是@property定義可訪問屬性的語法,即仍舊以屬性名為方法名,並在方法名上增加@屬性.setter就行了。
@property是個描述符(decorator),實際上他本身是類,
class Person(object):
def __init__(self, name, age):
self.name = name
self.__age = 18
def get_age(self): #恢復用方法名來獲取以及定義
return self.__age
def set_age(self, age):
if age < 18:
print('年齡必須大於18歲')
return
self.__age = age
return self.__age
age = property(get_age, set_age) #增加property類
上述程式碼的執行結果和前面一致,將@property裝飾的屬性方法再次修改回定義方法名,然後再類的最下方,定義:屬性=property(get,set,del),這個格式是固定的,是由property原始碼決定的。
class C(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
#如果 c 是 C 的例項化, c.x 將觸發 getter,c.x = value 將觸發 setter , del c.x 觸發 deleter。