Effective python(五):內建模組
阿新 • • 發佈:2020-03-28
#### 1,考慮使用contextlib和with語句改寫可複用的try/finally程式碼
>1. `with lock:print('lock is held')`相當於`try:print('lock is held')`,`finally:lock.release`,使用with可以避免繁瑣的語句
>2. 開發者可以使用內建的contextlib模組的contextmanager修飾器來處理自己編寫的物件和函式以支援with語句,這樣做比標準寫法更便捷,如果使用標準方式寫,需要定義新類並提供__enter__和__exit__方法
> ```python
> from contextlib import contextmanager
>
> #上下文管理器
> @contextmanager
> def test():
> print("初始化")
> #可以在這裡開啟資源,如log等級上升
> try:
> print("with開始")
> #如果有錯誤會通過yield彈出
> yield
> finally:
> print("離開時釋放資源")
>
> with test():
> print("go")
> ```
>3. 可以在yield處彈出一個物件,通過as指定為一個區域性變數,在with內可以對其進行互動
#### 2,使用copyreg實現可靠的pickle操作
>1. python內建的pickle模組能將python物件轉化為位元組流,也能把位元組反序列化為python物件,但pickle處理後的資料實際上就是一個程式,可能會混入惡意資訊,對程式造成損壞,json產生的資料是一種安全的資訊,只描述物件如何構成,不會造成額外風險,所以pickle處理的位元組流不應該在未受信任的程式之間傳播
>2. 例如定義一個`GameState`類,例項化為物件`state=GameState()`包含玩家的當前遊戲狀態,生命,金幣等等,玩家退出遊戲的時候,將state直接寫到一份檔案裡,遊戲載入時讀取
> ```python
> with open(path,'wb') as f:
> pickle.dump(state,f)
>
> with open(path,'rb') as f:
> state_after=pickle.load(f)
> print(state_after.__dict__)
> ```
>3. 如果類新增加了一些屬性,但儲存的物件仍然是舊的,那就需要使用copyreg了,先將類新增一個`__init__`構造器,通過傳參的方式初始化屬性,然後使用如下程式碼註冊函式,序列化和反序列化仍然按照原來的方式使用即可
> ```python
> def unpickle_game_state(kwargs):
> return GameState(**kwargs)#返回State例項化
>
> def pickle_game_state(game_state):#引數為State物件
> kwargs=game_state.__dict__#獲取其屬性
>
> #序列化封裝需要返回反序列化的函式和引數
> return unpickle_game_state,(kwargs,)
>
> copyreg.pickle(GameState,pickle_game_state)#註冊pickle函式
> ```
>4. 使用版本號管理類,修改copyreg註冊的pickle函式,在裡面新增一個版本號的引數`kwargs['version]=2`,然後在反序列化函式中根據版本號對其進行操作,即可相容版本
>5. 固定引入路徑,若重構時,將類名修改或刪除,那麼反序列化的時候會出錯,使用copyreg.pickle註冊後,會自動指向unpickle函式,所以不用擔心修改類名的問題,但如果沒有使用copyreg註冊,那麼修改類名後反序列化就會報錯
#### 4,使用datetime模組處理本地時間而不是time模組
>1. time模組,內建的time模組中有一個名叫localtime的函式,可以把UNIX時間戳(timestamp即UTC時刻距離UNIX計時原點的秒數)轉換為宿主計算機時區的當地時間,這個模組不夠穩定,只能轉換主機時區的時間,其它地區會出錯,應該儘量不用,而是使用datetime模組
> ```python
> from time import localtime,strftime,strptime,mktime
> time_format='%Y-%m-%d %H:%M:%S'
> time_str=strftime(time_format,localtime(1407694710))
> #將時間戳轉換為當地時間
> print(time_str)
>
> #將本地時間轉化為UTC時間
> #strptime解析時間字串,mktime將本地時間轉換為UNIX時間戳
> print(mktime(strptime(strptime(time_str,time_format))))
> ```
>2. datetime模組
> ```python
> from datetime import datetime,timezone
> from time import mktime
>
> #UTC時間轉本地時間
> now=datetime(2014,8,10,18,18,30)
> now_utc=now.replace(tzinfo=timezone.utc)
> #注意此處若想可靠的轉換時區,還需要搭配pytz模組
> now_local=now_utc.astimezone()#此處只包含UTC時區
> print(now_local)
>
> #本地時間轉UTC格式時間戳
> time_str='2014-08-10 11:18:30'
> time_format='%Y-%m-%d %H:%M:%S'
> now=datetime.strptime(time_str,time_format)
> time_tuple=now.timetuple()
> utc_now=mktime(time_tuple)
> print(time_tuple)
> ```
>3. 若要在不同時區之間執行可靠的轉換操作,還需要搭配pytz模組使用
#### 5,內建的資料結構與演算法 (開發者不應該自己去重新實現,因為很難把他們寫好)
>1. 雙向佇列,collections模組中的deque類,從該佇列頭部與尾部插入或移除一個元素,只有O(1)的時間複雜度
> ```python
> d=deque()
> d.append(1)
> x=d.popleft()
> ```
>2. 有序字典,collections模組中的OrderedDict類,它能夠按照鍵的插入順序,保留鍵值對在字典中的次序。標準字典是無序的,也就是說,在相同鍵值對的兩個字典上迭代可能出現不同的迭代順序
> ```python
> a=OrderedDict()
> a['x']=1
> ```
>3. 預設值字典,collections模組中的defaultdict類,本例中用int函式建立字典,預設值為0
> ```python
> stats=defaultdict()
> stats['my_counter']+=1
> ```
>4. 堆佇列(優先佇列),heapq模組中的heappush、heappop和nsmallest等函式,能夠在標準的list中建立堆結構,時間複雜度O(logn),普通列表O(n)
> ```python
> a=[]
> heappush(a,5)
> heappush(a,3)
> heappush(a,7)
> heappush(a,4)
> #總是能彈出優先順序較高的元素,
> #預設是越小元素優先順序越高
> print(heappop(a))
> #即使呼叫sort後依然能保持堆結構
> a.sort()
> ```
>5. 二分查詢,bisect模組中的bisect_left函式,使用Index查詢複雜度為O(n),二分為O(logn),注意:使用前列表應排好序,bisect搜尋一百萬個元素的列表,與index搜尋包含14個元素的列表,所耗時間差不多
> ```python
> i_index = bisect_left(alist,number)
> ```
>6. 與迭代器有關的工具,itertools模組,可分為三大類
>>♦ 能夠把迭代器連線起來的函式:
>>- chain:將多個迭代器按順序連成一個迭代器
>>- cycle:無限重複某個迭代器中的各個元素
>>- tee:把一個迭代器拆分成多個平行的迭代器
>>- zip_longest:與內建的zip函式相似,可以應對不同長度的迭代器
>>♦ 能夠從迭代器中過濾元素的函式:
>>- islice:在不復制的前提下,根據索引值來切割迭代器獲取迭代器的一部分
>>- takewhile:在判定函式為True的時候,從迭代器逐個返回元素
>>- dropwhile:從判定函式初次為False的地方開始,逐個返回迭代器中的元素
>>- filterfalse:從迭代器中逐個返回能令判定函式為False的所有元素,效果與filter函式相反
>>♦ 能夠把迭代器中的元素組合起來的函式:
>>- product:根據迭代器中的元素計算笛卡爾積(就是x*y),並返回。可以使用product改寫深度巢狀的列表推導操作
>>- permutations:用迭代器中的元素構建長度為N的有序排列,例:`permutations('ABCD',2) # AB AC AD BA BC BD CA CB CD DA DB DC`
>>- combination:用迭代器中的元素構建長度為N的無序組合,例:`combinations('ABCD', 2) # AB AC AD BC BD CD`
>備註:如果發現自己需要編寫一段非常麻煩的迭代程式,應該花些時間看看itertools的文件看看有沒有現成的工具可以用
#### 6,在重視精度的場合,使用decimal模組中的Decimal類,該類預設提供28個小數位,以進行定點數學運算,有需要可以把精度提的更高
>1. `dec=Decimal('1.45')`在decimal之間進行計算會得到精確的結果,計算後的型別仍然是Decimal型別,`print(dec)`
>2. Decimal類提供quantize內建函式,它可以按照精度和舍入方式精確的調整數值`result=dec.quantize(Decimal('0.01'),rounding=ROUND_UP)`,`print(result)`
>3. 若要使用精度不受限的方式表達有理數,那麼可以考慮使用Fraction類,包含在內建的fractions模組裡
#### 7,學會使用pypi,Python中央倉庫:https://pypi.python.org
>1. 如果碰到不熟悉的程式設計難題,應該去PyPI裡看看別人的程式碼
>2. pip3安裝python3版本的軟體包,pip安裝python2版本的