閱讀 20 Python Libraries You Aren't Using (But Should) 心得

閱讀 20 Python Libraries You Aren’t Using (But Should) 心得

此書是oreilly 提供的免費書,上面還有許多免費的書,有興趣的人就自行去觀賞一下。 這本書如題主要是要講一些好用但較不常聽到或是被你忽略的library,首先以standard library切入,其實python built-in library 實在太豐富了,我想其實很多人連一半的模組可能都沒用到呢~ 以下開始介紹

Standard libraries

下面記錄一些

collections

collections.OrderedDict

python的其中之一data type dict,dict是不會保持順序的,也就是說你去traverse他,順序是不一定的,但是相信有時候在寫程式時會有需求是要用特定順序去探訪資料,這也是為啥OrderedDict會出現,下面給個範例。

1
2
3
4
5
6
7
8
9
10
11
12
13
import string
from collections import OrderedDict

[x for x in zip(string.ascii_lowercase, range(5))]

# 為了要看出dict是不會保持順序的,這邊我們故意用dict在包裝zip的內容
# 你可以試著比較下面兩個輸出結果

dict(zip(string.ascii_lowercase, range(7)))
OrderedDict(zip(string.ascii_lowercase, range(7)))

# 將會發現 OrderedDict 仍然保持著順序,OrderedDcit保持的是一開始的順序
# 也就是traverse遇到的順序 a, b, c, d, e ,ouput時就是會依照這順序

這個library實用性挺高的,我個人在工作上偶爾都會用到,需要詳細了解的他整個功能其實建議直接看他源碼實作,官方文檔介紹的比較少

collections.defaultdict

相信各位使用dict時會遇到常常要先檢查某key值是否在dict中,有的話就會某某動作,沒有的話就做一些類似初始的動作,下面用範例來將上面敘述變得比較容易懂

1
2
3
4
5
6
7
from collections import defaultdict

s = [('yellow', 1), ('blue', 2)]
d = defaultdict(list)

for k, v in s:
d[k].append(v)

上面將為每個dict的key所對應的值,先預設初始為空list,因此你才可以直接call append這個方法,要不然一般的方式是

1
2
3
4
5
6
7
d = {}

for k, v in s:
if not k in d:
d[k] = []

d[k].append(v)

這個我到是就用得比較少,實際上也只是減少你的coding數目,讓他更簡潔,要不要使用這個lib,個人覺得倒是沒有那麼大必要性

collections.nametuple

哈這個我想應該很多人都知道這個才對,對於程式碼可讀性,個人絕對支持一定要用namedtuple,不只是讓你自己寫程式方便,也讓別人讀你的code更好懂,以下舉例

1
2
3
4
5
6

from collections import namedtuple

tup = (1, True, "red")
Spec = namedtuple('Spec', 'count enable color')
tup_new = Spec(count=1, enable=True, color='red')

namedtuple的好處就是我不會是用magic number來存取值,像是tup這個tuple,想要取值就會用tup[0]去取第一個值,那麼對於tup_new 我就可以使用tup_new.count取得第一個值,可讀性哪個高馬上立見高下。

contextlib

這個主要是用於resource clean up會比較常用到,像是

1
2
3

with open('test.dat', 'r') as f:
data = f.read()

使用with這語法後,就不用在自行呼叫f.close(),因為他會在你裡面區塊執行完畢後,自動幫你執行資源釋放,這個應該很多人都有用過,真的特別的地方是,contextmanager

首先先來講講 with 後面所接的物件,這個你是可以自定義的,要自己實作class,並且實作__enter____exit__,詳細的可以看PEP343,這邊簡單的說明一下,with這語法就是為了將try catch finally語法,做個算是更優美語意。

這篇提案有很多想法都是來自於pep340, pep346,經過一段修修改改後才終於討論出一個定案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

with EXPR as VAR:
BLOCK


mgr = (EXPR)
exit = type(mgr).__exit__
value = type(mgr).__enter__(mgr)
exc = True

try:
VAR = value # only if "as VAR" is present
BLOCK
except:

exc = False

if not exit(mgr, *sys.exc_info()):
raise

finally:
# the normal and non-local-goto cases are handled here
if exc:
exit(mgr, None, None, None)

上面列出部分pep的內容,with的語法大概等於下面那些一長串的東西,語法顯得更加簡潔吧~

但是這個是class層級的物件,假設你今天想要用function而已,那麼要怎麼做呢? python contextlib,裡面所實做的 contextmanager 就是為了這個需求而生,進而跑出一些有趣的寫法~

這本書舉個有趣的範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

from time import perf_counter
from array import array
from contextlib import contextmanager

@contextmanager
def timing(label):
t0 = perf_counter()
yield lambda: (label, t1-t0)
t1 = perf_counter()


with timing('Array tests') as total:
with timing('Array creation innermul') as inner:
x = array('d', [0]*1000000)

with timing('Array creation outermul') as outer:
x = array('d', [0]) * 1000000

print('Total [%s]: %.6f s' % total())
print('timing [%s]: %.6f s' % inner())
print('timing [%s]: %.6f s' % outer())

上面所做的就是去計算妳函式運算花了多少時間,很方便,同時也很讓人耳目一新吧,從範例的使用方法,也大概略知contextmanager是使用python generator去實作出來的,所以使用時才會有yield這語法,這範例有個有趣的地方,就是 lambda: (label, t1-t0) 我一開始其實對這個地方有點不解,因為照理說當程式執行到這行時,它應該會不懂t1是什麼而報錯才對,但是事實是不會喔~ 因為程式執行到yield時就會停住,lambda 那串還不會跑喔,等到你call next 他才會開始繼續執行下去,至於為何要多包一層lambda,這邊就是因為t1定義是在後面,要是你不這樣多包一層,直接 yield t1-t0 就會錯了喔~

另外,array 這模組就是能夠創造像c語言裡面的那種array,雖然應用情況在哪我不太清楚,我真的幾乎沒啥用到這個,不過這個範例到是讓我知道對於array而已直接丟一個大list進去反而效率會比較差… 倒是讓我長知識了。

atexit

在書本後面某範例有用到一個內建模組atexit,這個我也是沒用過,但是感覺挺新鮮的或許在某些地方可以用到,看官方文檔,簡單說這個模組的功用是可以在你的整個程式要結束時,通常是用來作為程市意外結束時,為了避免資料遺失還是做個保存,或者是資源釋放吧,目前大概想到這些。

Third Party Libraries

這邊有些套件就不特別介紹了,只挑幾個個人有興趣做記錄的套件

Fabulous

一個滿有趣的套件,讓你的開發terminal介面程式,可以更加漂亮的客製化

Pywebview

底層應該是使用webview的引擎之類的像是webkit去渲染html,js,css,建立出原生gui元件,比起一般的gui或許會更好用,但是我個人的話,會選擇用electron,畢竟他的community比較強大,比較多人維護,不過electron就不是用python寫程式囉~~

ptpython

這個老實講我覺得不是那麼需要拉,如果你懶得開editor或是ide,這個可以讓你在termial的repl享受到稍稍接近ide部份功能,雖然我覺得ipython就很夠了,我還是列上來記錄一下XD

boltons

boltons算是python builtins的增強吧,不過其實python本身內建模組就已經很夠了,所以這套件個人是覺得不太有必要,但是卻是一個可以拿來學習的套件

Cython

這個特別挑出來講的原因,其實也是因為我一直以來都有聽說這個但是卻沒有花時間來玩玩感受它的魔力,我另外還有耳聞過numba,只是這篇就先著重於cython來記錄改天再來挖時間比較這兩者差別。

總體上來說,他是一個算是小型語言語法上幾乎與python差不多,經過Cython interpreter會把code轉成c/c++在compile成python extension modules,來讓python使用

安裝cython

十分簡單的安裝方式 pip install Cython

cython process

.pyx file compiled by Cython to .c file and then .c file compiled by c compiler to .so or .pyd accroding to your operating system. There files can be import directly by python

哈,上面只是cython編譯流程,反正你只要知道.pyx是給cython看得就好,接著來看看官網的簡單範例。

hello.pyx

1
2
3

def say_hello_to(name):
print("Hello %s!" % name)

setup.py

1
2
3
4
5
6
7
8

from distutils.core import setup
from Cython.Build import cythonize

setup(
name = 'Hello world app',
ext_modules = cythonize("hello.pyx"),
)

使用distutils來產生library檔,輸入python setup.py build_ext --inplace,下完指令後,就會產生library檔,之後就可以在你的py file import使用

ex.

1
2
3

from hello import say_hello_to
say_hello_to('stranger')

其實就算你不用cython也是可以達到相同作用,但是就是用c/c++的語法去寫library,或是你用swig之類的工具也可,cython需要再多多使用才知道實用程度如何,不過有一點讓我覺得不錯的是,他跟ipython有結合,其實官網範例都有示範,詳情可以去看看~

ipython 有分很方便的 % 這是執行單行command line %% 這是執行整個cell的command
舉例你今天想要簡單分析某個程式碼所耗時間就是使用 %timeit range(10000) ,那麼你有很多行想要分析的話,就要使用 %%

個人有用ipython寫個簡單測試cython提升效率的測試,為了要知道效果所以就用個最簡單寫法同時也是效率最不好方式實現Fibonacci數列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

def factorial(n):
if n==1 or n==0:
return n
else:
return factorial(n-1)+factorial(n-2)
# above version will reach the maximum recursion

def gen_factorial(n):
# I think this will be much more efficient than recursive version
first, second = 0, 1

for i in range(n):
yield first
first, second = second, first+second

上面第一個寫法就是效率最爛的寫法,使用遞迴,原因是會造成多次重複計算,最簡單提升效率方法是,用個table記錄已算過的值,類似cache的概念,這樣效率就會大大提升,但是數字一大最終會遇到一個問題,就是遞迴次數有最大限制,會爆掉,所以我下面提供了用generator,同時也是目前個人覺得效率最好的方式,也不用怕遞迴次數上限,轉回正題,今天使用cython要來比較效率改善多少,就是要來改第一個版本

1
2
3
4
5
6

def cfactorial(int n):
if n==1 or n==0:
return n
else:
return cfactorial(n-1)+cfactorial(n-2)

這邊我只是加了,型態宣告而已喔~~ 呼叫 factorial(33) 以我個人電腦大概接近兩秒,然後呼叫 cfactorial(33) 大概0.5秒,讓我嚇到了呢,原來型態沒有確定的狀況會造成效率這麼大的差異呢~

個人是覺得可以先試著用ipython來嘗試cython,玩一段時間後再來決定是否拿來應用在工作上。

python