1. 程式人生 > 其它 >筆記1:python 裝飾器@

筆記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 '...'
@funA
def funB():
   #...
其等價於:
def funA(fn):
   #...
   fn() # 執行傳入的fn引數
   #...
   return '...'
def funB():
   #...
funB = funA(funB)

通過比對以上 2 段程式不難發現,使用函式裝飾器 A() 去裝飾另一個函式 B(),其底層執行了如下 2 步操作:

  • 將 B 作為引數傳給 A() 函式;

  • 將 A() 函式執行完成的返回值反饋回 B。

    示例如下:

    #funA 作為裝飾器函式
    def funA(fn):
       print("C語言中文網")
       fn() # 執行傳入的fn引數
       print("http://c.biancheng.net")
       return "裝飾器函式的返回值"

    @funA
    def funB():
       print("學習 Python")

    得到的是:

    C語言中文網
    學習 Python
    http://c.biancheng.net

    在此基礎上,如果在程式末尾新增如下語句:

    print(funB)

    其輸出結果為:

    裝飾器函式的返回值

    顯然,被“@函式”修飾的函式不再是原來的函式,而是被替換成一個新的東西(取決於裝飾器的返回值),即如果裝飾器函式的返回值為普通變數,那麼被修飾的函式名就變成了變數名;同樣,如果裝飾器返回的是一個函式的名稱,那麼被修飾的函式名依然表示一個函式。

    實際上,所謂函式裝飾器,就是通過裝飾器函式,在不修改原函式的前提下,來對函式的功能進行合理的擴充。

帶引數的函式裝飾器

在分析 funA() 函式裝飾器和 funB() 函式的關係時,細心的讀者可能會發現一個問題,即當 funB() 函式無引數時,可以直接將 funB 作為 funA() 的引數傳入。但是,如果被修飾的函式本身帶有引數,那應該如何傳值呢?

比較簡單的解決方法就是在函式裝飾器中巢狀一個函式,該函式帶有的引數個數和被裝飾器修飾的函式相同。例如:

def funA(fn):
   # 定義一個巢狀函式
   def say(arc):
       print("Python教程:",arc)
   return say

@funA
def funB(arc):
   print("funB():", a)
funB("http://c.biancheng.net/python")

程式執行結果為:

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

@funA
def funB(arc):
   print("C語言中文網:",arc)

@funA
def other_funB(name,arc):
   print(name,arc)
funB("http://c.biancheng.net")
other_funB("Python教程:","http://c.biancheng.net/python")

返回:

C語言中文網: http://c.biancheng.net Python教程: http://c.biancheng.net/python

函式裝飾器可以巢狀

上面示例中,都是使用一個裝飾器的情況,但實際上,Python 也支援多個裝飾器,比如:

@funA
@funB
@funC
def fun():    #...

上面程式的執行順序是裡到外,所以它等效於下面這行程式碼:

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

@funA(2)
def funB(a):
b = a + 100
print("funB():", b)
funB(1)

輸出:
a: 6
funB(): 106

python內建裝飾符函式:

  • @staticmethod:

使用場景:當某個方法不需要用到物件中的任何資源時,將這個方法改為一個靜態方法

 

也就是沒有用到任何外面的資源,此時加上@staticmethod,方法變為靜態方法,同事不需要常常要加的self。

比如:

@staticmethod
def get_name():
   return 'what?'

·

  • 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):
   @classmethod
   def get_date(cls, string_date):
       #這裡第一個引數是cls, 表示呼叫當前的類名
       year,month,day=map(int,string_date.split('-'))
       date1=cls(year,month,day)
       #返回的是一個初始化後的類
       return date1

# 使用:
r = Str2IntParam.get_date("2016-8-1")
r.out_date()

# 輸出:
year :
2016
month :
8
day :
1

可以擴充類的方法。易於維護。

 

@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
   def age(self):
       return self.__age
       
xm = Person('xiaoming')  #定義一個人名小明
print(xm.age) #結果為18
xm.age = -4 #報錯無法給年齡賦值
print(xm.age)
@property真正強大的是可以限制屬性的定義。往往我們定義類,希望其中的屬性必須符合實際,但因為在__init__裡定義的屬性可以隨意的修改,導致很難實現。如我想實現Person類,規定每個人(即建立的例項)的年齡必須大於18歲,正常實現的話,則必須將屬性age設為只讀屬性,然後通過方法來賦值,程式碼如下:
class Person(object):
   def __init__(self, name, age):
       self.name = name
       self.__age = 18

   @property
   def age(self):
       return self.__age

   def set_age(self, age): #定義函式來給self.__age賦值
       if age < 18:
           print('年齡必須大於18歲')
           return
       self.__age = age
       return self.__age
   
xm = Person('xiaoming', 20)
   
print(xm.age)
print('----------')
xm.set_age(10)
print(xm.age)
print('----------')
xm.set_age(20)
print(xm.age)


返回:
18
----------
年齡必須大於18歲
18
----------
20

@property方法

class Person(object):
   def __init__(self, name, age):
       self.name = name
       self.__age = 18

   @property
   def age(self):
       return self.__age

   @age.setter
   def age(self, age):
       if age < 18:
           print('年齡必須大於18歲')
           return
       self.__age = age
       return self.__age

xm = Person('xiaoming', 20)
print(xm.age)
print('----------')
xm.age = 10
print(xm.age)
print('----------')
xm.age = 20
print(xm.age)

結果和上圖一致。兩段程式碼變化的內容:將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。