代碼庫和插件 FAQ?

通用的代碼庫問題?

如何找到可以用來做 XXX 的模塊或應用??

代碼庫參考 中查找是否有適合的標準庫模塊。(如果你已經了解標準庫的內容,可以跳過這一步)

對于第三方軟件包,請搜索 Python Package Index 或是 Google 等其他搜索引擎。用“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 scriptfile 或是 chmod 755 scriptfile 做到。

第二點有很多種做法,最直接的方式是:

#!/usr/local/bin/python

在文件第一行,使用你所在平臺上的 Python 解釋器的路徑。

如果你希望腳本不依賴 Python 解釋器的具體路徑,你也可以使用 env 程序。假設你的 Python 解釋器所在目錄已經添加到了 PATH 環境變量中,幾乎所有的類 Unix 系統都支持下面的寫法:

#!/usr/bin/env python

不要 在 CGI 腳本中這樣做。CGI 腳本的 PATH 環境變量通常會非常精簡,所以你必須使用解釋器的完整絕對路徑。

有時候,用戶的環境變量如果太長,可能會導致 /usr/bin/env 執行失敗;又或者甚至根本就不存在 env 程序。在這種情況下,你可以嘗試使用下面的 hack 方法(來自 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 模塊的操作系統,但是目前仍在維護的系統應該都不會存在這種情況。

對于 Windows 平臺:使用 consolelib 模塊.

Python 中存在類似 C 的 onexit() 函數的東西嗎??

atexit 模塊提供了一個與 C 的 onexit() 函數類似的注冊函數。

為什么我的信號處理函數不能工作??

最常見的問題是信號處理函數沒有正確定義參數列表。它會被這樣調用:

handler(signum, frame)

因此函數應該定義兩個參數:

def handler(signum, frame):
    ...

通用任務?

怎樣測試 Python 程序或組件??

Python 帶有兩個測試框架。doctest 模塊從模塊的 docstring 中尋找示例并執行,對比輸出是否與 docstring 中給出的是否一致。

unittest 模塊是一個模仿 Java 和 Smalltalk 測試框架的更棒的測試框架。

為了使測試更容易,你應該在程序中使用良好的模塊化設計。程序中的絕大多數功能都應該用函數或類方法封裝 —— 有時這樣做會有額外驚喜,程序會運行得更快(因為局部變量比全局變量訪問要快)。除此之外,程序應該避免依賴可變的局部變量,這會使得測試困難許多。

程序的“全局主邏輯”應該盡量簡單:

if __name__ == "__main__":
    main_logic()

并放置在程序主模塊的最后面。

一旦你的程序已經用函數和類完善地組織起來,你就應該編寫測試函數來測試其行為。可以使用自動執行一系列測試函數的測試集與每個模塊進行關聯。聽起來似乎需要大量的工作,但是因為 Python 非常簡潔和靈活,所以實際上會相當簡單。在編寫“生產代碼”的同時別忘了也要編寫測試函數,你會發現編程會變得更愉快、更有趣,因為這樣會使得發現 bug 和設計缺陷更加容易。

程序主模塊之外的其他“輔助模塊”中可以增加自測試的入口。

if __name__ == "__main__":
    self_test()

通過使用 Python 實現的“假”接口,即使是需要與復雜的外部接口交互的程序也可以在外部接口不可用時進行測試。

怎樣用 docstring 創建文檔??

pydoc 模塊可以用 Python 源碼中的 docstring 創建 HTML 文件。也可以使用 epydoc 來只通過 docstring 創建 API 文檔。Sphinx 也可以引入 docstring 的內容。

怎樣一次只獲取一個按鍵??

在類 Unix 系統中有多種方案。最直接的方法是使用 curses,但是 curses 模塊太大了,難以學習。

線程相關?

程序中怎樣使用線程??

一定要使用 threading 模塊,不要使用 _thread 模塊。threading 模塊對 _thread 模塊提供的底層線程原語做了更易用的抽象。

Aahz 的非常實用的 threading 教程中有一些幻燈片;可以參閱 http://www.pythoncraft.com/OSCON2001/

我的線程都沒有運行,為什么??

一旦主線程退出,所有的子線程都會被殺掉。你的主線程運行得太快了,子線程還沒來得及工作。

簡單的解決方法是在程序中加一個時間足夠長的 sleep,讓子線程能夠完成運行。

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

但目前(在許多平臺上)線程不是并行運行的,而是按順序依次執行!原因是系統線程調度器在前一個線程阻塞之前不會啟動新線程。

簡單的解決方法是在運行函數的開始處加一個時間很短的 sleep。

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 模塊創建一個 queue 對象,讓每一個線程在運行結束時 append 一個令牌到 queue 對象中,主線程中從 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.currentThread(), end=' ')
            print('queue empty')
            break
        else:
            print('Worker', threading.currentThread(), 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 類提供了多種接口。

怎樣修改全局變量是線程安全的??

Python VM 內部會使用 global interpreter lock (GIL)來確保同一時間只有一個線程運行。通常 Python 只會在字節碼指令之間切換線程;切換的頻率可以通過設置 sys.setswitchinterval() 指定。從 Python 程序的角度來看,每一條字節碼指令以及每一條指令對應的 C 代碼實現都是原子的。

理論上說,具體的結果要看具體的 PVM 字節碼實現對指令的解釋。而實際上,對內建類型(int,list,dict 等)的共享變量的“類原子”操作都是原子的。

舉例來說,下面的操作是原子的(L、L1、L2 是列表,D、D1、D2 是字典,x、y 是對象,i,j 是 int 變量):

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

覆蓋其他對象的操作會在其他對象的引用計數變成 0 時觸發其 __del__() 方法,這可能會產生一些影響。對字典和列表進行大量操作時尤其如此。如果有疑問的話,使用互斥鎖!

不能刪除全局解釋器鎖嗎??

global interpreter lock (GIL)通常被視為 Python 在高端多核服務器上開發時的阻力,因為(幾乎)所有 Python 代碼只有在獲取到 GIL 時才能運行,所以多線程的 Python 程序只能有效地使用一個 CPU。

在 Python 1.5 時代,Greg Stein 開發了一個完整的補丁包(“free threadings” 補丁),移除了 GIL,并用粒度更合適的鎖來代替。Adam Olsen 最近也在他的 python-safethread 項目里做了類似的實驗。不幸的是,由于為了移除 GIL 而使用了大量細粒度的鎖,這兩個實驗在單線程測試中的性能都有明顯的下降(至少慢 30%)。

但這并意味著你不能在多核機器上很好地使用 Python!你只需將任務劃分為多*進程*,而不是多*線程*。新的 concurrent.futures 模塊中的 ProcessPoolExecutor 類提供了一個簡單的方法;如果你想對任務分發做更多控制,可以使用 multiprocessing 模塊提供的底層 API。

恰當地使用 C 拓展也很有用;使用 C 拓展處理耗時較久的任務時,拓展可以在線程執行 C 代碼時釋放 GIL,讓其他線程執行。zlibhashlib 等標準庫模塊已經這樣做了。

也有建議說 GIL 應該是解釋器狀態鎖,而不是完全的全局鎖;解釋器不應該共享對象。不幸的是,這也不可能發生。由于目前許多對象的實現都有全局的狀態,因此這是一個艱巨的工作。舉例來說,小整型數和短字符串會緩存起來,這些緩存將不得不移動到解釋器狀態中。其他對象類型都有自己的自由變量列表,這些自由變量列表也必須移動到解釋器狀態中。等等。

我甚至懷疑這些工作是否可能在有限的時間內完成,因為同樣的問題在第三方拓展中也會存在。第三方拓展編寫的速度可比你將它們轉換為把全局狀態存入解釋器狀態中的速度快得多。

最后,假設多個解釋器不共享任何狀態,那么這樣做比每個進程一個解釋器好在哪里呢?

輸入輸出?

怎樣刪除文件?(以及其他文件相關的問題……)?

使用 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() 函數。注意在 MacOS 9 中不會復制 resource fork 和 Finder info。

怎樣讀取(或寫入)二進制數據??

要讀寫復雜的二進制數據格式,最好使用 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 模塊。

注解

要讀寫二進制數據的話,需要強制以二進制模式打開文件(這里為 open() 函數傳入 "rb")。如果(默認)傳入 "r" 的話,文件會以文本模式打開,f.read() 會返回 str 對象,而不是 bytes 對象。

似乎 os.popen() 創建的管道不能使用 os.read(),這是為什么??

os.read() 是一個底層函數,它接收的是文件描述符 —— 用小整型數表示的打開的文件。os.popen() 創建的是一個高級文件對象,和內建的 open() 方法返回的類型一樣。因此,如果要從 os.popen() 創建的管道 p 中讀取 n 個字節的話,你應該使用 p.read(n)

怎樣訪問(RS232)串口??

對于 Win32,POSIX(Linux,BSD 等),Jython:

對于 Unix,查看 Mitch Chapman 發布的帖子:

為什么關閉 sys.stdout(stdin,stderr)并不會真正關掉它??

Python 文件對象 是一個對底層 C 文件描述符的高層抽象。

對于在 Python 中通過內建的 open() 函數創建的多數文件對象來說,f.close() 從 Python 的角度將其標記為已關閉,并且會關閉底層的 C 文件描述符。在 f 被垃圾回收的時候,析構函數中也會自動處理。

但由于 stdin,stdout 和 stderr 在 C 中的特殊地位,在 Python 中也會對它們做特殊處理。運行 sys.stdout.close() 會將 Python 的文件對象標記為已關閉,但是*不會*關閉與之關聯的文件描述符。

要關閉這三者的 C 文件描述符的話,首先你應該確認確實需要關閉它(比如,這可能會影響到處理 I/O 的拓展)。如果確實需要這么做的話,使用 os.close()

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

或者也可以使用常量 0,1,2 代替。

網絡 / Internet 編程?

Python 中的 WWW 工具是什么??

參閱代碼庫參考手冊中 互聯網協議和支持互聯網數據處理 這兩章的內容。Python 有大量模塊來幫助你構建服務端和客戶端 web 系統。

Paul Boddie 維護了一份可用框架的概覽,見 https://wiki.python.org/moin/WebProgramming

Cameron Laird 維護了一份關于 Python web 技術的實用網頁的集合,見 http://phaseit.net/claird/comp.lang.python/web_python

怎樣模擬發送 CGI 表單(METHOD=POST)??

我需要通過 POST 表單獲取網頁,有什么代碼能簡單做到嗎?

是的,這里有一個使用 urllib.request 的簡單例子:

#!/usr/local/bin/python

import urllib.request

# build the query string
qs = "First=Josephine&MI=Q&Last=Public"

# connect and send the server a path
req = urllib.request.urlopen('http://www.some-server.out-there'
                             '/cgi-bin/some-cgi-script', data=qs)
with req:
    msg, hdrs = req.read(), req.info()

注意,通常在百分號編碼的 POST 操作中,查詢字符串必須使用 urllib.parse.urlencode() 處理一下。舉個例子,如果要發送 name=Guy Steele, Jr. 的話:

>>> import urllib.parse
>>> urllib.parse.urlencode({'name': 'Guy Steele, Jr.'})
'name=Guy+Steele%2C+Jr.'

參見

查看 HOWTO 使用 urllib 包獲取網絡資源 獲取更多示例。

生成 HTML 需要使用什么模塊??

你可以在 Web 編程 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)

socket 的 connect() 方法怎樣避免阻塞??

通常會用 select 模塊處理 socket 異步 I/O。

要避免 TCP 連接阻塞,你可以設置將 socket 設置為非阻塞模式。此時當調用 connect() 時,要么連接會立刻建立好(幾乎不可能),要么會收到一個包含了錯誤碼 .error 的異常。errno.EINPROGRESS 表示連接正在進行,但還沒有完成。不同的系統會返回不同的值,所以你需要確認你使用的系統會返回什么。

你可以使用 connect_ex() 方法來避免產生異常。這個方法只會返回錯誤碼。如果需要輪詢的話,你可以再次調用 connect_ex() —— 0errno.EISCONN 表示連接已建立,或者你也可以用 select 檢查這個 socket 是否可寫。

注解

asyncore 模塊提供了編寫非阻塞網絡代碼框架性的方法。第三方的 Twisted 庫也很常用且功能強大。

數據庫?

Python 中有數據庫包的接口嗎??

有的。

標準 Python 還包含了基于磁盤的哈希接口例如 DBMGDBM 。除此之外還有 sqlite3 模塊,該模塊提供了一個輕量級的基于磁盤的關系型數據庫。

大多數關系型數據庫都已經支持。查看 數據庫編程 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 類,你可以將其實例化,用來創建多個獨立的隨機數生成器。