python高級(jí)特性

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ù)類型如listdictstr等是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 inwhile,迭代只能用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ò)誤,返回值包含在StopIteratione.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()

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 一、高級(jí)特性切片 對(duì)這種經(jīng)常取指定索引范圍的操作,用循環(huán)十分繁瑣,因此,Python提供了切片(Slice)操作符...
    zzj丶閱讀 562評(píng)論 0 1
  • 1.切片切片可以取list、tuple、string的元素python語言中把字符串看做一個(gè)tuple,因此可以通...
    JEZAU閱讀 447評(píng)論 1 2
  • 1、切片(slice)L[0:3]表示,從索引0開始取,直到索引3為止,但不包括索引3。即索引0,1,2,正好是3...
    bjchenli閱讀 254評(píng)論 0 0
  • 掌握了Python的數(shù)據(jù)類型、語句和函數(shù),基本上就可以編寫出很多有用的程序了。 比如構(gòu)造一個(gè)1, 3, 5, 7,...
    齊天大圣李圣杰閱讀 1,437評(píng)論 0 1
  • #!/usr/bin/python # -*- coding:UTF-8 -*- __author__ = 'wx...
    __Jasmine__閱讀 303評(píng)論 0 0