[學習總結] python語言學習總結 (三)
函式閉包
定義
延伸了作用域的函式(能訪問定義體之外定義的非全域性變數
作用
- 共享變數的時候避免使用了不安全的全域性變數
- 允許將函式與某些資料關聯起來,類似於簡化版面向物件程式設計
- 相同程式碼每次生成的閉包,其延伸的作用域都彼此獨立(計數器,登錄檔)
- 函式的一部分行為在編寫時無法預知,需要動態實現,同時又想保持介面一致性
- 較低的記憶體開銷:類的生命週期遠大於閉包
- 實現裝飾器
程式碼
print("Class方式:") class Averager: def __init__(self): self.series = [] def __call__(self,new_val): self.series.append(new_val) return sum(self.series)/len(self.series) avg1 = Averager() print(avg1(3)) print(avg1(2)) print("閉包方式:") def Avg_1(): count = 0 total = 0 def Avg_2(val): nonlocal total,count total+=val count+=1 return total/count return Avg_2 avg = Avg_1() print(avg(9)) print(avg(10)) ''' 輸出 Class方式: 3.0 2.5 閉包方式: 9.0 9.5 '''
理解
功能說明
這段程式碼需要解決的是,定義一個average函式,我們每次傳進去一個值,都可以得到這個值和之前傳入值的平均值。比如第一次傳入1,均值就是1,第二次傳入2,均值就是1.5,以此類推。
類方式
第一種方式是通過構建一個類,這也是我們通常容易想到的方式,利用在類裡面建立一個list,每次呼叫都將新值append進去,這是一種解決方法。
函式閉包方式
這裡跟前面提到的作用域是有關的,函式閉包延伸了作用域,我們可以看到,我們的外層函式只是為了儲存兩個變數而已,沒有其他作用了。其實這時候應該有個疑惑,在呼叫Avg_1()之後,由於count和total是在Loval層,函式呼叫結束了應該銷燬了才是,但實際上並沒有,我們在Avg_2中通過nolocal關鍵字延長了兩個變數的生命週期,使得我們在每次呼叫的時候都能訪問都兩個變數,達到效果。
裝飾器
為什麼會出現裝飾器這個東西
- 名稱管理
- 顯示呼叫
- 就近原則
- 充分複用
@語法糖
'@' 用做函式的修飾符,可以在模組或者類的定義層內對函式進行修飾,出現在函式定義的前一行,不允許和函式定義在同一行。
什麼是裝飾器
- 裝飾器是一個可呼叫的物件,以某種方式增強函式的功能
- 裝飾器是一個語法糖,在原始碼中標記函式(此原始碼指編譯後的原始碼)
- 直譯器解析原始碼的時候將被裝飾的函式作為第一個位置引數傳給裝飾器
- 裝飾器可能會直接處理被裝飾函式,然後返回它(一般僅修改屬性,不修改程式碼)
- 裝飾器也可能用一個新的函式或可呼叫物件替換被裝飾函式(但核心功能一般不變)
- 裝飾器僅僅看著像閉包,其實功能的定位與閉包有重合也有很大區別
- 裝飾器模式的本質是超程式設計:在執行時改變程式行為
- 裝飾器的一個不可忽視的特性:在模組載入時立即執行
- 裝飾器是可以堆疊的,自底向上逐個裝飾
- 裝飾器是可以帶引數的,但此時至少要寫兩個裝飾器
- 裝飾器的更加Pythonic的實現方式其實是在類中實現 call() 方法
程式碼
def check_param(**kw):
flag = kw.get('flag')
def check_p(func):
def decorate(*args):
if flag:
if not all([isinstance(arg,int) for arg in args]):
raise TypeError("{} only accepts integers as argument".format(func.__name__))
return func(*args)
return decorate
return check_p
@check_param(flag=True)
def my_sum(a,b):
return a+b
if __name__=='__main__':
print(my_sum(1,2))
print(my_sum(1,2.0))
'''
輸出
3
Traceback (most recent call last):
File "/home/xueaoru/文件/pydemo/blog.py", line 18, in <module>
print(my_sum(1,2.0))
File "/home/xueaoru/文件/pydemo/blog.py", line 7, in decorate
raise TypeError("{} only accepts integers as argument".format(func.__name__))
TypeError: my_sum only accepts integers as argument
'''
解釋
上面的程式碼是為了完成一個檢查my_sum函式的引數是否是整數的功能的裝飾器,同時增加了開關功能,我們將flag設定為true就是開啟檢查功能,如果不是整數,則直接報錯。
理解
裝飾器是對函式在不改變原有函式內部實現的情況下,對原有函式進行功能增強。而@語法糖是對原函式進行修飾的修飾符,當呼叫@語法糖進行修飾的時候,即使該函式並不呼叫,也會執行修飾語句,因為觸發了執行裝飾器。這時候該函式作為引數傳給修飾函式的外部函式,然後該函式作為引用賦值給內部函式的函式名,也就是說我們真正的操作是在內部函式中進行的,因此可以在內部函式中對原函式進行功能增強。其中,有引數的時候呢,內部函式也需要通過*和**拆包得到引數,原函式有返回值的時候呢,我們在呼叫完原函式的時候也應該給出返回值。而裝飾器本身帶引數的時候呢,可以在裝飾函式外再加一層接收引數的包裝得到引數。
OOP In Python
概念
一切都是物件
從語言設計層面理解Python中的資料型別:一切都是物件,都是從Object繼承過來的。Object由三部分組成:identity、type、value。
identity
理解
當Objects建立之後呢,identity也不會改變,直到被銷燬。我覺得跟c++裡的地址差不多吧,當然也不能完全這麼理解,也有不同的方。
要點
- 變數儲存的是建立的Object的identity
- 創建出來的不同Object有不同的identity
- 變數的id變了不是因為Object的identity變了,而是對應的Object變了
- 對於不可變物件而言,計算結果如果已經存在,可以直接返回相同的identity
type
要點
- 當Object建立後,其type不會改變
- type決定了一個Object可以支援那些運算,可能的值在哪些範圍
value
要點
- 有些Object的value可以改變:可變物件
- 有些Object的value不可以改變:不可變物件
每一個class在定義的時候如果沒有繼承的話,那麼他繼承的就是Object這個超級class,而每一個自定義的class在python中都是一個type object。
@classmethod
理解
可以把類中的某個方法變成這個類的方法而不依賴於物件,也就是說,在物件沒有建立的時候,我們也可以呼叫這個類的方法執行一定的操作。這就有點像C++中的靜態成員函式。
程式碼
class Student:
teacher_name = "Omg"
def __init__(self,name):
self._name = name
@classmethod
def Teacher(cls):
print(cls.teacher_name)
def Me(self):
print(self._name)
if __name__== '__main__':
aa = Student("xue")
aa.Teacher()
aa.Me()
Student.Teacher()
'''
Omg
xue
Omg
'''
解釋
這段程式碼是通過呼叫Teacher方法得到老師的名字,可以看出,我們就算不通過物件直接呼叫,也可以輸出老師的名字。classmethod就起這個作用。
@property
理解
本質上這是一個裝飾器,可以省去寫get、set函式的對外繫結。
property
函式原型為
property(fget=None, fset=None, fdel=None, doc=None)
程式碼
@property與下面的程式碼效果是一樣的
class Teacher:
def __init__(self,name,subject):
self._name = name
self._subject = subject
def setName(self,name):
self._name = name
def getName(self):
return self._name
def setSubject(self,subject):
self._subject = subject
def getSubject(self):
return self._subject
def show(self):
print("name is:{} and subject is:{}".format(self._name,self._subject))
name = property(getName,setName)
subject = property(getSubject,setSubject)
if __name__ == '__main__':
t = Teacher("A","math")
t.show()
t.name = "B"
t.subject = "English"
t.show()
'''
name is:A and subject is:math
name is:B and subject is:English
'''
使用@property之後的更加優美的版本
class Teacher:
def __init__(self,name,subject):
self._name = name
self._subject = subject
@property
def name(self):
return self._name
@name.setter
def name(self,name):
self._name = name
@property
def subject(self):
return self._subject
@subject.setter
def subject(self,subject):
self._subject = subject
def show(self):
print("name is:{} and subject is:{}".format(self._name,self._subject))
if __name__ == '__main__':
t = Teacher("A","math")
t.show()
t.name = "B"
t.subject = "English"
t.show()
注意
這裡@property必須在setter前面初始化,這個應該很容易理解,因為直譯器讀程式的時候是從上往下讀的。
Special method
要點
- 之所有要實現special method,是為了讓自定義的class與python中的內建函式無縫銜接
- python中有大量的內建函式,而這些函式中絕大部分是special method
- python中的special method :https://rszalski.github.io/magicmethods/
程式碼
class A:
def __init__(self):
pass
def __str__(self):
return "I am str"
def __len__(self):
return 15
def __bool__(self):
return False
if __name__ == '__main__':
a = A()
print(a,len(a),a==True)