python高級(jí)特性
iteration迭代
對(duì)list,tuple的遍歷被稱為迭代。對(duì)list實(shí)現(xiàn)類似Java那樣的下標(biāo)循環(huán)怎么辦?Python內(nèi)置的enumerate函數(shù)可以把一個(gè)list變成索引-元素對(duì)
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
iterator迭代器
凡是可作用于for循環(huán)的對(duì)象都是Iterable類型;凡是可作用于next()
函數(shù)的對(duì)象都是Iterator類型,它們表示一個(gè)惰性計(jì)算的序列。集合數(shù)據(jù)類型如list
、dict
、str
等是Iterable但不是Iterator,不過可以通過iter()
函數(shù)獲得一個(gè)Iterator對(duì)象。
>>> isinstance(iter([]), Iterator)
True
Python的for
循環(huán)本質(zhì)上就是通過不斷調(diào)用next()
函數(shù)實(shí)現(xiàn)的。循環(huán)有for in
和while
,迭代只能用for in
。
受到內(nèi)存限制,列表容量肯定是有限的。generator生成器,屬于iterator。生成方法:
g = (x * x for x in range(10))
函數(shù)定義中包含yield
關(guān)鍵字, 用next(g)
或for n in g
獲取值。想要拿到返回值,必須捕獲StopIteration
錯(cuò)誤,返回值包含在StopIteration
的e.value
中。
python并發(fā)
python multiprocessing模塊封裝了多進(jìn)程和多線程,其中multiprocessing.Process新啟動(dòng)進(jìn)程,multiprocessing.Pool對(duì)應(yīng)多進(jìn)程池,multiprocessing.dummy.Pool對(duì)應(yīng)多線程池。后兩者用法一致,以下是多進(jìn)程的用法,其中args
為元組格式,Iterable
為可迭代對(duì)象。由于進(jìn)程鎖存在,多線程通常無加速效果。
from multiprocessing import Process
p = Process(target=f, args=(num, arr))
p.start()
p.join()
# 獲取最大進(jìn)程數(shù),可設(shè)為更小的值,如一半
import os
count = os.cpu_count()
from multiprocessing import Pool
pool=Pool(count)
results, async_results = [], []
for i in range(count):
# 同步并發(fā),子進(jìn)程會(huì)block,直到獲取結(jié)果,func一直在一個(gè)子進(jìn)程中執(zhí)行,故無加速效果
results.append(pool.apply(func, args))
# 異步并發(fā),子進(jìn)程不會(huì)block,支持callback
async_results.append(pool.apply_async(func, args, callback))
# results已為所需結(jié)果
# 返回AsyncResult,通過get獲取結(jié)果
get_results = [x.get() for x in async_results]
pool.close()
pool.join()
或
from multiprocessing import Pool
pool = Pool(count)
# func只能有1個(gè)入?yún)?pool.map(func, Iterable) # 同步并發(fā)
pool.map_async(func, Iterable)。# 異步并發(fā)
# func只能有1個(gè)入?yún)ⅲ琹azy模式,返回類似Generator,遍歷時(shí)(可能)計(jì)算
pool.imap(func, Iterable) # 有序并發(fā)
pool.imap_unordered(func, Iterable)。# 無序并發(fā)
# func可以有多個(gè)入?yún)ⅲ琁terable元素仍為Iterable,可解包為多個(gè)入?yún)?pool.starmap(func, Iterable) # 同步并發(fā)
pool.starmap_async(func, Iterable) # 異步并發(fā)
multiprocessing[.sharedctypes]模塊可用于一維數(shù)組內(nèi)存共享:
- Array:有鎖版,避免寫沖突。
- RawArray:無鎖版,性能好。
multiprocessing.shared_memory模塊可用于子進(jìn)程間內(nèi)存共享:
- SharedMemory:內(nèi)存共享。
- ShareableList:List共享,僅支持幾種元素類型。
multiprocessing.managers.SharedMemoryManager模塊,支持上述兩種共享內(nèi)存類型的管理。
使用多進(jìn)程計(jì)算非定長(zhǎng)向量距離矩陣
import numpy as np
import os
import multiprocessing
from functools import partial
from dtaidistance import dtw, dtw_ndim
# 進(jìn)程池initializer函數(shù)
def init_pool(array):
global glob_array # 共享全局變量
glob_array = array
# 子進(jìn)程函數(shù)
def process_fn(ij, func=None, array_width=None):
i, j, ai, aj = ij
# 子進(jìn)程讀取全局變量glob_array,對(duì)齊一維glob_array與原始二維array的對(duì)應(yīng)位置關(guān)系
glob_array[i * array_width + j] = func(ai, aj)
def calc_relation_mat(func, list1, list2=None, relation='dist'):
len1 = len(list1)
len2 = len1 if list2 is None else len(list2)
array = np.zeros((len1, len2))
# Pool.map僅支持一個(gè)入?yún)ⅲ褂闷瘮?shù)functools.partial,預(yù)先傳入其他參數(shù)
fn_partial = partial(process_fn, func=func, array_width=array.shape[0])
# array為展平的矩陣(即multiprocessing.RawArray, 多進(jìn)程不支持二維矩陣)
array_shared = multiprocessing.RawArray('d', array.ravel())
# 由于各進(jìn)程改動(dòng)對(duì)應(yīng)矩陣位置(即內(nèi)存地址)處的值,無沖突,故無需加進(jìn)程鎖
# 定義進(jìn)程池,指定進(jìn)程數(shù)量(processes),初始化函數(shù)(initializer)及其參數(shù)(initargs)
n_proc = max(os.cpu_count(), 16)
p = multiprocessing.Pool(processes=n_proc, initializer=init_pool, initargs=(array_shared,))
# 若list1==list2,先計(jì)算下三角矩陣,然后轉(zhuǎn)置后復(fù)值到上三角位置,否則全部計(jì)算
it = [(i, j, list1[i], list1[j]) for i in range(len1) for j in range(i if list2 is None else len2)]
# map函數(shù)向子進(jìn)程函數(shù)分配不同的參數(shù)
p.map(fn_partial, it)
p.close()
p.join()
# glob_array為子進(jìn)程中的全局變量,在主進(jìn)程中并未被定義,主進(jìn)程中的array_shared與子進(jìn)程中的glob_array指向同一內(nèi)存地址
array = np.frombuffer(array_shared, np.double).reshape(array.shape)
if list2 is None:
if relation == 'dist':
# 無需 - np.diag(np.diag(dist_mat)),因?yàn)閷?duì)角線為0
array = array + array.T
elif relation == 'sim':
# 對(duì)角線為1
array = array + array.T + np.eye(len(list1))
return array
if __name__ == '__main__':
list1 = [np.random.randn(x) for x in range(1, 11)]
dist_mat = calc_relation_mat(dtw.distance, list1)
多進(jìn)程/線程調(diào)試,子進(jìn)程/線程代碼異常時(shí),報(bào)錯(cuò)信息不會(huì)輸出到當(dāng)前父進(jìn)程/線程窗口,無法使用pdb直接對(duì)多進(jìn)程進(jìn)行調(diào)試。有以下幾種方法:
- print可以生效,但順序隨機(jī)。
- 設(shè)置pdb.set_trace()后,通過Pycharm提供的遠(yuǎn)程調(diào)試功能。
- ForkedPdb,來源于stackoverflow。
異常
unable to find vcvarsall.bat
解決辦法參考:
http://www.cnblogs.com/youxin/p/3159363.html
http://blog.csdn.net/secretx/article/details/17472107
Microsoft Visual C++ Compiler for Python 2.7
http://aka.ms/vcpython27
鎖
線程鎖,只能在同一個(gè)進(jìn)程不同線程之間加鎖,無法在不同進(jìn)程(如不同用戶)之間加鎖。如果其他線程鎖定同一個(gè)Flag,會(huì)被阻塞,直到鎖被釋放。任務(wù)會(huì)按加鎖的順序執(zhí)行。
import threading
# 創(chuàng)建鎖
mutex = threading.Lock()
# 加鎖,傳遞一個(gè)Flag
mutex.acquire(5)
# 執(zhí)行任務(wù),如讀寫文件
# 解鎖
mutex.release()
文件鎖,通過Linux文件在不同進(jìn)行(如不同用戶)之間加鎖。如果其他進(jìn)程/線程鎖定同一個(gè)文件,會(huì)被阻塞,直到鎖被釋放。任務(wù)不一定會(huì)按加鎖順序執(zhí)行。
在Linux下,Python的標(biāo)準(zhǔn)庫有現(xiàn)成的文件鎖模塊fcntl
,提供了unix系統(tǒng)fcntl()和ioctl()的接口。
fcntl.flock(fd, operation)
其中:
- ` fd 表示文件描述符;
-
operation
表示鎖操作,取值如下:- LOCK_SH:表示共享鎖,一個(gè)文件的共享鎖可以同時(shí)被多個(gè)進(jìn)程擁有。
- LOCK_EX:表示排他鎖,一個(gè)文件的排他鎖只能同時(shí)被一個(gè)進(jìn)程擁有。
- LOCK_UN:表示刪除文件鎖。
- LOCK_MAND:表示共享模式強(qiáng)制鎖,與LOCK_READ或者LOCK_WRITE聯(lián)合使用,表示是否允許并發(fā)的讀/寫操作。
import fcntl
# 方式一:
with open('/tmp/myfile.lock', 'w') as f:
# 加鎖
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
# 執(zhí)行任務(wù),如讀寫文件
# 解鎖方式一,主動(dòng)解鎖
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
# 解鎖方式二,文件關(guān)閉后,自動(dòng)解鎖
# 方式二:
f = open('/tmp/myfile.lock', 'w')
# 加鎖
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
# 執(zhí)行任務(wù),如讀寫文件
# 解鎖方式一,主動(dòng)解鎖
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
# 解鎖方式二,文件關(guān)閉后,自動(dòng)解鎖
f.close()
fcntl
模塊在Windows上不可用,可以使用msvcrt
模塊代替。一種跨平臺(tái)的文件鎖實(shí)現(xiàn)如下。
# Reference:
# - https://docs.python.org/zh-cn/3/library/fcntl.html
# - https://docs.python.org/zh-cn/3/library/msvcrt.html
# - https://docs.python.org/zh-cn/3/library/ctypes.html#ctypes.WinDLL
# - https://juejin.cn/post/6870689230440529927
# - https://zhuanlan.zhihu.com/p/354383209
import platform
if platform.system() != 'Windows':
import fcntl
is_unix = True
LOCK_FILE = '/tmp/file.lock'
else:
import msvcrt
is_unix = False
LOCK_FILE = 'C:\\file.lock'
NBYTES = 1
LOCK_EX = 2
LOCK_NB = 4
def lock(file_desc, mode=LOCK_EX):
"""同一進(jìn)程內(nèi)對(duì)同一文件重復(fù)加鎖,不同進(jìn)程對(duì)同一個(gè)文件重復(fù)加鎖,會(huì)阻塞或返回False。"""
if mode == LOCK_NB:
if is_unix:
try:
fcntl.flock(file_desc, fcntl.LOCK_EX | fcntl.LOCK_NB)
except:
return False
else:
try:
msvcrt.locking(file_desc.fileno(), msvcrt.LK_NBLCK, NBYTES)
file_desc.seek(0)
except:
return False
return True
else:
if is_unix:
fcntl.flock(file_desc, fcntl.LOCK_EX)
else:
msvcrt.locking(file_desc.fileno(), msvcrt.LK_LOCK, NBYTES)
file_desc.seek(0)
return True
def unlock(file_desc):
if is_unix:
fcntl.flock(file_desc, fcntl.LOCK_UN)
else:
# file_desc.seek(0)
msvcrt.locking(file_desc.fileno(), msvcrt.LK_UNLCK, 1)
return True
拷貝
淺層與深層復(fù)制(拷貝)的區(qū)別僅與復(fù)合對(duì)象(即包含列表或類的實(shí)例等其他對(duì)象的對(duì)象)相關(guān)。參考示例
-
=
:賦值語句(即引用),不復(fù)制對(duì)象,而是創(chuàng)建目標(biāo)和對(duì)象的綁定關(guān)系,id()
不變。 -
copy.copy(x)
:淺拷貝,不拷貝內(nèi)部對(duì)象。構(gòu)造一個(gè)新的復(fù)合對(duì)象,然后(在盡可能的范圍內(nèi))將原始對(duì)象中找到的對(duì)象的 引用 插入其中。 -
copy.deepcopy(x[, memo])
:深拷貝,完全拷貝了對(duì)象及其內(nèi)部對(duì)象。構(gòu)造一個(gè)新的復(fù)合對(duì)象,然后,遞歸地將在原始對(duì)象里找到的對(duì)象的副本插入其中。
深度復(fù)制操作通常存在兩個(gè)問題, 而淺層復(fù)制操作并不存在這些問題:
- 遞歸對(duì)象 (直接或間接包含對(duì)自身引用的復(fù)合對(duì)象) 可能會(huì)導(dǎo)致遞歸循環(huán)。
- 由于深層復(fù)制會(huì)復(fù)制所有內(nèi)容,因此可能會(huì)過多復(fù)制(例如本應(yīng)該在副本之間共享的數(shù)據(jù))。
deepcopy()
函數(shù)用以下方式避免了這些問題:
- 保留在當(dāng)前復(fù)制過程中已復(fù)制的對(duì)象的 "備忘錄" (
memo
) 字典;以及 - 允許用戶定義的類重載復(fù)制操作或復(fù)制的組件集合。
淺拷貝等價(jià)用法:
dict.copy()
-
numpy.copy()
,ndarray.copy()
:兩者默認(rèn)的存儲(chǔ)order
不同。推薦后者,同np.array(a, copy=True)
。 -
original_list[:]
:列表的索引和切片。 - 類可以使用與控制序列化(
pickling
)操作相同的接口來控制復(fù)制操作。 - 自定義類的拷貝,copy() 和 deepcopy()
深拷貝等價(jià)用法:
-
numpy.copyto()
,ndarray+0
(ndarray運(yùn)算)
numpy視圖(ndarray.view()
)/切片/索引,介于引用和淺拷貝之間,創(chuàng)建新對(duì)象,形狀等屬性可以不同,但共享數(shù)據(jù)區(qū)。numpy淺拷貝,數(shù)據(jù)區(qū)值類型數(shù)據(jù)不共享,子對(duì)象共享。numpy深度拷貝請(qǐng)用copy.deepcopy()
。