Python基礎知識點整理


參考資料

[1]. 廖雪峰 <Python 2.7 教程>

[2]. <Python性能雞湯> http://www.oschina.net/question/1579_45822

內容整理

函數

  • 函數返回: 函數執行時遇到return即執行完畢, 返回結果; 沒有return, 執行完畢返回None; return None可以簡寫為return.
  • 內建函數: 高效快速, e.g. input(), ord(), pow(), isinstance(), iter()
  • 局部變量: 比全局變量快, 盡量避免global; 但下文有提到全局字典保存不同線程專屬對象的技巧
  • 默認參數: 必須指向不變對象: 函數在定義時, 默認參數值就會被計算出來
  • Python解釋器: 只檢查參數個數, 不檢查參數類型. 但它會檢查自己的內置函數參數格式和類型, 這不公平...對於自己寫的函數, 可以用內置函數isinstance實現參數類型檢查
1 if not isinstance(x, (int)):
2         raise TypeError('not int type')
  • 多返回值: return x, y, 當然本質是省略了括號的tuple
  • 多重賦值: x,y = y,x 要比三條語句執行快
  • 字符串連接: 和Java語法一樣, 使用join()要比"+"高效, "+"會創建一個新的字符串並復制內容
  • 延遲加載: ipmort在需要時再導入
  • 無限循環: while 1比while True高效, 因為while 1是單步運算
  • 列表生成式(list comprehension): 取代for和while更高效, 因為Python解釋器能夠在循環中發現它是一個可以預測的模式而被優化, 同時list更具可讀性. 和Java一樣, 可以在循環中節省一個額外的計數變量. e.g. evens = [i for i in range(10) if i%2 == 0] 要比 while i<10: if i%2 == 0: evens.append(i) i+=1 易讀高效
  • 生成器(generator): <Python 2.7 教程>里提到, 生成器保存的是算法, 每次調用next()時才會計算出下一個值, 返回后保存這次計算的位置. <Python性能雞湯>里也給出了逐個字節發送視頻流的例子: chunk=(1000*i for i in xrange(1000)), chunk.next(), chunk.next(). 從兩個例子中可以看出生成器的共性: 是一個表達式, 再調用的時候才執行一次, 再次調用時執行第二次...而構造生成器, 要么是一個xrange表達式, 要么是在函數里合適的位置加入yield, 保存當前狀態后返回本次計算結果
  • 查找效率: dict和set的查找很快, 因為是哈希表實現. list的實現是數組, 所以在list前插入效率不高, 因為list的后續下標不得不全部改變. 類比到Java里的集合框架: ArrayList數組線性表不適合在中間插入, 因為要改變后續元素下標; Java的Set和Python的set一樣都是元素不重復的, 而且由Hash實現. Java的集合框架太強大太繁雜, Python則簡化了很多, 記住dict, set, list, tuple目前就夠用了
  • 裝飾器: 目前只掌握了簡單修飾, 加參數修飾的用法

進程和線程的基礎知識

  • CPU執行代碼是順序執行, 單核CPU通過讓任務交替執行, "模擬"除了多任務並發執行. 真正的多任務並發, 是在多核CPU上, 每個CPU負責執行一個任務. 但實際任務數量遠多於CPU核心數量, 所以最終還是操作系統把多任務輪流調度到不同的核心上執行.
  • 進程/線程和物理內存(寄存器)/CPU的關聯: 函數調用, 會在棧中分配一塊空間, 存放局部變量和參數, 調用結束, 棧空間被釋放. 每個線程都有獨立的棧, 寄存器. 同一進程里的所有線程共享文件, 代碼和數據.
  • 進程獨立性: 以CPU中的程序計數器PC為例, 物理的PC只有一個, 但是每個進程有獨立的邏輯PC, 保存了程序運行中的值, 但CPU輪到該進程時, 就將邏輯PC值復制到物理PC.

Python跨平台支持多進程

  • Unix/Linux平台Python支持fork(): 父進程復制出一個子進程
  • Windows平台Python支持Process(target,args): "模擬出fork()的效果", 即父進程所有Python對象pickle后傳遞給子進程

Python進程間通信的數據交換方式

multiprocessing模塊提供的Queue, Pipes

Python多線程基礎知識

  • 標准庫提供了兩個模塊: thread和threading(推薦).
  • 任務進程都默認啟動一個叫做MainThread的主線程, 主線程可以啟動新的子線程.
  • 多線程的變量鎖: 高級語言的一條語句在CPU執行時是若干條語句, 計算中的結果會存入臨時變量中, 每個線程都有自己的臨時變量.
  • 獲取鎖后一定要釋放, 否則等待鎖的線程會一直阻塞下去, 成為死線程. Python中可以通過try...finally確保釋放.
  • 鎖的壞處: (1) 加鎖的代碼只能以單線程模式執行, 阻止了多線程並發, 降低效率; (2) 可能造成死鎖.

Python的GIL鎖導致多線程不能充分利用多核CPU

  • Python的解釋器有一個GIL(Global Interpreter Lock)鎖, 任何線程執行前, 先獲得GIL鎖, 每執行100行代碼, 解釋器會自動釋放GIL鎖, 讓別的線程有機會執行. 所以即使100個線程跑在100核CPU上, 也只能用到1個核.
  • GIL是Python解釋器設計的歷史遺留問題, 使得Python不能有效利用多核, 但可以通過多進程實現.
  • 多個Python進程有各自獨立的GIL鎖, 互不影響.
  • GIL: 只是CPython解釋器存在. "GIL會序列化你的所有線程". 可以使用線程來管理多個派生進程, 使這些進程獨立運行於Python代碼之外.

多線程下選擇加鎖的全局變量or不得不傳遞下去的局部變量?

加鎖和傳遞局部變量都是老方法了, 只是局部變量傳遞起來很麻煩. 如果遇到每個線程一個專屬對象, 不能使用全局變量的情況, 作者就想到用一個全局dict, 但是通過線程自身作為key獲得保存的對應的對象, 例如:

 1 # -*-coding:utf-8 -*-
 2 import time, threading
 3 
 4 global_dict = {}
 5 
 6 class Student(object):
 7     
 8     def __init__(self, name):
 9         self.name = name
10     
11     def return_name(self):
12         return self.name
13 
14 
15 def do_task_1():
16     std = Student('Elsa')
17     global_dict[threading.currentThread()] = std
18     std = global_dict[threading.current_thread()]
19     print 'thread %s, do_task_1() std.name = %s\n' % (threading.currentThread().name, std.return_name())
20 
21 def do_task_2():
22     std = Student('Anna')
23     global_dict[threading.currentThread()] = std
24     std = global_dict[threading.current_thread()]
25     print 'thread %s, do_task_2() std.name = %s\n' % (threading.currentThread().name, std.return_name())
26         
27 
28 if __name__ == '__main__':
29     print 'thread %s is running...' % threading.currentThread().name
30     t1 = threading.Thread(target=do_task_1, name='t1')
31     t2 = threading.Thread(target=do_task_2, name='t2')
32     t1.start()
33     t2.start()
34     t1.join()
35     t2.join()
36     print 'thread %s ended.' % threading.currentThread().name

global_dict = {}作為一個全局變量, 保存了線程和它的專屬對象. 運行結果:

D:\WorkspaceVSCode>python multiprocess.py
thread MainThread is running...
thread t1, do_task_1() std.name = Elsa

thread t2, do_task_2() std.name = Anna

thread MainThread ended.

然后Python又簡化了對每個線程專屬對象的存儲和訪問, 也就是封裝了上面的global_dict = {}. 可以理解成, local_school = threading.local()是一個全局變量, 但它里面保存的是各個線程的局部變量, 還不用管理線程鎖了, 感覺很方便的樣子.

多線程時使用ThreadLocal用全局變量保存線程的局部變量

常用於為每個線程綁定一個數據庫連接, HTTP請求, 用戶身份信息等.

進程, 線程和計算密集, IO密集型任務

  • 多進程更穩定, 系統的子進程互相內存獨立, 子進程崩潰不會影響其它進程(主進程除外)
  • 多線程更高效, 但由於所有線程共享進程的內存, 一個崩潰會引發系統強制結束整個進程
  • 計算密集型任務, 如視頻高清解碼, 主要開銷在CPU計算, 任務數=核心數最好
  • IO密集型任務, 如網絡, 磁盤IO等, CPU消耗很少, 大部分時間都是在等待IO操作完成, 任務越多, CPU效率越高(有限度)

CPU和IO操作間巨大的速度差異催生了異步IO

既然一個任務在執行時大部分時間都是CPU等待IO, 單進程單線程並不能充分的利用CPU資源, 在CPU等待IO的時候, 不放用空閑時間去執行CPU計算任務. "現代操作系統對IO操作已經做出了巨大改進, 最大的特點就是支持異步IO. 如果充分利用操作系統提供的異步IO支持, 就可以用單進程單線程模型來執行多任務." (Android里的AsyncTask就是一種異步, 但是它本質是另外起了一個小線程, 所以異步就是多線程?)

分布式多進程, 還能夠通過網絡共享任務隊列

  • Python的multiprocessing模塊, 支持多進程, 還支持多進程分布到多台設備
  • 原理就是將多進程共享的Queue通過網絡暴露出去, 其它機器就可以訪問Queue了(就像remoterobotserver通過網絡暴露出robot的庫一樣)

性能調優工具

python -m cProfile mypython.py

Native+包裝器

Python程序可以直接加載已變成的二進制庫(.dll或.so)文件, 不用編寫C/C++代碼(直接源碼+NDK)或構建依賴(Gradle中添加.so庫構建依賴)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM