本文接[上篇]()。python
第29條:用純屬性取代get和set方法算法
(1)編寫新類時,應該用簡單的public屬性來定義其接口,而不要手工實現set和get方法json
(2)若是訪問對象的某個屬性,須要表現出特殊的行爲,那就用@property來定義這種行爲網絡
好比下面的示例:成績必須在0-100範圍內數據結構
class Homework:
def __init__(self):
self.__grade = 0多線程
@property
def grade(self):
return self.__grade併發
@grade.setter
def grade(self,value):
if not (0<=value<=100):
raise ValueError('Grade must be between 0 and 100')
self.__grade = value函數
(3)@property方法應該遵循最小驚訝原則,而不該該產生奇怪的反作用工具
(4)@property方法須要執行得迅速一些,緩慢或複雜的工做,應該放在普通的方法裏面性能
(5)@property的最大缺點在於和屬性相關的方法,只能在子類裏面共享,而與之無關的其餘類都沒法複用同一份實現代碼
第30條:考慮用@property來代替屬性重構
做者的意思是:當咱們須要遷移屬性時(也就是對屬性的需求發生變化的時候),咱們只須要給本類添加新的功能,原來的那些調用代碼都不須要改變,它在持續完善接口的過程當中是一種重要的緩衝方案
(1)@property能夠爲現有的實例屬性添加新的功能
(2)能夠用@properpy來逐步完善數據模型
(3)若是@property用的太過頻繁,那就應該考慮完全重構該類並修改相關的調用代碼
第31條:用描述符來改寫須要複用的@property方法
首先對描述符進行說明,先看下面的例子:
class Grade:
def __init(self):
self.__value = 0
def __get__(self, instance, instance_type):
return self.__value
def __set__(self, instance, value):
if not (0 <= value <= 100):
raise ValueError('Grade must be between 0 and 100')
self.__value = value
class Exam:
math_grade = Grade()
chinese_grade = Grade()
science_grade = Grade()
if name == "__main__":
exam = Exam()
exam.math_grade = 99
exam1 = Exam()
exam1.math_grade = 75
print('exam.math_grade:',exam.math_grade, 'is wrong')
print('exam1.math_grade:',exam1.math_grade, 'is right')
輸出:
會發如今兩個Exam實例上面分別操做math_grade時,致使了錯誤的結果,出現這種狀況的緣由是由於 該math_grade屬性爲Exam類的實例 ,爲了解決這個問題,看下面的代碼
class Grade:
def __init__(self):
self.__value = {}
def __get__(self, instance, instance_type):
if instance is None:
return self
return self.__value.get(instance,0)
def __set__(self, instance, value):
if not (0 <= value <= 100):
raise ValueError('Grade must be between 0 and 100')
self.__value[instance] = value
class Exam:
math_grade = Grade()
chinese_grade = Grade()
science_grade = Grade()
if name == "__main__":
exam = Exam()
exam.math_grade = 99
exam1 = Exam()
exam1.math_grade = 75
print('exam.math_grade:',exam.math_grade, 'is wrong')
print('exam1.math_grade:',exam1.math_grade, 'is right')
輸出:
上面這種實現方式很簡單,並且可以正常運做,但它仍然有個問題,那就是會泄露內存,在程序的生命期內,對於傳給__set__方法的每一個Exam實例來講,__values字典都會保存指向該實例的一份引用,者就致使實例的引用計數沒法降爲0,從而使垃圾收集器沒法將其收回。使用python的內置weakref模塊,可解決上述問題。
class Grade:
def __init(self):
self.__value = weakref.WeakKeyDictionary()
(1)若是想複用@property方法及其驗證機制,那麼能夠本身定義描述符
(2)WeakKeyDictionary能夠保證描述符類不會泄露內存
(3)經過描述符協議來實現屬性的獲取和設置操做時,不要糾結於__getattribute__的方法具體運做細節
第32條:用__getattr__、__getattribute__和__setattr__實現按需生成的屬性
若是某個類定義了__getattr__,同時系統在該類對象的實例字典中又找不到待查詢的屬性,那麼就會調用這個方法
惰性訪問的概念:初次執行__getattr__的時候進行一些操做,把相關的屬性加載進來,之後再訪問該屬性時,只需從現有的結果中獲取便可
程序每次訪問對象的屬性時,Python系統都會調用__getattribute__,即便屬性字典裏面已經有了該屬性,也以讓會觸發__getattribute__方法
(1)經過__getattr__和__setattr__,咱們能夠用惰性的方式來加載並保存對象的屬性
(2)要理解__getattr__和__getattribute__的區別:前者只會在待訪問的屬性缺失時觸發,,然後者則會在每次訪問屬性時觸發
(3)若是要在__getattribute__和__setattr__方法中訪問實例屬性,那麼應該直接經過super()來作,以免無限遞歸
第33條:用元類來驗證子類
元類最簡單的一種用途,就是驗證某個類定義的是否正確,構建複雜的類體系時,咱們可能須要確保類的風格協調一致,確保某些方法獲得了覆寫,或是確保類屬性之間具有某些嚴格的關係。
下例判斷類屬性中是否含有name屬性:
class Meta(type):
def __new__(meta,name,bases,class_dict):
print('class_dict:',class_dict)
if not class_dict.get('name',None): #判斷類屬性中是否含有name屬性
raise AttributeError('must has name attribute')
return type.__new__(meta,name,bases,class_dict)
class A(metaclass=Meta):
def __init__(self):
self.chinese_grade = 90
self.math_grade = 99
if name == '__main__':
a = A()
輸出:
(1)經過元類,咱們能夠在生成子類對象以前,先驗證子類的定義是否合乎規範
(2)python系統把子類的整個class語句體處理完畢以後,就會調用其元類的__new__方法
第34條:用元類來註冊子類
元類還有一個用途就是在程序中自動註冊類型,對於須要反向查找(reverse lookup)的場合,這種註冊操做頗有用
看下面的例子:對對象進行序列化和反序列化
import json
register = {}
class Meta(type):
def __new__(meta,name,bases,attr_dic):
cls = type.__new__(meta,name,bases,attr_dic)
print('create class in Meta:', cls)
register[cls.__name__] = cls
return cls
class Serializable(metaclass=Meta):
def __init__(self,*args):
self.args = args
def serialize(self):
return json.dumps({'class':self.__class__.__name__, 'args':self.args})
def deserilize(self,json_data):
json_dict = json.loads(json_data)
classname = json_dict['class']
args = json_dict['args']
return registerclassname
class Point2D(Serializable):
def __init__(self,x,y):
super().__init__(x,y)
self.x = x
self.y = y
def add(self):
return self.x + self.y
if name == "__main__":
p = Point2D(2,5)
data = p.serialize()
print('serialize_data:',data)
new_point2d = p.deserilize(data)
print('new_point2d:',new_point2d)
print(new_point2d.add())
輸出:
(1)經過元類來實現類的註冊,能夠確保全部子類就都不會泄露,從而避免後續的錯誤
第35條:用元類來註解類的屬性
(1)藉助元類,咱們能夠在某個類徹底定義好以前,率先修改該類的屬性
(2)描述符與元類可以有效的組合起來,以便對某種行爲作出修飾,或在程序運行時探查相關信息
(3)若是把元類與描述符相結合,那就能夠在不使用weakref模塊的前提下避免內存泄漏
併發和並行的關鍵區別在於能不能提速,如果並行,則總任務的執行時間會減半,如果併發,那麼即便能夠看似平行的方式分別執行多條路徑,依然不會使總任務的執行速度獲得提高,用Python語言編寫併發程序,是比較容易的,經過系統調用、子進程和C語言擴展等機制,也能夠用Python平行地處理一些事務,可是,要想使併發式的python代碼以真正平行的方式來運行,卻至關困難。
第36條:用subprocess模塊來管理子進程
在多年的發展過程當中,Python演化出了多種運行子進程的方式,其中包括popen、popen2和os.exec*等,然而,對於至今的Python來講,最好且最簡單的子進程管理模塊,應該是內置的subprocess模塊
第37條:能夠用線程來執行阻塞式I/O,但不要用它作平行計算
(1)由於受全局解釋鎖(GIL)的限制,因此多條Python線程不能在多個CPU核心上面平行地執行字節碼
(2)儘管受制於GIL,可是python的多線程功能依然頗有用,它能夠輕鬆地模擬出同一時刻執行多項任務的效果
(3)經過python線程,咱們能夠平行地執行多個系統調用,這使得程序可以在執行阻塞式I/O操做的同時,執行一些運算操做
第38條:在線程中使用Lock來防止數據競爭
class LockingCounter:
def __init__(self):
self.lock = threading.Lock()
self.count = 0
def increment(self, offset):
with self.lock:
self.count += offset
第39條:用Queue來協調各線程之間的工做
做者舉了一個照片處理系統的例子:
需求:該系統從數碼相機裏面持續獲取照片、調整其尺寸,並將其添加到網絡相冊中。
實現:使用三階段的管線實現,須要4個自定義的deque消息隊列,第一階段獲取新照片,第二階段把下載好的照片傳給縮放函數,第三階段把縮放後的照片交給上傳函數
問題:該程序雖然能夠正常運行,可是每一個階段的工做函數都會有差異,這使得前一階段可能會拖慢後一階段的進度,從而令整條管線遲滯,後一階段會在其循環語句中,反覆查詢輸入隊列,以求獲取新的任務,而任務卻遲遲未到達,這將令後一階段陷入飢餓,會白白浪費CPU時間,效率特低
內置的queue模塊的Queue類能夠解決上述問題,由於其get方法會持續阻塞,直到有新的數據加入
import threading
from queue import Queue
class ClosableQueue(Queue):
SENTINEL = object()
def close(self):
self.put(SENTINEL)
def __iter__(self):
while True:
item = self.get()
try:
if item is self.SENTINEL:
return
yield item
finally:
self.task_done()
class StoppabelWoker(threading.Thread):
def __init__(self,func,in_queue,out_queue):
self.func = func
self.in_queue = in_queue
self.out_queue = out_queue
def run(self):
for item in self.in_queue:
result = self.func(item)
self.out_queue.put(result)
(1)管線是一種優秀的任務處理方式,它能夠把處理流程劃分未若干個階段,並使用多條python線程來同時執行這些任務
(2)構建併發式的管線時,要注意許多問題,其中包括:如何防止某個階段陷入持續等待的狀態之中,如何中止工做線程,以及如何防止內存膨脹等
(3)Queue類所提供的機制,能夠cedilla解決上述問題,它具有阻塞式的隊列操做,可以指定緩衝區的尺寸,並且還支持join方法,這使得開發者能夠構建出健壯的管線
第40條:考慮用協程來併發地運行多個函數
(1)協程提供了一種有效的方式,令程序看上去好像可以同時運行大量函數
(2)對於生成器內的yield表達式來講,外部代碼經過send方法傳給生成器的那個值就是該表達式所要具有的值
(3)協程是一種強大的工具,它能夠把程序的核心邏輯,與程序同外部環境交互時所使用的代碼相隔離
第41條:考慮用concurrent.futures來實現真正的平行計算
第42條:用functools.wrap定義函數修飾器
爲了維護函數的接口,修飾以後的函數,必須保留原函數的某些標準Python屬性,例如__name__和__module__,這個時候咱們須要使用functools.wraps來確保修飾後函數具有正確的行爲
第43條:考慮以contextlib和with語句來改寫可複用的try/finally代碼
(1)能夠用with語句來改寫try/finally塊中的邏輯,以提高複用程度,並使代碼更加整潔
import threading
lock = threading.Lock()
lock.acquier()
try:
print("lock is held")
finally:
lock.release()
能夠直接使用下面的語法:
import threading
lock = threading.Lock()
with lock:
print("lock is held")
(2)內置的contextlib模塊提供了名叫爲contextmanager的修飾器,開發者只須要用它來修飾本身的函數,便可令該函數支持with語句
from contextlib import contextmanager
@contextmanager
def file_open(path):
''' file open test'''
try:
fp = open(path,"wb")
yield fp
except OSError:
print("We had an error!")
finally:
print("Closing file")
fp.close()
if name == "__main__":
with file_open("contextlibtest.txt") as fp:
fp.write("Testing context managers".encode("utf-8"))
(3)情景管理器能夠經過yield語句向with語句返回一個值,此值會賦給由as關鍵字所指定的變量
第44條:用copyreg實現可靠pickle操做
(1)內置的pickle模塊,只適合用來彼此信任的程序之間,對相關對象執行序列化和反序列化操做
(2)若是用法比較複雜,那麼pickle模塊的功能可能就會出現問題,咱們能夠用內置的copyreg模塊和pickle結合起來使用,以便爲舊數據添加缺失的屬性值、進行類的版本管理、並給序列化以後的數據提供固定的引入路徑
第45條:應該用datetime模塊來處理本地時間,而不是time模塊
(1)不要用time模塊在不一樣時區之間進行轉換
(2)若是要在不一樣時區之間,可靠地執行轉換操做,那就應該把內置的datetime模塊與開發者社區提供的pytz模塊打起來使用
(3)開發者老是應該先把時間表示爲UTC格式,而後對其執行各類轉換操做,最後再把它轉回本地時間
第46條:使用內置算法和數據結構
(1)雙向隊列 collections.deque
(2)有序字典 dollections.OrderDict
(3)帶有默認值的有序字典 collections.defaultdict
(4)堆隊列(優先級隊列)heapq.heap
(5)二分查找 bisect模塊中的bisect_left函數等提供了高效的二分折半搜索算法
(6)與迭代器有關的工具 itertools模塊
第47條:在重視精度的場合,應該使用decimal
(1)decimal模塊中的Decimal類默認提供28個小數位,以進行定點數字運算,還能夠按照開發射所要求的精度及四捨五入
第48條:學會安裝由Python開發者社區所構建的模塊
第49條:爲每一個函數、類和模塊編寫文檔字符串
第50條:用包來安排模塊,並提供穩固的API
(1)只要把__init__.py文件放入含有其餘源文件的目錄裏,就能夠將該目錄定義爲包,目錄中的文件,都將成爲包的子模塊,該包的目錄下面,也能夠含有其餘的包
(2)把外界可見的名稱,列在名爲__all__的特殊屬性裏,便可爲包提供一套明確的API
第51條:爲自編的模塊定義根異常,以便調用者與API相隔離
意思就是單獨用個模塊提供各類異常API
第52條:用適當的方式打破循環依賴關係
(1)調整引入順序
(2)先引入、再配置、最後運行
只在模塊中給出函數、類和常量的定義,而不要在引入的時候真正去運行那些函數
(3)動態引入:在函數或方法內部使用import語句
第53條:用虛擬環境隔離項目,並重建其依賴關係
第54條:考慮用模塊級別的代碼來配置不一樣的部署環境
(1)能夠根據外部條件來決定模塊的內容,例如,經過sys和os模塊來查詢宿主操做系統的特性,並以此來定義本模塊中的相關結構
第55條:經過repr字符串來輸出調試信息
第56條:經過unittest來測試所有代碼
這個在後面會單獨寫篇博客對unittest單元測試模塊進行詳細說明
第57條:考慮用pdb實現交互調試
第58條:先分析性能,而後再優化
(1)優化python程序以前,必定要先分析其性能,由於python程序的性能瓶頸一般很難直接觀察出來
(2)作性能分析時,應該使用cProfile模塊,而不要使用profile模塊,由於前者可以給出更爲精確的性能分析數據
第59條:用tracemalloc來掌握內存的使用及泄露狀況
在Python的默認實現中,也就是Cpython中,內存管理是經過引用計數來處理的,另外,Cpython還內置了循環檢測器,使得垃圾回收機制可以把那些自我引用的對象清除掉
(1)使用內置的gc模塊進行查詢,列出垃圾收集器當前所知道的每一個對象,該方法至關笨拙
(2)python3.4提供了內置模塊tracemalloc能夠打印出Python系統在執行每個分配內存操做時所具有的完整堆棧信息
文章到這裏就所有結束了,感謝您這麼有耐心的閱讀!
本文由博客羣發一文多發等運營工具平臺 OpenWrite 發佈