庫和擴充套件 FAQ¶
通用庫問題¶
如何找到一個模組或應用程式來執行任務 X?¶
檢視庫參考,看看是否有相關的標準庫模組。(最終你會了解標準庫中有什麼,並能夠跳過這一步。)
對於第三方包,請搜尋 Python Package Index 或嘗試 Google 或其他網路搜尋引擎。搜尋“Python”加上一兩個與你感興趣的主題相關的關鍵詞通常會找到有用的東西。
math.py (socket.py, regex.py, etc.) 的原始檔在哪裡?¶
如果你找不到模組的原始檔,它可能是一個內建的或動態載入的模組,用 C、C++ 或其他編譯語言實現。在這種情況下,你可能沒有原始檔,或者它可能像 mathmodule.c
一樣,在某個 C 源目錄中(不在 Python 路徑上)。
Python 中有 (至少) 三種模組
用 Python 編寫的模組 (.py);
用 C 編寫並動態載入的模組 (.dll, .pyd, .so, .sl, etc);
用 C 編寫並與直譯器連結的模組;要獲取這些模組的列表,請鍵入
import sys print(sys.builtin_module_names)
如何在 Unix 上使 Python 指令碼可執行?¶
你需要做兩件事:指令碼檔案的模式必須是可執行的,並且第一行必須以 #!
開頭,後面跟著 Python 直譯器的路徑。
第一步是透過執行 chmod +x scriptfile
或 chmod 755 scriptfile
完成的。
第二步可以透過多種方式完成。最直接的方法是寫入
#!/usr/local/bin/python
作為檔案的第一行,使用 Python 直譯器安裝在你平臺上的路徑。
如果你希望指令碼獨立於 Python 直譯器的位置,你可以使用 env 程式。幾乎所有的 Unix 變體都支援以下內容,假設 Python 直譯器位於使用者 PATH
中的目錄中
#!/usr/bin/env python
CGI 指令碼不要這樣做。CGI 指令碼的 PATH
變數通常非常少,所以你需要使用直譯器的實際絕對路徑。
偶爾,使用者的環境太滿,導致 /usr/bin/env 程式失敗;或者根本沒有 env 程式。在這種情況下,你可以嘗試以下技巧(由 Alex Rezinsky 提供)
#! /bin/sh
""":"
exec python $0 ${1+"$@"}
"""
次要的缺點是這定義了指令碼的 __doc__ 字串。但是,你可以透過新增
__doc__ = """...Whatever..."""
Python 有 curses/termcap 包嗎?¶
對於 Unix 變體:標準 Python 原始碼分發在 Modules 子目錄中包含一個 curses 模組,但預設情況下不編譯。(請注意,這在 Windows 分發版中不可用——Windows 沒有 curses 模組。)
curses
模組支援基本的 curses 功能以及 ncurses 和 SYSV curses 的許多附加功能,例如顏色、替代字元集支援、襯墊和滑鼠支援。這意味著該模組與僅具有 BSD curses 的作業系統不相容,但目前似乎沒有任何維護的作業系統屬於此類別。
Python 中有 C 語言 onexit() 的等價物嗎?¶
atexit
模組提供了一個類似於 C 語言 onexit()
的註冊函式。
為什麼我的訊號處理程式不起作用?¶
最常見的問題是訊號處理程式宣告的引數列表不正確。它被呼叫為
handler(signum, frame)
所以它應該宣告兩個引數
def handler(signum, frame):
...
常見任務¶
如何測試 Python 程式或元件?¶
Python 附帶兩個測試框架。doctest
模組查詢模組文件字串中的示例並執行它們,將輸出與文件字串中給出的預期輸出進行比較。
unittest
模組是一個更高階的測試框架,模仿了 Java 和 Smalltalk 測試框架。
為了使測試更容易,你應該在你的程式中使用良好的模組化設計。你的程式應該將幾乎所有的功能封裝在函式或類方法中——這有時會產生令人驚訝和愉快的副作用,即使程式執行得更快(因為區域性變數訪問比全域性訪問更快)。此外,程式應該避免依賴於改變全域性變數,因為這使得測試變得更加困難。
你的程式的“全域性主邏輯”可能很簡單,如下所示
if __name__ == "__main__":
main_logic()
在程式主模組的底部。
一旦你的程式被組織成一個可處理的函式和類行為集合,你應該編寫測試函式來練習這些行為。一個自動化測試序列的測試套件可以與每個模組關聯起來。這聽起來工作量很大,但由於 Python 如此簡潔和靈活,它出奇地容易。你可以透過與“生產程式碼”並行編寫測試函式來使編碼更加愉快和有趣,因為這使得更容易發現錯誤甚至設計缺陷。
不打算作為程式主模組的“支援模組”可能包含模組的自測試。
if __name__ == "__main__":
self_test()
即使是與複雜外部介面互動的程式,在外部介面不可用時也可以透過使用 Python 中實現的“模擬”介面進行測試。
如何從文件字串建立文件?¶
pydoc
模組可以從你的 Python 原始碼中的文件字串建立 HTML。另一個純粹從文件字串建立 API 文件的替代方案是 epydoc。Sphinx 也可以包含文件字串內容。
如何一次獲取一個按鍵?¶
對於 Unix 變體有幾種解決方案。使用 curses 很容易做到這一點,但 curses 是一個相當大的模組,需要學習。
執行緒¶
如何使用執行緒程式設計?¶
請務必使用 threading
模組,而不是 _thread
模組。threading
模組在 _thread
模組提供的低階原語之上構建了方便的抽象。
我的所有執行緒似乎都沒有執行:為什麼?¶
一旦主執行緒退出,所有執行緒都會被終止。你的主執行緒執行得太快,導致執行緒沒有時間做任何工作。
一個簡單的解決方法是在程式末尾新增一個足夠長的睡眠,以便所有執行緒都能完成工作。
import threading, time
def thread_task(name, n):
for i in range(n):
print(name, i)
for i in range(10):
T = threading.Thread(target=thread_task, args=(str(i), i))
T.start()
time.sleep(10) # <---------------------------!
但現在 (在許多平臺上) 執行緒不是並行執行,而是似乎一個接一個地按順序執行!原因是作業系統執行緒排程程式在先前的執行緒被阻塞之前不會啟動新執行緒。
一個簡單的解決方法是在執行函式的開頭新增一個微小的睡眠
def thread_task(name, n):
time.sleep(0.001) # <--------------------!
for i in range(n):
print(name, i)
for i in range(10):
T = threading.Thread(target=thread_task, args=(str(i), i))
T.start()
time.sleep(10)
與其嘗試猜測 time.sleep()
的合適延遲值,不如使用某種訊號量機制。一個想法是使用 queue
模組建立一個佇列物件,讓每個執行緒在完成時向佇列新增一個令牌,並讓主執行緒從佇列中讀取與執行緒數相同數量的令牌。
如何將工作分配給一堆工作執行緒?¶
最簡單的方法是使用 concurrent.futures
模組,特別是 ThreadPoolExecutor
類。
或者,如果你想對排程演算法進行精細控制,你可以手動編寫自己的邏輯。使用 queue
模組建立一個包含作業列表的佇列。Queue
類維護一個物件列表,並具有一個將項新增到佇列的 .put(obj)
方法和一個返回它們的 .get()
方法。該類將負責必要的鎖定,以確保每個作業只分發一次。
這是一個簡單的例子
import threading, queue, time
# The worker thread gets jobs off the queue. When the queue is empty, it
# assumes there will be no more work and exits.
# (Realistically workers will run until terminated.)
def worker():
print('Running worker')
time.sleep(0.1)
while True:
try:
arg = q.get(block=False)
except queue.Empty:
print('Worker', threading.current_thread(), end=' ')
print('queue empty')
break
else:
print('Worker', threading.current_thread(), end=' ')
print('running with argument', arg)
time.sleep(0.5)
# Create queue
q = queue.Queue()
# Start a pool of 5 workers
for i in range(5):
t = threading.Thread(target=worker, name='worker %i' % (i+1))
t.start()
# Begin adding work to the queue
for i in range(50):
q.put(i)
# Give threads time to run
print('Main thread sleeping')
time.sleep(5)
執行時,這將產生以下輸出
Running worker
Running worker
Running worker
Running worker
Running worker
Main thread sleeping
Worker <Thread(worker 1, started 130283832797456)> running with argument 0
Worker <Thread(worker 2, started 130283824404752)> running with argument 1
Worker <Thread(worker 3, started 130283816012048)> running with argument 2
Worker <Thread(worker 4, started 130283807619344)> running with argument 3
Worker <Thread(worker 5, started 130283799226640)> running with argument 4
Worker <Thread(worker 1, started 130283832797456)> running with argument 5
...
有關更多詳細資訊,請查閱模組的文件;Queue
類提供了一個功能豐富的介面。
哪些全域性值變異是執行緒安全的?¶
內部使用全域性直譯器鎖(GIL)來確保一次只有一個執行緒在 Python VM 中執行。通常,Python 只在位元組碼指令之間進行執行緒切換;切換的頻率可以透過 sys.setswitchinterval()
設定。因此,從 Python 程式的角度來看,每條位元組碼指令以及從每條指令到達的所有 C 實現程式碼都是原子的。
理論上,這意味著精確的核算需要對 PVM 位元組碼實現有精確的理解。實際上,這意味著對內建資料型別(整數、列表、字典等)的共享變數進行“看起來是原子”的操作確實是原子的。
例如,以下操作都是原子性的(L、L1、L2 是列表,D、D1、D2 是字典,x、y 是物件,i、j 是整數)
L.append(x)
L1.extend(L2)
x = L[i]
x = L.pop()
L1[i:j] = L2
L.sort()
x = y
x.field = y
D[x] = y
D1.update(D2)
D.keys()
這些不是
i = i+1
L.append(L[-1])
L[i] = L[j]
D[x] = D[x] + 1
替換其他物件的操作可能會在它們的引用計數達到零時呼叫這些其他物件的 __del__()
方法,這可能會影響事情。對於字典和列表的批次更新尤其如此。有疑問時,請使用互斥鎖!
我們不能擺脫全域性直譯器鎖嗎?¶
全域性直譯器鎖 (GIL) 通常被視為 Python 在高階多處理器伺服器機器上部署的障礙,因為多執行緒 Python 程式實際上只使用一個 CPU,因為它堅持(幾乎)所有 Python 程式碼只能在持有 GIL 的情況下執行。
經 PEP 703 批准,目前正在進行從 Python 的 CPython 實現中移除 GIL 的工作。最初,它將作為構建直譯器時的可選編譯器標誌來實現,因此將提供帶和不帶 GIL 的獨立構建。從長遠來看,一旦完全瞭解移除 GIL 的效能影響,希望能夠確定一個單一的構建。Python 3.13 可能是第一個包含此工作的版本,儘管它在該版本中可能不完全功能。
當前移除 GIL 的工作基於 Sam Gross 移除了 GIL 的 Python 3.9 分支。在此之前,在 Python 1.5 時代,Greg Stein 實際上實現了一套全面的補丁(“自由執行緒”補丁),該補丁移除了 GIL 並將其替換為細粒度鎖定。Adam Olsen 在他的 python-safethread 專案中也做了類似的實驗。不幸的是,這兩個早期的實驗都顯示出單執行緒效能急劇下降(至少慢了 30%),原因是需要大量細粒度鎖定來彌補 GIL 的移除。Python 3.9 分支是第一次在可接受的效能影響下移除 GIL 的嘗試。
當前 Python 版本中 GIL 的存在並不意味著你不能在多 CPU 機器上很好地利用 Python!你只需要創造性地在多個程序而不是多個執行緒之間劃分工作。新的 concurrent.futures
模組中的 ProcessPoolExecutor
類提供了一種簡單的方法來做到這一點;如果你想對任務排程有更多控制,multiprocessing
模組提供了更低階的 API。
明智地使用 C 擴充套件也有幫助;如果你使用 C 擴充套件來執行耗時任務,擴充套件可以在執行執行緒處於 C 程式碼中時釋放 GIL,並允許其他執行緒完成一些工作。一些標準庫模組,如 zlib
和 hashlib
已經這樣做了。
另一種減少 GIL 影響的方法是使 GIL 成為每個直譯器狀態鎖,而不是真正的全域性鎖。這首次在 Python 3.12 中實現,並在 C API 中可用。預計在 Python 3.13 中將提供一個 Python 介面。目前的主要限制可能是第三方擴充套件模組,因為這些模組必須在考慮多個直譯器的情況下編寫才能使用,因此許多較舊的擴充套件模組將無法使用。
輸入和輸出¶
如何刪除檔案?(以及其他檔案問題……)¶
使用 os.remove(filename)
或 os.unlink(filename)
;有關文件,請參見 os
模組。這兩個函式是相同的;unlink()
只是 Unix 系統呼叫此函式的名稱。
要刪除目錄,請使用 os.rmdir()
;使用 os.mkdir()
建立目錄。os.makedirs(path)
將在 path
中建立所有不存在的中間目錄。os.removedirs(path)
將刪除中間目錄,只要它們是空的;如果你想刪除整個目錄樹及其內容,請使用 shutil.rmtree()
。
要重新命名檔案,請使用 os.rename(old_path, new_path)
。
要截斷檔案,請使用 f = open(filename, "rb+")
開啟它,然後使用 f.truncate(offset)
;offset 預設為當前查詢位置。對於使用 os.open()
開啟的檔案,也有 os.ftruncate(fd, offset)
,其中 fd 是檔案描述符(一個小的整數)。
shutil
模組還包含許多用於檔案操作的函式,包括 copyfile()
、copytree()
和 rmtree()
。
如何複製檔案?¶
shutil
模組包含一個 copyfile()
函式。請注意,在 Windows NTFS 捲上,它不復制 備用資料流,也不復制 macOS HFS+ 捲上的 資源 fork,儘管兩者現在都很少使用。它也不復制檔案許可權和元資料,儘管使用 shutil.copy2()
代替會保留大部分(但不是全部)許可權和元資料。
如何讀取 (或寫入) 二進位制資料?¶
要讀取或寫入複雜的二進位制資料格式,最好使用 struct
模組。它允許你獲取包含二進位制資料(通常是數字)的字串並將其轉換為 Python 物件;反之亦然。
例如,以下程式碼從檔案中讀取兩個 2 位元組整數和一個 4 位元組整數,採用大端格式
import struct
with open(filename, "rb") as f:
s = f.read(8)
x, y, z = struct.unpack(">hhl", s)
格式字串中的 '>' 強制使用大端資料;字母 'h' 讀取一個“短整數”(2 位元組),'l' 讀取一個“長整數”(4 位元組)。
對於更規則的資料(例如,一個同構的整數或浮點數列表),你還可以使用 array
模組。
我似乎無法在用 os.popen() 建立的管道上使用 os.read();為什麼?¶
os.read()
是一個低階函式,它接受一個檔案描述符,一個表示已開啟檔案的小整數。os.popen()
建立一個高階檔案物件,與內建的 open()
函式返回的型別相同。因此,要從使用 os.popen()
建立的管道 p 中讀取 n 位元組,你需要使用 p.read(n)
。
如何訪問序列 (RS232) 埠?¶
對於 Win32、OSX、Linux、BSD、Jython、IronPython
對於 Unix,請參閱 Mitch Chapman 的 Usenet 帖子
為什麼關閉 sys.stdout (stdin, stderr) 並不能真正關閉它?¶
Python 檔案物件 是低階 C 檔案描述符的高階抽象層。
對於透過內建的 open()
函式在 Python 中建立的大多數檔案物件,f.close()
將 Python 檔案物件標記為從 Python 的角度看已關閉,並且還安排關閉底層的 C 檔案描述符。當 f
變成垃圾時,這也會在 f
的解構函式中自動發生。
但是 stdin、stdout 和 stderr 在 Python 中被特殊對待,因為 C 也賦予它們特殊的地位。執行 sys.stdout.close()
將 Python 級檔案物件標記為已關閉,但不會關閉關聯的 C 檔案描述符。
要關閉這三個中的一個的底層 C 檔案描述符,你應該首先確定這確實是你想要做的事情(例如,你可能會混淆試圖進行 I/O 的擴充套件模組)。如果是這樣,請使用 os.close()
os.close(stdin.fileno())
os.close(stdout.fileno())
os.close(stderr.fileno())
或者你可以分別使用數字常量 0、1 和 2。
網路/網際網路程式設計¶
Python 有哪些 WWW 工具?¶
請參閱庫參考手冊中標題為Internet 協議和支援和Internet 資料處理的章節。Python 有許多模組可以幫助你構建伺服器端和客戶端 Web 系統。
Paul Boddie 在 https://wiki.python.org/moin/WebProgramming 維護了一個可用框架的摘要。
我應該使用哪個模組來幫助生成 HTML?¶
你可以在 Web Programming wiki 頁面上找到有用的連結集合。
如何從 Python 指令碼傳送郵件?¶
使用標準庫模組 smtplib
。
這是一個使用它的非常簡單的互動式郵件傳送器。此方法適用於任何支援 SMTP 監聽器的主機。
import sys, smtplib
fromaddr = input("From: ")
toaddrs = input("To: ").split(',')
print("Enter message, end with ^D:")
msg = ''
while True:
line = sys.stdin.readline()
if not line:
break
msg += line
# The actual mail send
server = smtplib.SMTP('localhost')
server.sendmail(fromaddr, toaddrs, msg)
server.quit()
僅限 Unix 的替代方法使用 sendmail。sendmail 程式的路徑在不同系統之間有所不同;有時是 /usr/lib/sendmail
,有時是 /usr/sbin/sendmail
。sendmail 手冊頁會幫助你。以下是一些示例程式碼
import os
SENDMAIL = "/usr/sbin/sendmail" # sendmail location
p = os.popen("%s -t -i" % SENDMAIL, "w")
p.write("To: receiver@example.com\n")
p.write("Subject: test\n")
p.write("\n") # blank line separating headers from body
p.write("Some text\n")
p.write("some more text\n")
sts = p.close()
if sts != 0:
print("Sendmail exit status", sts)
如何避免在套接字的 connect() 方法中阻塞?¶
select
模組通常用於協助套接字的非同步 I/O。
為了防止 TCP 連線阻塞,你可以將套接字設定為非阻塞模式。然後當你執行 connect()
時,你將立即連線(不太可能)或者會得到一個包含錯誤號作為 .errno
的異常。errno.EINPROGRESS
表示連線正在進行中,但尚未完成。不同的作業系統會返回不同的值,所以你必須檢查你的系統上返回的值。
你可以使用 connect_ex()
方法來避免建立異常。它只會返回 errno 值。要輪詢,你可以在稍後再次呼叫 connect_ex()
—— 0
或 errno.EISCONN
表示你已連線 —— 或者你可以將此套接字傳遞給 select.select()
以檢查它是否可寫。
資料庫¶
Python 中有資料庫包的介面嗎?¶
有。
標準 Python 還包括對基於磁碟的雜湊(例如 DBM
和 GDBM
)的介面。此外,還有 sqlite3
模組,它提供了一個輕量級的基於磁碟的關係資料庫。
大多數關係資料庫都支援。有關詳細資訊,請參閱 DatabaseProgramming wiki 頁面。
如何在 Python 中實現持久化物件?¶
pickle
庫模組以非常通用的方式解決了這個問題(儘管你仍然無法儲存開啟的檔案、套接字或視窗之類的東西),而 shelve
庫模組使用 pickle 和 (g)dbm 建立包含任意 Python 物件的持久對映。
數學和數值計算¶
如何在 Python 中生成隨機數?¶
標準模組 random
實現了一個隨機數生成器。用法很簡單
import random
random.random()
這會返回一個範圍在 [0, 1) 的隨機浮點數。
此模組中還有許多其他專用生成器,例如
randrange(a, b)
從範圍 [a, b) 中選擇一個整數。uniform(a, b)
從範圍 [a, b) 中選擇一個浮點數。normalvariate(mean, sdev)
取樣正態(高斯)分佈。
一些更高階的函式直接對序列進行操作,例如
choice(S)
從給定序列中選擇一個隨機元素。shuffle(L)
在原地打亂列表,即隨機排列它。
還有一個 Random
類,你可以例項化它來建立獨立的多個隨機數生成器。