庫和擴充套件常見問題解答

常規庫問題

如何查詢執行任務 X 的模組或應用程式?

檢視 庫參考 以檢視是否有相關的標準庫模組。(最終您將瞭解標準庫中的內容,並且能夠跳過此步驟。)

對於第三方軟體包,請搜尋 Python 包索引 或嘗試 Google 或其他 Web 搜尋引擎。搜尋“Python”加上一個或兩個您感興趣的主題的關鍵字通常會找到一些有用的東西。

math.py (socket.py, regex.py 等) 原始檔在哪裡?

如果您找不到模組的原始檔,它可能是一個用 C、C++ 或其他編譯語言實現的內建或動態載入的模組。在這種情況下,您可能沒有原始檔,或者它可能是像 mathmodule.c 這樣的檔案,位於 C 源目錄中的某個位置(而不是在 Python 路徑上)。

Python 中至少有三種模組

  1. 用 Python 編寫的模組 (.py);

  2. 用 C 編寫並動態載入的模組 (.dll, .pyd, .so, .sl 等);

  3. 用 C 編寫並與直譯器連結的模組;要獲取這些模組的列表,請鍵入

    import sys
    print(sys.builtin_module_names)
    

如何在 Unix 上使 Python 指令碼可執行?

您需要做兩件事:指令碼檔案的模式必須是可執行的,並且第一行必須以 #! 開頭,後跟 Python 直譯器的路徑。

第一步是透過執行 chmod +x scriptfilechmod 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 模組提供了一個 register 函式,該函式類似於 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 文件的另一種方法是 epydocSphinx 也可以包含文件字串內容。

如何一次獲取單個按鍵?

對於 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)  # <---------------------------!

但是現在(在許多平臺上),執行緒不是並行執行,而是看起來順序執行,一次一個!原因是作業系統執行緒排程器在之前的執行緒被阻塞之前不會啟動新執行緒。

一個簡單的解決方法是在run函式的開頭新增一個很小的休眠。

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 獲得批准後,目前正在進行從 CPython Python 實現中刪除 GIL 的工作。最初,它將在構建直譯器時作為可選的編譯器標誌實現,因此將提供帶有和不帶有 GIL 的單獨構建。長期來看,希望在充分了解刪除 GIL 的效能影響後,確定單個構建。Python 3.13 可能是第一個包含這項工作的版本,儘管它在這個版本中可能並不完全可用。

當前刪除 GIL 的工作基於 Sam Gross 的 刪除了 GIL 的 Python 3.9 分支。在此之前,在 Python 1.5 的時代,Greg Stein 實際上實現了一個全面的補丁集(“自由執行緒”補丁),該補丁集刪除了 GIL 並將其替換為細粒度鎖定。Adam Olsen 在他的 python-safethread 專案中進行了類似的實驗。不幸的是,由於為補償刪除 GIL 所需的大量細粒度鎖定,這兩個早期實驗都表現出單執行緒效能的急劇下降(至少慢 30%)。Python 3.9 分支是第一次嘗試在可接受的效能影響下刪除 GIL。

在當前 Python 版本中存在 GIL 並不意味著你不能在多 CPU 機器上很好地使用 Python!你只需要在多個程序而不是多個執行緒之間創造性地劃分工作。新的 concurrent.futures 模組中的 ProcessPoolExecutor 類提供了一種簡單的方法來實現這一點;如果你想更好地控制任務的排程,multiprocessing 模組提供了更底層的 API。

合理使用 C 擴充套件也會有所幫助;如果你使用 C 擴充套件來執行耗時的任務,則當執行執行緒位於 C 程式碼中時,擴充套件可以釋放 GIL,並允許其他執行緒完成一些工作。一些標準庫模組(如 zlibhashlib)已經這樣做了。

減少 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 是檔案描述符(一個小整數)。

要讀取或寫入複雜的二進位制資料格式,最好使用

例如,以下程式碼從檔案中讀取兩個 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 位元組)。

對於更規則的資料(例如,整數或浮點數的同構列表),你也可以使用

注意

要讀取和寫入二進位制資料,必須以二進位制模式開啟檔案(此處,將 "rb" 傳遞給

對於 Win32、OSX、Linux、BSD、Jython、IronPython

對於 Unix,請參閱 Mitch Chapman 的 Usenet 帖子

Python

對於你透過內建的

但是,由於 C 也賦予了 stdin、stdout 和 stderr 特殊狀態,Python 會對它們進行特殊處理。執行 sys.stdout.close() 會將 Python 級檔案物件標記為已關閉,但不會關閉關聯的 C 檔案描述符。

要關閉這三個中的一個的底層 C 檔案描述符,你首先應確保這確實是你想要做的(例如,你可能會混淆試圖進行 I/O 的擴充套件模組)。如果確實要關閉,請使用

os.close(stdin.fileno())
os.close(stdout.fileno())
os.close(stderr.fileno())

或者你可以分別使用數字常量 0、1 和 2。

請參閱《庫參考手冊》中標題為

Paul Boddie 在

你可以在

使用標準庫模組

這是一個非常簡單的互動式郵件傳送器,它使用該模組。此方法適用於任何支援 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)

要防止 TCP 連線阻塞,可以將套接字設定為非阻塞模式。然後,當你執行

你可以使用

注意

是的。

標準 Python 也包含與基於磁碟的雜湊介面,例如 DBMGDBM。 還有 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 類,你可以例項化它來建立獨立的多個隨機數生成器。