代碼庫和插件 FAQ?

目錄

通用的代碼庫問題?

如何找到可以用來做 XXX 的模塊或應(yīng)用??

代碼庫參考 中查找是否有適合的標(biāo)準(zhǔn)庫模塊。(如果你已經(jīng)了解標(biāo)準(zhǔn)庫的內(nèi)容,可以跳過這一步)

對于第三方軟件包,請搜索 Python Package Index 或者是嘗試 Google 或其他網(wǎng)絡(luò)搜索引擎。 搜索 "Python" 加上一兩個你感興趣的關(guān)鍵詞通常就會找到一些有用的信息。

math.py(socket.py,regex.py 等)的源文件在哪??

如果找不到模塊的源文件,可能它是一個內(nèi)建的模塊,或是使用 C,C++ 或其他編譯型語言實現(xiàn)的動態(tài)加載模塊。這種情況下可能是沒有源碼文件的,類似 mathmodule.c 這樣的文件會存放在 C 代碼目錄中(但不在 Python 目錄中)。

Python 中(至少)有三類模塊:

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

  2. 使用 C 編寫的動態(tài)加載模塊(.dll,.pyd,.so,.sl 等);

  3. 使用 C 編寫并鏈接到解釋器的模塊,要獲取此列表,輸入:

    import sys
    print(sys.builtin_module_names)
    

在 Unix 中怎樣讓 Python 腳本可執(zhí)行??

你需要做兩件事:文件必須是可執(zhí)行的,并且第一行需要以 #! 開頭,后面跟上 Python 解釋器的路徑。

第一點可以用執(zhí)行 chmod +x scriptfile 或是 chmod 755 scriptfile 做到。

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

#!/usr/local/bin/python

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

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

#!/usr/bin/env python

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

有時候,用戶的環(huán)境變量如果太長,可能會導(dǎo)致 /usr/bin/env 執(zhí)行失??;又或者甚至根本就不存在 env 程序。在這種情況下,你可以嘗試使用下面的 hack 方法(來自 Alex Rezinsky):

#! /bin/sh
""":"
exec python $0 ${1+"$@"}
"""

這樣做有一個小小的缺點,它會定義腳本的 __doc__ 字符串。不過可以這樣修復(fù):

__doc__ = """...Whatever..."""

Python 中有 curses/termcap 包嗎??

對于類 Unix 系統(tǒng):標(biāo)準(zhǔn) Python 源碼發(fā)行版會在 Modules 子目錄中附帶 curses 模塊,但默認(rèn)并不會編譯。(注意:在 Windows 平臺下不可用 —— Windows 中沒有 curses 模塊。)

curses 模塊支持基本的 curses 特性,同時也支持 ncurses 和 SYSV curses 中的很多額外功能,比如顏色、不同的字符集支持、填充和鼠標(biāo)支持。這意味著這個模塊不兼容只有 BSD curses 模塊的操作系統(tǒng),但是目前仍在維護(hù)的系統(tǒng)應(yīng)該都不會存在這種情況。

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

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

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

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

handler(signum, frame)

因此它應(yīng)當(dāng)聲明為帶有兩個形參:

def handler(signum, frame):
    ...

通用任務(wù)?

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

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

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

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

程序的“全局主邏輯”應(yīng)該盡量簡單:

if __name__ == "__main__":
    main_logic()

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

一旦你的程序已經(jīng)組織為一個函數(shù)和類行為的有完整集合,你就應(yīng)該編寫測試函數(shù)來檢測這些行為。 可以將自動執(zhí)行一系列測試的測試集關(guān)聯(lián)到每個模塊。 這聽起來似乎需要大量的工作,但是由于 Python 是如此簡潔靈活因此它會極其容易。 你可以通過與“生產(chǎn)代碼”同步編寫測試函數(shù)使編程更為愉快和有趣,因為這將更容易并更早發(fā)現(xiàn)代碼問題甚至設(shè)計缺陷。

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

if __name__ == "__main__":
    self_test()

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

怎樣用 docstring 創(chuàng)建文檔??

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

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

在類 Unix 系統(tǒng)中有多種方案。最直接的方法是使用 curses,但是 curses 模塊太大了,難以學(xué)習(xí)。

線程相關(guān)?

程序中怎樣使用線程??

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

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

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

簡單的解決方法是在程序中加一個時間足夠長的 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)  # <---------------------------!

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

簡單的解決方法是在運行函數(shù)的開始處加一個時間很短的 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() 猜一個合適的等待時間,使用信號量機(jī)制會更好些。有一個辦法是使用 queue 模塊創(chuàng)建一個 queue 對象,讓每一個線程在運行結(jié)束時 append 一個令牌到 queue 對象中,主線程中從 queue 對象中讀取與線程數(shù)量一致的令牌數(shù)量即可。

如何將任務(wù)分配給多個工作線程??

最簡單的方式是使用 concurrent.futures 模塊,特別是其中的 ThreadPoolExecutor 類。

或者,如果你想更好地控制分發(fā)算法,你也可以自己寫邏輯實現(xiàn)。使用 queue 模塊來創(chuàng)建任務(wù)列表隊列。Queue 類維護(hù)一個了一個存有對象的列表,提供了 .put(obj) 方法添加元素,并且可以用 .get() 方法獲取元素。這個類會使用必要的加鎖操作,以此確保每個任務(wù)只會執(zhí)行一次。

這是一個簡單的例子:

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)

運行時會產(chǎn)生如下輸出:

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 內(nèi)部會使用 global interpreter lock (GIL)來確保同一時間只有一個線程運行。通常 Python 只會在字節(jié)碼指令之間切換線程;切換的頻率可以通過設(shè)置 sys.setswitchinterval() 指定。從 Python 程序的角度來看,每一條字節(jié)碼指令以及每一條指令對應(yīng)的 C 代碼實現(xiàn)都是原子的。

理論上說,具體的結(jié)果要看具體的 PVM 字節(jié)碼實現(xiàn)對指令的解釋。而實際上,對內(nèi)建類型(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

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

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

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

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

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

恰當(dāng)?shù)厥褂?C 拓展也很有用;使用 C 拓展處理耗時較久的任務(wù)時,拓展可以在線程執(zhí)行 C 代碼時釋放 GIL,讓其他線程執(zhí)行。zlibhashlib 等標(biāo)準(zhǔn)庫模塊已經(jīng)這樣做了。

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

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

最后,假設(shè)多個解釋器不共享任何狀態(tài),那么這樣做比每個進(jìn)程一個解釋器好在哪里呢?

輸入與輸出?

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

使用 os.remove(filename)os.unlink(filename)。查看 os 模塊以獲取更多文檔。這兩個函數(shù)是一樣的,unlink() 是這個函數(shù)在 Unix 系統(tǒng)調(diào)用中的名字。

如果要刪除目錄,應(yīng)該使用 os.rmdir();使用 os.mkdir() 創(chuàng)建目錄。os.makedirs(path) 會創(chuàng)建 path 中任何不存在的目錄。os.removedirs(path) 則會刪除其中的目錄,只要它們都是空的;如果你想刪除整個目錄以及其中的內(nèi)容,可以使用 shutil.rmtree()。

重命名文件可以使用 os.rename(old_path, new_path)。

如果需要截斷文件,使用 f = open(filename, "rb+") 打開文件,然后使用 f.truncate(offset);offset 默認(rèn)是當(dāng)前的搜索位置。也可以對使用 os.open() 打開的文件使用 os.ftruncate(fd, offset),其中 fd 是文件描述符(一個小的整型數(shù))。

shutil 模塊也包含了一些處理文件的函數(shù),包括 copyfile(),copytree()rmtree()。

怎樣復(fù)制文件??

shutil 模塊有一個 copyfile() 函數(shù)。注意在 MacOS 9 中不會復(fù)制 resource fork 和 Finder info。

怎樣讀?。ɑ?qū)懭耄┒M(jìn)制數(shù)據(jù)??

要讀寫復(fù)雜的二進(jìn)制數(shù)據(jù)格式,最好使用 struct 模塊。該模塊可以讀取包含二進(jìn)制數(shù)據(jù)(通常是數(shù)字)的字符串并轉(zhuǎn)換為 Python 對象,反之亦然。

舉例來說,下面的代碼會從文件中以大端序格式讀取一個 2 字節(jié)的整型和一個 4 字節(jié)的整型:

import struct

with open(filename, "rb") as f:
    s = f.read(8)
    x, y, z = struct.unpack(">hhl", s)

格式字符串中的 ‘>’ 強(qiáng)制以大端序讀取數(shù)據(jù);字母 ‘h’ 從字符串中讀取一個“短整型”(2 字節(jié)),字母 ‘l’ 讀取一個“長整型”(4 字節(jié))。

對于更常規(guī)的數(shù)據(jù)(例如整型或浮點類型的列表),你也可以使用 array 模塊。

備注

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

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

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

怎樣訪問(RS232)串口??

For Win32, OSX, Linux, BSD, Jython, IronPython:

對于 Unix,查看 Mitch Chapman 發(fā)布的帖子:

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

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

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

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

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

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

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

網(wǎng)絡(luò) / Internet 編程?

Python 中的 WWW 工具是什么??

參閱代碼庫參考手冊中 互聯(lián)網(wǎng)協(xié)議和支持互聯(lián)網(wǎng)數(shù)據(jù)處理 這兩章的內(nèi)容。Python 有大量模塊來幫助你構(gòu)建服務(wù)端和客戶端 web 系統(tǒng)。

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

Cameron Laird 維護(hù)了一份關(guān)于 Python web 技術(shù)的實用網(wǎng)頁的集合,見 http://phaseit.net/claird/comp.lang.python/web_python

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

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

是的。 這里是一個使用 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() 處理一下。舉個例子,如果要發(fā)送 name=Guy Steele, Jr. 的話:

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

參見

查看 如何利用 urllib 包獲取網(wǎng)絡(luò)資源 獲取更多示例。

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

你可以在 Web 編程 wiki 頁面 找到許多有用的鏈接。

怎樣使用 Python 腳本發(fā)送郵件??

使用 smtplib 標(biāo)準(zhǔn)庫模塊。

下面是一個很簡單的交互式發(fā)送郵件的代碼。這個方法適用于任何支持 SMTP 協(xié)議的主機(jī)。

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 系統(tǒng)中還可以使用 sendmail。sendmail 程序的位置在不同系統(tǒng)中不一樣,有時是在 /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 連接發(fā)生阻塞,你可以將 socket 設(shè)為非阻塞模式。 這樣當(dāng)你執(zhí)行 socket.connect() 時,你將或是立即完成連接(不大可能)或是收到一個包含 .errno 錯誤碼的異常。 errno.EINPROGRESS 表示連接正在進(jìn)行但還沒有完成。 不同的操作系統(tǒng)將返回不同的值,因此你需要確認(rèn)你的系統(tǒng)會返回什么值。

你可以使用 socket.connect_ex() 方法來避免生成異常。 它將只返回 errno 值。 要進(jìn)行輪詢,你可以稍后再次調(diào)用 socket.connect_ex() -- 0errno.EISCONN 表示連接已完成 -- 或者你也可以將此 socket 傳給 select.select() 來檢查它是否可寫。

備注

asyncio 模塊提供了通用的單線程并發(fā)異步庫,它可被用來編寫非阻塞的網(wǎng)絡(luò)代碼。 第三方的 Twisted 庫是一個熱門且功能豐富的替代選擇。

數(shù)據(jù)庫?

Python 中有數(shù)據(jù)庫包的接口嗎??

有的。

標(biāo)準(zhǔn) Python 還包含了基于磁盤的哈希接口例如 DBMGDBM 。除此之外還有 sqlite3 模塊,該模塊提供了一個輕量級的基于磁盤的關(guān)系型數(shù)據(jù)庫。

大多數(shù)關(guān)系型數(shù)據(jù)庫都已經(jīng)支持。查看 數(shù)據(jù)庫編程 wiki 頁面 獲取更多信息。

在 Python 中如何實現(xiàn)持久化對象??

pickle 庫模塊以一種非常通用的方式解決了這個問題(雖然你依然不能用它保存打開的文件、套接字或窗口之類的東西),此外 shelve 庫模塊可使用 pickle 和 (g)dbm 來創(chuàng)建包含任意 Python 對象的持久化映射。

數(shù)學(xué)和數(shù)字?

Python 中怎樣生成隨機(jī)數(shù)??

random 標(biāo)準(zhǔn)庫模塊實現(xiàn)了隨機(jī)數(shù)生成器,使用起來非常簡單:

import random
random.random()

這個函數(shù)會返回 [0, 1) 之間的隨機(jī)浮點數(shù)。

該模塊中還有許多其他的專門的生成器,例如:

  • randrange(a, b) 返回 [a, b) 區(qū)間內(nèi)的一個整型數(shù)。

  • uniform(a, b) 返回 [a, b) 區(qū)間之間的浮點數(shù)。

  • normalvariate(mean, sdev) 使用正態(tài)(高斯)分布采樣。

還有一些高級函數(shù)直接對序列進(jìn)行操作,例如:

  • choice(S) 從給定的序列中隨機(jī)選擇一個元素。

  • shuffle(L) 會對列表執(zhí)行原地重排,即將其隨機(jī)地打亂。

還有 Random 類,你可以將其實例化,用來創(chuàng)建多個獨立的隨機(jī)數(shù)生成器。