上一節的最後,初步瞭解Python多進程,如今咱們能夠繼續探索multiprocessing包中更加高級的工具。這些工具可讓咱們更加便利地實現多進程。html
**進程池 (Process Pool)**能夠建立多個進程。這些進程就像是隨時待命的士兵,準備執行任務(程序)。一個進程池中能夠容納多個待命的士兵: python
好比下面的程序:算法
import multiprocessing as mul def f(x): return x**2 pool = mul.Pool(5) rel = pool.map(f,[1,2,3,4,5,6,7,8,9,10]) print(rel) #打印結果:[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
咱們建立了一個允許5個進程的進程池 (Process Pool) 。Pool運行的每一個進程都執行f()函數。咱們利用map()方法,將f()函數做用到表的每一個元素上。這與built-in的map()函數相似,只是這裏用5個進程並行處理。若是進程運行結束後,還有須要處理的元素,那麼的進程會被用於從新運行f()函數。除了map()方法外,Pool還有下面的經常使用方法。sql
apply_async(func,args) 從進程池中取出一個進程執行func,args爲func的參數。它將返回一個AsyncResult的對象,你能夠對該對象調用**get()**方法以得到結果。數據庫
close() 進程池再也不建立新的進程編程
join() wait進程池中的所有進程。必須對Pool先調用close()方法才能join。後端
###二、共享資源 咱們在Python多進程初步已經提到,咱們應該儘可能避免多進程共享資源。多進程共享資源必然會帶來進程間相互競爭。而這種競爭又會形成race condition,咱們的結果有可能被競爭的不肯定性所影響。但若是須要,咱們依然能夠經過共享內存和Manager對象這麼作。數組
共享內存安全
咱們已經講述了**共享內存(shared memory)**的原理,這裏給出用Python實現的例子:服務器
# modified from official documentation import multiprocessing def f(n, a): n.value = 3.14 a[0] = 5 num = multiprocessing.Value('d', 0.0) arr = multiprocessing.Array('i', range(10)) p = multiprocessing.Process(target=f, args=(num, arr)) p.start() p.join() print num.value # 打印結果:3.14 print arr[:] # 打印結果:5, 1, 2, 3, 4, 5, 6, 7, 8, 9]
這裏咱們實際上只有主進程和Process對象表明的進程。咱們在主進程的內存空間中建立共享的內存,也就是Value和Array兩個對象。對象Value被設置成爲雙精度數(d), 並初始化爲0.0。而Array則相似於C中的數組,有固定的類型(i, 也就是整數)。在Process進程中,咱們修改了Value和Array對象。回到主程序,打印出結果,主程序也看到了兩個對象的改變,說明資源確實在兩個進程之間共享。
Manager
Manager對象相似於服務器與客戶之間的通訊 (server-client),與咱們在Internet上的活動很相似。咱們用一個進程做爲服務器,創建Manager來真正存放資源。其它的進程能夠經過參數傳遞或者根據地址來訪問Manager,創建鏈接後,操做服務器上的資源。在防火牆容許的狀況下,咱們徹底能夠將Manager運用於多計算機,從而模仿了一個真實的網絡情境。下面的例子中,咱們對Manager的使用相似於shared memory,但能夠共享更豐富的對象類型。
import multiprocessing def f(x, arr, l): x.value = 3.14 arr[0] = 5 l.append('Hello') server = multiprocessing.Manager() x = server.Value('d', 0.0) arr = server.Array('i', range(10)) l = server.list() proc = multiprocessing.Process(target=f, args=(x, arr, l)) proc.start() proc.join() print(x.value) print(arr) print(l)
Manager利用list()方法提供了表的共享方式。實際上你能夠利用dict()來共享詞典,Lock()來共享threading.Lock(注意,咱們共享的是threading.Lock,而不是進程的mutiprocessing.Lock。後者自己已經實現了進程共享)等。 這樣Manager就容許咱們共享更多樣的對象。
##2、數學與隨機數 (math包,random包) 咱們已經在Python運算中看到Python最基本的數學運算功能。此外,math包補充了更多的函數。固然,若是想要更加高級的數學功能,能夠考慮選擇標準庫以外的numpy和scipy項目,它們不但支持數組和矩陣運算,還有豐富的數學和物理方程可供使用,牛逼啊。
此外,random包能夠用來生成隨機數。隨機數不只能夠用於數學用途,還常常被嵌入到算法中,用以提升算法效率,並提升程序的安全性。
###一、math包 math包主要處理數學相關的運算。math包定義了兩個常數:
math.e # 天然常數e math.pi # 圓周率pi 此外,math包還有各類運算函數 (下面函數的功能能夠參考數學手冊): math.ceil(x) # 對x向上取整,好比x=1.2,返回2 math.floor(x) # 對x向下取整,好比x=1.2,返回1 math.pow(x,y) # 指數運算,獲得x的y次方 math.log(x) # 對數,默認基底爲e。可使用base參數,來改變對數的基地。好比math.log(100,base=10) math.sqrt(x) # 平方根
三角函數: math.sin(x), math.cos(x), math.tan(x), math.asin(x), math.acos(x), math.atan(x)
這些函數都接收一個弧度(radian)爲單位的x做爲參數。
角度和弧度互換: math.degrees(x), math.radians(x)
雙曲函數: math.sinh(x), math.cosh(x), math.tanh(x), math.asinh(x), math.acosh(x), math.atanh(x)
特殊函數: math.erf(x), math.gamma(x)
###二、random包 若是你已經瞭解**僞隨機數(psudo-random number)**的原理,那麼你可使用以下:
random.seed(x)
來改變隨機數生成器的種子seed。若是你不瞭解其原理,你沒必要特別去設定seed,Python會幫你選擇seed。
####(1)、隨機挑選和排序
random.choice(seq) # 從序列的元素中隨機挑選一個元素,好比random.choice(range(10)),從0到9中隨機挑選一個整數。 random.sample(seq,k) # 從序列中隨機挑選k個元素 random.shuffle(seq) # 將序列的全部元素隨機排序
####(2)、隨機生成實數
下面生成的實數符合均勻分佈(uniform distribution),意味着某個範圍內的每一個數字出現的機率相等:
random.random() # 隨機生成下一個實數,它在[0,1)範圍內。 random.uniform(a,b) # 隨機生成下一個實數,它在[a,b]範圍內。
下面生成的實數符合其它的分佈 (你能夠參考一些統計方面的書籍來了解這些分佈):
random.gauss(mu,sigma) # 隨機生成符合高斯分佈的隨機數,mu,sigma爲高斯分佈的兩個參數。 random.expovariate(lambd) # 隨機生成符合指數分佈的隨機數,lambd爲指數分佈的參數。
此外還有對數分佈,正態分佈,Pareto分佈,Weibull分佈,可參考下面連接:
http://docs.python.org/library/random.html
假設咱們有一羣人蔘加舞蹈比賽,爲了公平起見,咱們要隨機排列他們的出場順序。咱們下面利用random包實現:
import random all_people = ['Tom', 'Vivian', 'Paul', 'Liya', 'Manu', 'Daniel', 'Shawn'] random.shuffle(all_people) for i,name in enumerate(all_people): print(i,':'+name)
##3、循環器 (itertools) 在循環對象和函數對象中,咱們瞭解了循環器(iterator)的功能。循環器是對象的容器,包含有多個對象。經過調用循環器的next()方法 (next()方法,在Python 3.x中),循環器將依次返回一個對象。直到全部的對象遍歷窮盡,循環器將舉出StopIteration錯誤。
itertools的工具均可以自行實現。itertools只是提供了更加成形的解決方案。
在for i in iterator結構中,循環器每次返回的對象將賦予給i,直到循環結束。使用**iter()**內置函數,咱們能夠將諸如表、字典等容器變爲循環器。好比:
for i in iter([2, 4, 5, 6]): print(i)
標準庫中的itertools包提供了更加靈活的生成循環器的工具。這些工具的輸入大都是已有的循環器。另外一方面,這些工具徹底能夠自行使用Python實現,該包只是提供了一種比較標準、高效的實現方式。這也符合Python「只有且最好只有解決方案」的理念。
# import the tools from itertools import *
###一、無窮循環器
count(5, 2) #從5開始的整數循環器,每次增長2,即5, 7, 9, 11, 13, 15 ... cycle('abc') #重複序列的元素,既a, b, c, a, b, c ... repeat(1.2) #重複1.2,構成無窮循環器,即1.2, 1.2, 1.2, ...
repeat也能夠有一個次數限制:
repeat(10, 5) #重複10,共重複5次
###二、函數式工具 函數式編程是將函數自己做爲處理對象的編程範式。在Python中,函數也是對象,所以能夠輕鬆的進行一些函數式的處理,好比map(), filter(), reduce()函數。
itertools包含相似的工具。這些函數接收函數做爲參數,並將結果返回爲一個循環器。
好比:
from itertools import * rlt = imap(pow, [1, 2, 3], [1, 2, 3]) for num in rlt: print(num)
上面顯示了imap函數。該函數與map()函數功能類似,只不過返回的不是序列,而是一個循環器。包含元素1, 4, 27,即11, 22, 33的結果。函數pow**(內置的乘方函數)做爲第一個參數。pow()依次做用於後面兩個列表的每一個元素,並收集函數結果,組成返回的循環器。
此外,還能夠用下面的函數:
starmap(pow, [(1, 1), (2, 2), (3, 3)]) #pow將依次做用於表的每一個tuple。 ifilter函數與filter()函數相似,只是返回的是一個循環器。 ifilter(lambda x: x > 5, [2, 3, 5, 6, 7]) #將lambda函數依次做用於每一個元素,若是函數返回True,則收集原來的元素。6, 7。
此外:
ifilterfalse(lambda x: x > 5, [2, 3, 5, 6, 7]) #與上面相似,但收集返回False的元素。2, 3, 5 takewhile(lambda x: x < 5, [1, 3, 6, 7, 1]) #當函數返回True時,收集元素到循環器。一旦函數返回False,則中止。1, 3 dropwhile(lambda x: x < 5, [1, 3, 6, 7, 1]) #當函數返回False時,跳過元素。一旦函數返回True,則開始收集剩下的全部元素到循環器。6, 7, 1
###三、組合工具 咱們能夠經過組合原有循環器,來得到新的循環器。
chain([1, 2, 3], [4, 5, 7]) # 鏈接兩個循環器成爲一個。1, 2, 3, 4, 5, 7 product('abc', [1, 2]) # 多個循環器集合的笛卡爾積。至關於嵌套循環 for m, n in product('abc', [1, 2]): print m, n permutations('abc', 2) # 從'abc'中挑選兩個元素,好比ab, bc, ... 將全部結果排序,返回爲新的循環器。
注意,上面的組合分順序,即ab, ba都返回。
combinations('abc', 2) # 從'abcd'中挑選兩個元素,好比ab, bc, ... 將全部結果排序,返回爲新的循環器。
注意,上面的組合不分順序,即ab, ba的話,只返回一個ab。
combinations_with_replacement('abc', 2) # 與上面相似,但容許兩次選出的元素重複。即多了aa, bb, cc
###四、groupby() 將key函數做用於原循環器的各個元素。根據key函數結果,將擁有相同函數結果的元素分到一個新的循環器。每一個新的循環器以函數返回結果爲標籤。
這就好像一羣人的身高做爲循環器。咱們可使用這樣一個key函數: 若是身高大於180,返回"tall";若是身高底於160,返回"short";中間的返回"middle"。最終,全部身高將分爲三個循環器,即"tall", "short", "middle"。
def height_class(h): if h > 180: return "tall" elif h < 160: return "short" else: return "middle" friends = [191, 158, 159, 165, 170, 177, 181, 182, 190] friends = sorted(friends, key = height_class) for m, n in groupby(friends, key = height_class): print(m) print(list(n))
注意,groupby的功能相似於UNIX中的uniq命令。分組以前須要使用sorted()對原循環器的元素,根據key函數進行排序,讓同組元素先在位置上靠攏。
###五、其它工具
compress('ABCD', [1, 1, 1, 0]) # 根據[1, 1, 1, 0]的真假值狀況,選擇第一個參數'ABCD'中的元素。A, B, C islice() # 相似於slice()函數,只是返回的是一個循環器 izip() # 相似於zip()函數,只是返回的是一個循環器。
##4、數據庫 (sqlite3) Python自帶一個輕量級的關係型數據庫SQLite。這一數據庫使用SQL語言。SQLite做爲後端數據庫,能夠搭配Python建網站,或者製做有數據存儲需求的工具。SQLite還在其它領域有普遍的應用,好比HTML5和移動端。Python標準庫中的sqlite3提供該數據庫的接口。
我將建立一個簡單的關係型數據庫,爲一個書店存儲書的分類和價格。數據庫中包含兩個表:category用於記錄分類,book用於記錄某個書的信息。一本書歸屬於某一個分類,所以book有一個外鍵(foreign key),指向catogory表的主鍵id。
sqlite3只是一個SQLite的接口。想要熟練的使用SQLite數據庫,還須要學習更多的關係型數據庫的知識。
###一、建立數據庫 我首先來建立數據庫,以及數據庫中的表。在使用connect()鏈接數據庫後,我就能夠經過定位指針cursor,來執行SQL命令:
import sqlite3 # test.db is a file in the working directory. conn = sqlite3.connect("test.db") c = conn.cursor() # create tables c.execute('''CREATE TABLE category (id int primary key, sort int, name text)''') c.execute('''CREATE TABLE book (id int primary key, sort int, name text, price real, category int, FOREIGN KEY (category) REFERENCES category(id))''') # save the changes conn.commit() # close the connection with the database conn.close()
SQLite的數據庫是一個磁盤上的文件,如上面的test.db,所以整個數據庫能夠方便的移動或複製。test.db一開始不存在,因此SQLite將自動建立一個新文件。
利用execute()命令,我執行了兩個SQL命令,建立數據庫中的兩個表。建立完成後,保存並斷開數據庫鏈接。
###二、插入數據 上面建立了數據庫和表,確立了數據庫的抽象結構。下面將在同一數據庫中插入數據:
import sqlite3 conn = sqlite3.connect("test.db") c = conn.cursor() books = [(1, 1, 'Cook Recipe', 3.12, 1), (2, 3, 'Python Intro', 17.5, 2), (3, 2, 'OS Intro', 13.6, 2), ] # execute "INSERT" c.execute("INSERT INTO category VALUES (1, 1, 'kitchen')") # using the placeholder c.execute("INSERT INTO category VALUES (?, ?, ?)", [(2, 2, 'computer')]) # execute multiple commands c.executemany('INSERT INTO book VALUES (?, ?, ?, ?, ?)', books) conn.commit() conn.close()
插入數據一樣可使用execute()來執行完整的SQL語句。SQL語句中的參數,使用"?"做爲替代符號,並在後面的參數中給出具體值。這裏不能用Python的格式化字符串,如"%s",由於這一用法容易受到SQL注入攻擊。
我也能夠用**executemany()**的方法來執行屢次插入,增長多個記錄。每一個記錄是表中的一個元素,如上面的books表中的元素。
###三、查詢 在執行查詢語句後,Python將返回一個循環器,包含有查詢得到的多個記錄。你循環讀取,也可使用sqlite3提供的**fetchone()和fetchall()**方法讀取記錄:
import sqlite3 conn = sqlite3.connect('test.db') c = conn.cursor() # retrieve one record c.execute('SELECT name FROM category ORDER BY sort') print(c.fetchone()) print(c.fetchone()) # retrieve all records as a list c.execute('SELECT * FROM book WHERE book.category=1') print(c.fetchall()) # iterate through the records for row in c.execute('SELECT name, price FROM book ORDER BY sort'): print(row)
查詢結果:
c.execute('SELECT * FROM book ORDER BY sort') print(c.fetchall())
###四、更新與刪除 你能夠更新某個記錄,或者刪除記錄:
conn = sqlite3.connect("test.db") c = conn.cursor() c.execute('UPDATE book SET price=? WHERE id=?',(1000, 1)) c.execute('DELETE FROM book WHERE id=2') conn.commit() conn.close()
你也能夠直接刪除整張表:
c.execute('DROP TABLE book')
若是刪除test.db,那麼整個數據庫會被刪除。
注:本Python學習筆記是按照Vamei的博客教程來學習的,若有興趣能夠參考Vamei Python快速開發博文