(一)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的好處是其作了不少全面的異常處理,使得調用端能夠直接使用而不用本身實現多種異常的處理。
(二)深淺拷貝
(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)
深拷貝就是數據完徹底全獨立拷貝出來一份,包含對象裏面的自對象的拷貝,因此原始對象的改變不會形成深拷貝里任何子元素的改變