python yield,yield from,深淺拷貝

(一)yield和yield fromhtml

轉自:理解yield   yield from編程

 

(1)yield數組

一、一般的for…in…循環中,in後面是一個數組,這個數組就是一個可迭代對象,相似的還有鏈表,字符串,文件。它能夠是mylist = [1, 2, 3],也能夠是mylist = [x*x for x in range(3)]。 它的缺陷是全部數據都在內存中,若是有海量數據的話將會很是耗內存。多線程

二、對比可迭代對象,迭代器其實就只是多了一個函數:__next__(),能夠再也不使用for循環來間斷獲取元素值,而能夠直接使用next()方法來實現,可經過iter(a),將可迭代對象a轉換爲一個迭代器。併發

三、生成器是能夠迭代的,但只能夠讀取它一次。由於用的時候才生成。好比 mygenerator = (x*x for x in range(3)),注意這裏用到了(),它就不是數組,而上面的例子是[]。可迭代對象和迭代器,是將全部的值都生成存放在內存中,而生成器則是須要元素才臨時生成,節省時間,節省空間。異步

四、帶有 yield 的函數再也不是一個普通函數,而是一個生成器generator,可用於迭代。yield 是一個相似 return 的關鍵字,迭代一次遇到yield時就返回yield後面的值。並在這裏阻塞,等待下一次的調用。從而實現節省內存,實現異步編程。重點是:下一次迭代時,從上一次迭代遇到的yield後面的代碼開始執行。即yield就是 return 返回一個值,而且記住這個返回的位置,下次迭代就從這個位置後開始。異步編程

五、生成器並非一次生成全部元素,而是一次一次的執行返回,激活生成器執行的方法有:使用next(),或者使用generator.send(None)。next()等同於send(None),for循環就用到了next()函數

六、帶有yield的函數不只僅只用於for循環中,並且可用於某個函數的參數,只要這個函數的參數容許迭代參數。好比array.extend函數,它的原型是array.extend(iterable)。性能

七、send(msg)與next()的區別在於send能夠傳遞參數給yield表達式,這時傳遞的參數會做爲yield表達式的值,而yield的參數是返回給調用者的值。換句話說,就是send能夠強行修改上一個yield表達式值。好比函數中有一個yield賦值,a = yield 5,第一次迭代到這裏會返回5,a尚未賦值。第二次迭代時,使用.send(10),那麼,就是強行修改yield 5表達式的值爲10,原本是5的,那麼a=10。spa

八、send(msg)與next()都有返回值,它們的返回值是當前迭代遇到yield時,yield後面表達式的值,其實就是當前迭代中yield後面的參數。

九、第一次調用時必須先next()或send(None),不然會報錯,send後之因此爲None是由於這時候沒有上一個yield。能夠認爲,next()等同於send(None),for循環就用到了next()。

 

(2)yield from

多線程與協程:使用多線程實現併發時,多線程的運行須要頻繁的加鎖解鎖,切換線程,這極大地下降了併發性能;協程是爲非搶佔式多任務產生子程序的計算機程序組件,協程容許不一樣入口點在不一樣位置暫停或開始執行程序。協程和線程,有類似點,多個協程之間和線程同樣,只會交叉串行執行;也有不一樣點,線程之間要頻繁進行切換,加鎖解鎖,複雜度高且效率低。協程經過使用 yield 暫停生成器,能夠將程序的執行流程交給其餘的子程序,從而實現不一樣子程序的之間的交替執行,即程序從yield處暫停,而後能夠返回去作別的事。

yield from 後面須要加的是可迭代對象,它能夠是普通的可迭代對象,也能夠是迭代器,甚至是生成器

yield from後面加上可迭代對象,能夠把可迭代對象裏的每一個元素一個一個的yield出來,對比yield來講代碼更加簡潔,結構更加清晰(這種狀況下使用yield須要兩個循環,而yield from只須要一個循環)。

當 yield from 後面加上一個生成器後,就實現了生成的嵌套。實現生成器的嵌套,並非必定必需要使用yield from,但使用yield from能夠避免本身處理各類料想不到的異常。生成器嵌套的幾個概念:

一、調用方:調用委託生成器的客戶端(調用方)代碼

二、委託生成器:包含yield from表達式的生成器函數

三、子生成器:yield from後面加的生成器函數

這幾個概念的示例以下:

# 子生成器
def average_gen():
    total = 0
    count = 0
    average = 0
    while True:
        new_num = yield average
        count += 1
        total += new_num
        average = total/count

# 委託生成器
def proxy_gen():
    while True:
        yield from average_gen()

# 調用方
def main():
    calc_average = proxy_gen()
    next(calc_average)            # 預激下生成器
    print(calc_average.send(10))  # 打印:10.0
    print(calc_average.send(20))  # 打印:15.0
    print(calc_average.send(30))  # 打印:20.0

if __name__ == '__main__':
    main()

委託生成器的做用是:在調用方與子生成器之間創建一個雙向通道即調用方能夠經過send()直接發送消息給子生成器,而子生成器yield的值,也是直接返回給調用方。委託生成器只起一個橋樑做用,它創建的是一個雙向通道,它並不會對子生成器yield回來的內容作攔截。以下:

# 子生成器
def average_gen():
    total = 0
    count = 0
    average = 0
    while True:
        new_num = yield average
        if new_num is None:
            break
        count += 1
        total += new_num
        average = total/count

    # 每一次return,都意味着當前協程結束。
    return total,count,average

# 委託生成器
def proxy_gen():
    while True:
        # 只有子生成器要結束(return)了,yield from左邊的變量纔會被賦值,後面的代碼纔會執行
        total, count, average = yield from average_gen()
        print("計算完畢!!\n總共傳入 {} 個數值, 總和:{},平均數:{}".format(count, total, average))

# 調用方
def main():
    calc_average = proxy_gen()
    next(calc_average)            # 預激協程
    print(calc_average.send(10))  # 打印:10.0
    print(calc_average.send(20))  # 打印:15.0
    print(calc_average.send(30))  # 打印:20.0
    calc_average.send(None)      # 結束協程
    # 若是此處再調用calc_average.send(10),因爲上一協程已經結束,將重開一協程

if __name__ == '__main__':
    main()


程序輸出:
10.0
15.0
20.0
計算完畢!!
總共傳入 3 個數值, 總和:60,平均數:20.0

yield from的好處是其作了不少全面的異常處理,使得調用端能夠直接使用而不用本身實現多種異常的處理。

 

(二)深淺拷貝

轉自 Python拷貝    深淺拷貝

 

(1)=賦值:數據徹底共享(=賦值是在內存中指向同一個對象,若是是可變(mutable)類型,好比列表,修改其中一個,另外一個一定改變

若是是不可變類型(immutable),好比字符串,修改了其中一個,另外一個並不會變

l2 = l1 ,l1 徹底賦值給l2 ,l2的內存地址與l1 相同,即內存徹底指向

l1 = [1, 2, 3, ['aa', 'bb']]
l2 = l1
l2[0]='aaa'
l2[3][0]='bbb'
print(l1)  #['aaa', 2, 3, ['bbb', 'bb']]
print(id(l1)==id(l2))  #True

 

(2)淺拷貝:數據半共享(複製其數據獨立內存存放,可是隻拷貝成功第一層)沒有拷貝子對象,因此原始數據改變,子對象會改變

l1 = [1,2,3,[11,22,33]]
l2 = l1.copy()
print(l2) #[1,2,3,[11,22,33]]
l2[3][2]='aaa'
print(l1) #[1, 2, 3, [11, 22, 'aaa']]
print(l2) #[1, 2, 3, [11, 22, 'aaa']]
l1[0]= 0
print(l1) #[0, 2, 3, [11, 22, 'aaa']]
print(l2) #[1, 2, 3, [11, 22, 'aaa']]
print(id(l1)==id(l2)) #Flase

l2淺拷貝了l1 ,以後l2把其列表中的列表的元素給修改,從結果看出,l1也被修改了。可是僅僅修改l1列表中的第一層元素,卻並無影響l2。

比較一下l2與l1的內存地址:False,說明,l2在內存中已經獨立出一部分複製了l1的數據,可是隻是淺拷貝,第二層的數據並無拷貝成功,而是指向了l1中的第二層數據的內存地址,因此共享內存‘至關於‘’等號賦值’‘,因此就會有l2中第二層數據發生變化,l1中第二層數據也發生變化

l2拷貝l1的時候只拷貝了他的第一層,也就是在其餘內存中從新建立了l1的第一層數據,可是l2沒法拷貝l1的第二層數據,也就是列表中的列表,因此他就只能指向l1中的第二層數據

由此,當修改l1中第二層數據的時候,淺拷貝l1的l2中的第二層數據也隨之發生改變

 

(3)深拷貝:數據徹底不共享(複製其數據完徹底全放獨立的一個內存,徹底拷貝,數據不共享)

深拷貝就是完徹底全複製了一份,且數據不會互相影響,由於內存不共享。

深拷貝的方法有:導入模塊

import copy
l1 = [1, 2, 3, [11, 22, 33]]
# l2 = copy.copy(l1)  淺拷貝
l2 = copy.deepcopy(l1)
print(l1,'>>>',l2)
l2[3][0] = 1111
print(l1,">>>",l2)

深拷貝就是數據完徹底全獨立拷貝出來一份,包含對象裏面的自對象的拷貝,因此原始對象的改變不會形成深拷貝里任何子元素的改變

相關文章
相關標籤/搜索