python 各種模組學習

工程師WWW發表於2017-04-24
  1. 核心模組
  2. 更多標準模組
  3. 執行緒和程式
  4. 資料表示
  5. 檔案格式
  6. 郵件和新聞訊息處理
  7. 網路協議
  8. 國際化
  9. 多媒體相關模組
  10. 資料儲存
  11. 工具和實用程式
  12. 其他模組
  13. 執行支援模組
  14. 其他模組
  15. Py 2.0 後新增模組
  16. 後記

"We'd like to pretend that 'Fredrik' is a role, but even hundreds of volunteers couldn't possibly keep up. No, 'Fredrik' is the result of crossing an http server with a spam filter with an emacs whatsit and some other stuff besides."
-Gordon McMillan, June 1998

Python 2.0釋出附帶了一個包含200個以上模組的可擴充套件的標準庫. 本書簡要地介紹每個模組並提供至少一個例子來說明如何使用它. 本書一共包含360個例子.

0.1. 關於本書

"Those people who have nothing better to do than post on the Internet all day long are rarely the ones who have the most insights."
- Jakob Nielsen, December 1998

五年前我偶然遇到了 Python, 開始了我的 Python 之旅, 我花費了大量的時間 在 comp.lang.python 新聞組裡回答問題. 也許某個人發現一個模組正是他想要的, 但是卻不知道如何使用它. 也許某個人為他的任務挑選的不合適的模組. 也許某個人已經厭 倦了發明新輪子. 大多時候, 一個簡短的例子要比一份手冊文件更有幫助.

本書是超過3,000個新聞組討論的精華部分, 當然也有很多的新指令碼, 為了涵蓋標準庫的每個角落.

我盡力使得每個指令碼都易於理解, 易於重用程式碼. 我有意縮短註釋的長度, 如果你想更深入地 瞭解背景, 那麼你可以參閱每個 Python 釋出中的參考手冊. 本書的重要之處在於範例程式碼.

我們歡迎任何評論, 建議, 以及 bug 報告, 請將它們傳送到 fredrik@pythonware.com. 我將閱讀盡我所能閱讀所有的郵件, 但可能回覆不是那麼及時.

本書的相關更新內容以及其他資訊請訪問 http://www.pythonware.com/people/fredrik/librarybook.htm

為什麼沒有Tkinter?

本書涵蓋了整個標準庫, 除了(可選的)Tkinter ui(user-interface : 使用者介面) 庫. 有很多原因, 更多是因為時間, 本書的空間, 以及我正在寫另一本關於 Tkinter 的書.

關於這些書的資訊, 請訪問 http://www.pythonware.com/people/fredrik/tkinterbook.htm. (不用看了,又一404)

產品細節

本書使用DocBook SGML編寫, 我使用了一系列的工具, 包括Secret Labs' PythonWorks, Excosoft Documentor, James Clark's Jade DSSSL processor, Norm Walsh's DocBook stylesheets, 當然,還有一些 Python 指令碼.

感謝幫忙校對的人們: Tim Peters, Guido van Rossum, David Ascher, Mark Lutz, 和 Rael Dornfest, 以及 PythonWare 成員: Matthew Ellis, Håkan Karlsson, 和 Rune Uhlin.

感謝 Lenny Muellner, 他幫助我把SGML檔案轉變為你們現在所看到的這本書, 以及Christien Shangraw, 他將那些程式碼檔案集合起來做成了隨書CD (可以在http://examples.oreilly.com/pythonsl 找到, 竟然沒有404, 奇蹟).

0.2. 程式碼約定

本書使用以下習慣用法:

斜體

用於檔名和命令. 還用於定義術語.

等寬字型 e.g. Python

用於程式碼以及方法,模組,操作符,函式,語句,屬性等的名稱.

等寬粗體

用於程式碼執行結果.

0.3. 關於例子

除非提到,所有例子都可以在 Python 1.5.2 和 Python 2.0 下執行. 能不能在 Python 2.4/2.5 下執行.....看參與翻譯各位的了.

除了一些平臺相關模組的指令碼, 所有例子都可以在 Windows, Solaris, 以及 Linux 下正常執行.

所有程式碼都是有版權的. 當然,你可以自由地使用這些這些模組,別忘記你是從哪得到(?學會)這些的.

大多例子的檔名都包含它所使用的模組名稱,後邊是 "-example-" 以及一個唯一的"序號". 注意有些例子並不是按順序出現的, 這是為了匹配本書的較早版本 - (the eff-bot guide to) The Standard Python Library.

你可以在網上找到本書附帶CD的內容 (參閱 http://examples.oreilly.com/pythonsl). 更多資訊以及更新內容參閱http://www.pythonware.com/people/fredrik/librarybook.htm. (ft, 又一404. 大家一定不要看~)

0.4. 如何聯絡我們

Python 江湖 QQ 群: 43680167

Feather (校對) QQ: 85660100


1. 核心模組

"Since the functions in the C runtime library are not part of the Win32 API, we believe the number of applications that will be affected by this bug to be very limited."
- Microsoft, January 1999

1.1. 介紹

Python 的標準庫包括了很多的模組, 從 Python 語言自身特定的型別和宣告, 到一些只用於少數程式的不著名的模組.

本章描述了一些基本的標準庫模組. 任何大型 Python 程式都有可能直接或間接地使用到這類模組的大部分.

1.1.1. 內建函式和異常

下面的這兩個模組比其他模組加在一起還要重要: 定義內建函式(例如 len, int, range ...)的 _ _builtin_ _ 模組, 以及定義所有內建異常的 exceptions模組.

Python 在啟動時匯入這兩個模組, 使任何程式都能夠使用它們.

1.1.2. 作業系統介面模組

Python 有許多使用了 POSIX 標準 API 和標準 C 語言庫的模組. 它們為底層作業系統提供了平臺獨立的介面.

這類的模組包括: 提供檔案和程式處理功能的 os 模組; 提供平臺獨立的檔名處理 (分拆目錄名, 檔名, 字尾等)的 os.path 模組; 以及時間日期處理相關的 time/datetime 模組.

[!Feather注: datetime 為 Py2.3 新增模組, 提供增強的時間處理方法 ]

延伸一點說, 網路和執行緒模組同樣也可以歸為這一個型別. 不過 Python 並沒有在所有的平臺/版本實現這些.

1.1.3. 型別支援模組

標準庫裡有許多用於支援內建型別操作的庫. string 模組實現了常用的字串處理. math 模組提供了數學計算操作和常量(pi, e都屬於這類常量), cmath模組為複數提供了和 math 一樣的功能.

1.1.4. 正規表示式

re 模組為 Python 提供了正規表示式支援. 正規表示式是用於匹配字串或特定子字串的 有特定語法的字串模式.

1.1.5. 語言支援模組

sys 模組可以讓你訪問直譯器相關引數,比如模組搜尋路徑,直譯器版本號等. operator 模組提供了和內建操作符作用相同的函式. copy 模組允許 你複製物件, Python 2.0 新加入的 gc 模組提供了對垃圾收集的相關控制功能.


1.2. _ _builtin_ _ 模組

這個模組包含 Python 中使用的內建函式. 一般不用手動匯入這個模組; Python會幫你做好一切.

1.2.1. 使用元組或字典中的引數呼叫函式

Python允許你實時地建立函式引數列表. 只要把所有的引數放入一個元組中, 然後通過內建的 apply 函式呼叫函式. 如 Example 1-1.

1.2.1.1. Example 1-1. 使用 apply 函式

File: builtin-apply-example-1.py def function(a, b): print a, b apply(function, ("whither", "canada?")) apply(function, (1, 2 + 3)) whither canada? 1 5

要想把關鍵字引數傳遞給一個函式, 你可以將一個字典作為 apply 函式的第 3 個引數, 參考 Example 1-2.

1.2.1.2. Example 1-2. 使用 apply 函式傳遞關鍵字引數

File: builtin-apply-example-2.py def function(a, b): print a, b apply(function, ("crunchy", "frog")) apply(function, ("crunchy",), {"b": "frog"}) apply(function, (), {"a": "crunchy", "b": "frog"}) crunchy frog crunchy frog crunchy frog

apply 函式的一個常見用法是把建構函式引數從子類傳遞到基類, 尤其是建構函式需要接受很多引數的時候. 如 Example 1-3 所示.

1.2.1.3. Example 1-3. 使用 apply 函式呼叫基類的建構函式

File: builtin-apply-example-3.py class Rectangle: def _ _init_ _(self, color="white", width=10, height=10): print "create a", color, self, "sized", width, "x", height class RoundedRectangle(Rectangle): def _ _init_ _(self, **kw): apply(Rectangle._ _init_ _, (self,), kw) rect = Rectangle(color="green", height=100, width=100) rect = RoundedRectangle(color="blue", height=20) create a green <Rectangle instance at 8c8260> sized 100 x 100 create a blue <RoundedRectangle instance at 8c84c0> sized 10 x 20

Python 2.0 提供了另個方法來做相同的事. 你只需要使用一個傳統的函式呼叫 , 使用 * 來標記元組, ** 來標記字典.

下面兩個語句是等價的:

result = function(*args, **kwargs) result = apply(function, args, kwargs)

1.2.2. 載入和過載模組

如果你寫過較龐大的 Python 程式, 那麼你就應該知道 import 語句是用來匯入外部模組的 (當然也可以使用 from-import 版本). 不過你可能不知道import 其實是靠呼叫內建 函式 _ _import_ _ 來工作的.

通過這個戲法你可以動態地呼叫函式. 當你只知道模組名稱(字串)的時候, 這將很方便. Example 1-4 展示了這種用法, 動態地匯入所有以 "-plugin" 結尾的模組.

1.2.2.1. Example 1-4. 使用 _ _import_ _ 函式載入模組

File: builtin-import-example-1.py import glob, os modules = [] for module_file in glob.glob("*-plugin.py"): try: module_name, ext = os.path.splitext(os.path.basename(module_file)) module = _ _import_ _(module_name) modules.append(module) except ImportError: pass # ignore broken modules # say hello to all modules for module in modules: module.hello() example-plugin says hello

注意這個 plug-in 模組檔名中有個 "-" (hyphens). 這意味著你不能使用普通的 import 命令, 因為 Python 的辨識符不允許有 "-" .

Example 1-5 展示了 Example 1-4 中使用的 plug-in .

1.2.2.2. Example 1-5. Plug-in 例子

File: example-plugin.py def hello(): print "example-plugin says hello"

Example 1-6 展示瞭如何根據給定模組名和函式名獲得想要的函式物件.

1.2.2.3. Example 1-6. 使用 _ _import_ _ 函式獲得特定函式

File: builtin-import-example-2.py def getfunctionbyname(module_name, function_name): module = _ _import_ _(module_name) return getattr(module, function_name) print repr(getfunctionbyname("dumbdbm", "open")) <function open at 794fa0>

你也可以使用這個函式實現延遲化的模組匯入 (lazy module loading). 例如在 Example 1-7 中 的 string 模組只在第一次使用的時候匯入.

1.2.2.4. Example 1-7. 使用 _ _import_ _ 函式實現 延遲匯入

File: builtin-import-example-3.py class LazyImport: def _ _init_ _(self, module_name): self.module_name = module_name self.module = None def _ _getattr_ _(self, name): if self.module is None: self.module = _ _import_ _(self.module_name) return getattr(self.module, name) string = LazyImport("string") print string.lowercase abcdefghijklmnopqrstuvwxyz

Python 也提供了重新載入已載入模組的基本支援. [Example 1-8 #eg-1-8 會載入 3 次 hello.py 檔案.

1.2.2.5. Example 1-8. 使用 reload 函式

File: builtin-reload-example-1.py import hello reload(hello) reload(hello) hello again, and welcome to the show hello again, and welcome to the show hello again, and welcome to the show

reload 直接接受模組作為引數.

[!Feather 注: ^ 原句無法理解, 稍後討論.]

注意,當你重載入模組時, 它會被重新編譯, 新的模組會代替模組字典裡的老模組. 但是, 已經用原模組裡的類建立的例項仍然使用的是老模組(不會被更新).

同樣地, 使用 from-import 直接建立的到模組內容的引用也是不會被更新的.

1.2.3. 關於名稱空間

dir 返回由給定模組, 類, 例項, 或其他型別的所有成員組成的列表. 這可能在互動式 Python 直譯器下很有用, 也可以用在其他地方. Example 1-9展示了 dir函式的用法.

1.2.3.1. Example 1-9. 使用 dir 函式

File: builtin-dir-example-1.py def dump(value): print value, "=>", dir(value) import sys dump(0) dump(1.0) dump(0.0j) # complex number dump([]) # list dump({}) # dictionary dump("string") dump(len) # function dump(sys) # module 0 => [] 1.0 => [] 0j => ['conjugate', 'imag', 'real'] [] => ['append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] {} => ['clear', 'copy', 'get', 'has_key', 'items', 'keys', 'update', 'values'] string => [] <built-in function len> => ['_ _doc_ _', '_ _name_ _', '_ _self_ _'] <module 'sys' (built-in)> => ['_ _doc_ _', '_ _name_ _', '_ _stderr_ _', '_ _stdin_ _', '_ _stdout_ _', 'argv', 'builtin_module_names', 'copyright', 'dllhandle', 'exc_info', 'exc_type', 'exec_prefix', 'executable', ...

在例子 Example 1-10中定義的 getmember 函式返回給定類定義的所有類級別的屬性和方法.

1.2.3.2. Example 1-10. 使用 dir 函式查詢類的所有成員

File: builtin-dir-example-2.py class A: def a(self): pass def b(self): pass class B(A): def c(self): pass def d(self): pass def getmembers(klass, members=None): # get a list of all class members, ordered by class if members is None: members = [] for k in klass._ _bases_ _: getmembers(k, members) for m in dir(klass): if m not in members: members.append(m) return members print getmembers(A) print getmembers(B) print getmembers(IOError) ['_ _doc_ _', '_ _module_ _', 'a', 'b'] ['_ _doc_ _', '_ _module_ _', 'a', 'b', 'c', 'd'] ['_ _doc_ _', '_ _getitem_ _', '_ _init_ _', '_ _module_ _', '_ _str_ _']

getmembers 函式返回了一個有序列表. 成員在列表中名稱出現的越早, 它所處的類層次就越高. 如果無所謂順序的話, 你可以使用字典代替列表.

[!Feather 注: 字典是無序的, 而列表和元組是有序的, 網上有關於有序字典的討論]

vars 函式與此相似, 它返回的是包含每個成員當前值的字典. 如果你使用不帶引數的 vars , 它將返回當前區域性名稱空間的可見元素(同 locals() 函式 ). 如 Example 1-11所表示.

1.2.3.3. Example 1-11. 使用 vars 函式

File: builtin-vars-example-1.py book = "library2" pages = 250 scripts = 350 print "the %(book)s book contains more than %(scripts)s scripts" % vars() the library book contains more than 350 scripts

1.2.4. 檢查物件型別

Python 是一種動態型別語言, 這意味著給一個定變數名可以在不同的場合繫結到不同的型別上. 在接下面例子中, 同樣的函式分別被整數, 浮點數, 以及一個字串呼叫:

def function(value): print value function(1) function(1.0) function("one")

type 函式 (如 Example 1-12 所示) 允許你檢查一個變數的型別. 這個函式會返回一個 type descriptor (型別描述符), 它對於 Python 直譯器提供的每個型別都是不同的.

1.2.4.1. Example 1-12. 使用 type 函式

File: builtin-type-example-1.py def dump(value): print type(value), value dump(1) dump(1.0) dump("one") <type 'int'> 1 <type 'float'> 1.0 <type 'string'> one

每個型別都有一個對應的型別物件, 所以你可以使用 is 操作符 (物件身份?) 來 檢查型別. (如 Example 1-13所示).

1.2.4.2. Example 1-13. 對檔名和檔案物件使用 type 函式

File: builtin-type-example-2.py def load(file): if isinstance(file, type("")): file = open(file, "rb") return file.read() print len(load("samples/sample.jpg")), "bytes" print len(load(open("samples/sample.jpg", "rb"))), "bytes" 4672 bytes 4672 bytes

callable 函式, 如 Example 1-14 所示, 可以檢查一個物件是否是可呼叫的 (無論是直接呼叫或是通過 apply). 對於函式, 方法, lambda 函式, 類, 以及實現了 _ _call_ _ 方法的類例項, 它都返回 True.

1.2.4.3. Example 1-14. 使用 callable 函式

File: builtin-callable-example-1.py def dump(function): if callable(function): print function, "is callable" else: print function, "is *not* callable" class A: def method(self, value): return value class B(A): def _ _call_ _(self, value): return value a = A() b = B() dump(0) # simple objects dump("string") dump(callable) dump(dump) # function dump(A) # classes dump(B) dump(B.method) dump(a) # instances dump(b) dump(b.method) 0 is *not* callable string is *not* callable <built-in function callable> is callable <function dump at 8ca320> is callable A is callable B is callable <unbound method A.method> is callable <A instance at 8caa10> is *not* callable <B instance at 8cab00> is callable <method A.method of B instance at 8cab00> is callable

注意類物件 (A 和 B) 都是可呼叫的; 如果呼叫它們, 就產生新的物件(類例項). 但是 A 類的例項不可呼叫, 因為它的類沒有實現 _ _call_ _ 方法.

你可以在 operator 模組中找到檢查物件是否為某一內建型別(數字, 序列, 或者字典等) 的函式. 但是, 因為建立一個類很簡單(比如實現基本序列方法的類), 所以對這些 型別使用顯式的型別判斷並不是好主意.

在處理類和例項的時候會複雜些. Python 不會把類作為本質上的型別對待; 相反地, 所有的類都屬於一個特殊的類型別(special class type), 所有的類例項屬於一個特殊的例項型別(special instance type).

這意味著你不能使用 type 函式來測試一個例項是否屬於一個給定的類; 所有的例項都是同樣 的型別! 為了解決這個問題, 你可以使用 isinstance 函式,它會檢查一個物件是 不是給定類(或其子類)的例項. Example 1-15 展示了 isinstance 函式的使用.

1.2.4.4. Example 1-15. 使用 isinstance 函式

File: builtin-isinstance-example-1.py class A: pass class B: pass class C(A): pass class D(A, B): pass def dump(object): print object, "=>", if isinstance(object, A): print "A", if isinstance(object, B): print "B", if isinstance(object, C): print "C", if isinstance(object, D): print "D", print a = A() b = B() c = C() d = D() dump(a) dump(b) dump(c) dump(d) dump(0) dump("string")<A instance at 8ca6d0> => A <B instance at 8ca750> => B <C instance at 8ca780> => A C <D instance at 8ca7b0> => A B D 0 => string =>

issubclass 函式與此相似, 它用於檢查一個類物件是否與給定類相同, 或者是給定類的子類. 如 Example 1-16 所示.

注意, isinstance 可以接受任何物件作為引數, 而 issubclass 函式在接受非類物件參 數時會引發 TypeError 異常.

1.2.4.5. Example 1-16. 使用 issubclass 函式

File: builtin-issubclass-example-1.py class A: pass class B: pass class C(A): pass class D(A, B): pass def dump(object): print object, "=>", if issubclass(object, A): print "A", if issubclass(object, B): print "B", if issubclass(object, C): print "C", if issubclass(object, D): print "D", print dump(A) dump(B) dump(C) dump(D) dump(0) dump("string") A => A B => B C => A C D => A B D 0 => Traceback (innermost last): File "builtin-issubclass-example-1.py", line 29, in ? File "builtin-issubclass-example-1.py", line 15, in dump TypeError: arguments must be classes

1.2.5. 計算 Python 表示式

Python 提供了在程式中與直譯器互動的多種方法. 例如 eval 函式將一個字串 作為 Python 表示式求值. 你可以傳遞一串文字, 簡單的表示式, 或者使用 內建 Python 函式. 如 Example 1-17 所示.

1.2.5.1. Example 1-17. 使用 eval 函式

File: builtin-eval-example-1.py def dump(expression): result = eval(expression) print expression, "=>", result, type(result) dump("1") dump("1.0") dump("'string'") dump("1.0 + 2.0") dump("'*' * 10") dump("len('world')") 1 => 1 <type 'int'> 1.0 => 1.0 <type 'float'> 'string' => string <type 'string'> 1.0 + 2.0 => 3.0 <type 'float'> '*' * 10 => ********** <type 'string'> len('world') => 5 <type 'int'>

如果你不確定字串來源的安全性, 那麼你在使用 eval 的時候會遇到些麻煩. 例如, 某個使用者可能會使用 _ _import_ _ 函式載入 os 模組, 然後從硬碟刪除檔案 (如 Example 1-18 所示).

1.2.5.2. Example 1-18. 使用 eval 函式執行任意命令

File: builtin-eval-example-2.py print eval("_ _import_ _('os').getcwd()") print eval("_ _import_ _('os').remove('file')")/home/fredrik/librarybook Traceback (innermost last): File "builtin-eval-example-2", line 2, in ? File "<string>", line 0, in ? os.error: (2, 'No such file or directory')

這裡我們得到了一個 os.error 異常, 這說明 Python 事實上在嘗試刪除檔案!

幸運地是, 這個問題很容易解決. 你可以給 eval 函式傳遞第 2 個引數, 一個定義了該表示式求值時名稱空間的字典. 我們測試下, 給函式傳遞個空字典:

>>> print eval("_ _import_ _('os').remove('file')", {}) Traceback (innermost last): File "<stdin>", line 1, in ? File "<string>", line 0, in ? os.error: (2, 'No such file or directory')

呃.... 我們還是得到了個 os.error 異常.

這是因為 Python 在求值前會檢查這個字典, 如果沒有發現名稱為 _ _builtins_ _ 的變數(複數形式), 它就會新增一個:

>>> namespace = {} >>> print eval("_ _import_ _('os').remove('file')", namespace) Traceback (innermost last): File "<stdin>", line 1, in ? File "<string>", line 0, in ? os.error: (2, 'No such file or directory') >>> namespace.keys() ['_ _builtins_ _']

如果你列印這個 namespace 的內容, 你會發現裡邊有所有的內建函式.

[!Feather 注: 如果我RP不錯的話, 新增的這個_ _builtins_ _就是當前的_ _builtins_ _]

我們注意到瞭如果這個變數存在, Python 就不會去新增預設的, 那麼我們的解決方法也來了, 為傳遞的字典引數加入一個 _ _builtins_ _ 項即可. 如Example 1-19 所示.

1.2.5.3. Example 1-19. 安全地使用 eval 函式求值

File: builtin-eval-example-3.py print eval("_ _import_ _('os').getcwd()", {}) print eval("_ _import_ _('os').remove('file')", {"_ _builtins_ _": {}}) /home/fredrik/librarybook Traceback (innermost last): File "builtin-eval-example-3.py", line 2, in ? File "<string>", line 0, in ? NameError: _ _import_ _

即使這樣, 你仍然無法避免針對 CPU 和記憶體資源的攻擊. (比如, 形如 eval("'*'*1000000*2*2*2*2*2*2*2*2*2") 的語句在執行後會使你的程式耗盡系統資源).

1.2.6. 編譯和執行程式碼

eval 函式只針對簡單的表示式. 如果要處理大塊的程式碼, 你應該使用 compile 和 exec 函式 (如 Example 1-20 所示).

1.2.6.1. Example 1-20. 使用 compile 函式檢查語法

File: builtin-compile-example-1.py NAME = "script.py" BODY = """ prnt 'owl-stretching time' """ try: compile(BODY, NAME, "exec") except SyntaxError, v: print "syntax error:", v, "in", NAME # syntax error: invalid syntax in script.py

成功執行後, compile 函式會返回一個程式碼物件, 你可以使用 exec 語句執行它, 參見 Example 1-21 .

1.2.6.2. Example 1-21. 執行已編譯的程式碼

File: builtin-compile-example-2.py BODY = """ print 'the ant, an introduction' """ code = compile(BODY, "<script>", "exec") print code exec code <code object ? at 8c6be0, file "<script>", line 0> the ant, an introduction

使用 Example 1-22 中的類可以在程式執行時實時地生成程式碼. write 方法用於新增程式碼, indent 和 dedent 方法用於控制縮排結構. 其他部分交給類來處理.

1.2.6.3. Example 1-22. 簡單的程式碼生成工具

File: builtin-compile-example-3.py import sys, string class CodeGeneratorBackend: "Simple code generator for Python" def begin(self, tab="/t"): self.code = [] self.tab = tab self.level = 0 def end(self): self.code.append("") # make sure there's a newline at the end  return compile(string.join(self.code, "/n"), "<code>", "exec") def write(self, string): self.code.append(self.tab * self.level + string) def indent(self): self.level = self.level + 1 # in 2.0 and later, this can be written as: self.level += 1 def dedent(self): if self.level == 0: raise SyntaxError, "internal error in code generator" self.level = self.level - 1 # or: self.level -= 1 # # try it out! c = CodeGeneratorBackend() c.begin() c.write("for i in range(5):") c.indent() c.write("print 'code generation made easy!'") c.dedent() exec c.end() code generation made easy! code generation made easy! code generation made easy! code generation made easy! code generation made easy!

Python 還提供了 execfile 函式, 一個從檔案載入程式碼, 編譯程式碼, 執行程式碼的快捷方式. Example 1-23 簡單地展示瞭如何使用這個函式.

1.2.6.4. Example 1-23. 使用 execfile 函式

File: builtin-execfile-example-1.py execfile("hello.py") def EXECFILE(filename, locals=None, globals=None): exec compile(open(filename).read(), filename, "exec") in locals, globals EXECFILE("hello.py") hello again, and welcome to the show hello again, and welcome to the show

Example 1-24 中的程式碼是 Example 1-23 中使用的 hello.py 檔案.

1.2.6.5. Example 1-24. hello.py 指令碼

File: hello.py print "hello again, and welcome to the show"

1.2.7. 從 _ _builtin_ _ 模組過載函式

因為 Python 在檢查區域性名稱空間和模組名稱空間前不會檢查內建函式, 所以有時候你可能要顯式地引用 _ _builtin_ _ 模組. 例如 Example 1-25 過載了內建的 open 函式. 這時候要想使用原來的 open 函式, 就需要指令碼顯式地指明模組名稱.

1.2.7.1. Example 1-25. 顯式地訪問 _ _builtin_ _ 模組中的函式

File: builtin-open-example-1.py def open(filename, mode="rb"): import _ _builtin_ _ file = _ _builtin_ _.open(filename, mode) if file.read(5) not in("GIF87", "GIF89"): raise IOError, "not a GIF file" file.seek(0) return file fp = open("samples/sample.gif") print len(fp.read()), "bytes" fp = open("samples/sample.jpg") print len(fp.read()), "bytes" 3565 bytes Traceback (innermost last): File "builtin-open-example-1.py", line 12, in ? File "builtin-open-example-1.py", line 5, in open IOError: not a GIF file
[!Feather 注: 明白這個open()函式是幹什麼的麼? 檢查一個檔案是否是 GIF 檔案,  一般如這類的圖片格式都在檔案開頭有預設的格式.  另外開啟檔案推薦使用file()而不是open() , 雖然暫時沒有區別]

1.3. exceptions 模組

exceptions 模組提供了標準異常的層次結構. Python 啟動的時候會自動匯入這個模組, 並且將它加入到 _ _builtin_ _ 模組中. 也就是說, 一般不需要手動匯入這個模組.

在 1.5.2 版本時它是一個普通模組, 2.0 以及以後版本成為內建模組.

該模組定義了以下標準異常:

  • Exception 是所有異常的基類. 強烈建議(但不是必須)自定義的異常異常也繼承這個類.
  • SystemExit(Exception) 由 sys.exit 函式引發. 如果它在最頂層沒有被 try-except 語句捕獲, 那麼直譯器將直接關閉而不會顯示任何跟蹤返回資訊.
  • StandardError(Exception) 是所有內建異常的基類(除 SystemExit 外).
  • KeyboardInterrupt(StandardError) 在使用者按下 Control-C(或其他打斷按鍵)後 被引發. 如果它可能會在你使用 "捕獲所有" 的 try-except 語句時導致奇怪的問題.
  • ImportError(StandardError) 在 Python 匯入模組失敗時被引發.
  • EnvironmentError 作為所有直譯器環境引發異常的基類. (也就是說, 這些異常一般不是由於程式 bug 引起).
  • IOError(EnvironmentError) 用於標記 I/O 相關錯誤.
  • OSError(EnvironmentError) 用於標記 os 模組引起的錯誤.
  • WindowsError(OSError) 用於標記 os 模組中 Windows 相關錯誤.
  • NameError(StandardError) 在 Python 查詢全域性或區域性名稱失敗時被引發.
  • UnboundLocalError(NameError) , 當一個區域性變數還沒有賦值就被使用時, 會引發這個異常. 這個異常只有在2.0及之後的版本有; 早期版本只會引發一個普通的 NameError .
  • AttributeError(StandardError) , 當 Python 尋找(或賦值)給一個例項屬性, 方法, 模組功能或其它有效的命名失敗時, 會引發這個異常.
  • SyntaxError(StandardError) , 當直譯器在編譯時遇到語法錯誤, 這個異常就被引發.
  • (2.0 及以後版本) IndentationError(SyntaxError) 在遇到非法的縮排時被引發. 該異常只用於 2.0 及以後版本, 之前版本會引發一個 SyntaxError 異常.
  • (2.0 及以後版本) TabError(IndentationError) , 當使用 -tt 選項檢查不一致縮排時有可能被引發. 該異常只用於 2.0 及以後版本, 之前版本會引發一個SyntaxError 異常.
  • TypeError(StandardError) , 當給定型別的物件不支援一個操作時被引發.
  • AssertionError(StandardError) 在 assert 語句失敗時被引發(即表示式為 false 時).
  • LookupError(StandardError) 作為序列或字典沒有包含給定索引或鍵時所引發異常的基類.
  • IndexError(LookupError) , 當序列物件使用給定索引數索引失敗時(不存在索引對應物件)引發該異常.
  • KeyError(LookupError) 當字典物件使用給定索引索引失敗時(不存在索引對應物件)引發該異常.
  • ArithmeticError(StandardError) 作為數學計算相關異常的基類.
  • OverflowError(ArithmeticError) 在操作溢位時被引發(例如當一個整數太大, 導致不能符合給定型別).
  • ZeroDivisionError(ArithmeticError) , 當你嘗試用 0 除某個數時被引發.
  • FloatingPointError(ArithmeticError) , 當浮點數操作失敗時被引發.
  • ValueError(StandardError) , 當一個引數型別正確但值不合法時被引發.
  • (2.0 及以後版本) UnicodeError(ValueError) , Unicode 字串型別相關異常. 只使用在 2.0 及以後版本.
  • RuntimeError(StandardError) , 當出現執行時問題時引發, 包括在限制模式下嘗試訪問外部內容, 未知的硬體問題等等.
  • NotImplementedError(RuntimeError) , 用於標記未實現的函式, 或無效的方法.
  • SystemError(StandardError) , 直譯器內部錯誤. 該異常值會包含更多的細節 (經常會是一些深層次的東西, 比如 "eval_code2: NULL globals" ). 這本書的作者編了 5 年程式都沒見過這個錯誤. (想必是沒有用 raise SystemError).
  • MemoryError(StandardError) , 當直譯器耗盡記憶體時會引發該異常. 注意只有在底層記憶體分配抱怨時這個異常才會發生; 如果是在你的舊機器上, 這個異常發生之前系統會陷入混亂的記憶體交換中.

你可以建立自己的異常類. 只需要繼承內建的 Exception 類(或者它的任意一個合適的子類)即可, 有需要時可以再過載它的 _ _str_ _ 方法. Example 1-26展示瞭如何使用 exceptions 模組.

1.3.0.1. Example 1-26. 使用 exceptions 模組

File: exceptions-example-1.py # python imports this module by itself, so the following # line isn't really needed # python 會自動匯入該模組, 所以以下這行是不必要的 # import exceptions class HTTPError(Exception): # indicates an HTTP protocol error def _ _init_ _(self, url, errcode, errmsg): self.url = url self.errcode = errcode self.errmsg = errmsg def _ _str_ _(self): return ( "<HTTPError for %s: %s %s>" % (self.url, self.errcode, self.errmsg) ) try: raise HTTPError("http://www.python.org/foo", 200, "Not Found") except HTTPError, error: print "url", "=>", error.url print "errcode", "=>", error.errcode print "errmsg", "=>", error.errmsg raise # reraise exception url => http://www.python.org/foo errcode => 200 errmsg => Not Found Traceback (innermost last): File "exceptions-example-1", line 16, in ? HTTPError: <HTTPError for http://www.python.org/foo: 200 Not Found>

1.4. os 模組

這個模組中的大部分函式通過對應平臺相關模組實現, 比如 posix 和 nt. os 模組會在第一次匯入的時候自動載入合適的執行模組.

1.4.1. 處理檔案

內建的 open / file 函式用於建立, 開啟和編輯檔案, 如 Example 1-27 所示. 而 os 模組提供了重新命名和刪除檔案所需的函式.

1.4.1.1. Example 1-27. 使用 os 模組重新命名和刪除檔案

File: os-example-3.py import os import string def replace(file, search_for, replace_with): # replace strings in a text file back = os.path.splitext(file)[0] + ".bak" temp = os.path.splitext(file)[0] + ".tmp" try: # remove old temp file, if any os.remove(temp) except os.error: pass fi = open(file) fo = open(temp, "w") for s in fi.readlines(): fo.write(string.replace(s, search_for, replace_with)) fi.close() fo.close() try: # remove old backup file, if any os.remove(back) except os.error: pass # rename original to backup... os.rename(file, back) # ...and temporary to original os.rename(temp, file) # # try it out! file = "samples/sample.txt" replace(file, "hello", "tjena") replace(file, "tjena", "hello")

1.4.2. 處理目錄

os 模組也包含了一些用於目錄處理的函式.

listdir 函式返回給定目錄中所有檔名(包括目錄名)組成的列表, 如 Example 1-28 所示. 而 Unix 和 Windows 中使用的當前目錄和父目錄標記(. 和 .. )不包含在此列表中.

1.4.2.1. Example 1-28. 使用 os 列出目錄下的檔案

File: os-example-5.py import os for file in os.listdir("samples"): print file sample.au sample.jpg sample.wav ...

getcwd 和 chdir 函式分別用於獲得和改變當前工作目錄. 如 Example 1-29 所示.

1.4.2.2. Example 1-29. 使用 os 模組改變當前工作目錄

File: os-example-4.py import os # where are we? cwd = os.getcwd() print "1", cwd # go down os.chdir("samples") print "2", os.getcwd() # go back up os.chdir(os.pardir) print "3", os.getcwd() 1 /ematter/librarybook 2 /ematter/librarybook/samples 3 /ematter/librarybook

makedirs 和 removedirs 函式用於建立或刪除目錄層,如 Example 1-30 所示.

1.4.2.3. Example 1-30. 使用 os 模組建立/刪除多個目錄級

File: os-example-6.py import os os.makedirs("test/multiple/levels") fp = open("test/multiple/levels/file", "w") fp.write("inspector praline") fp.close() # remove the file os.remove("test/multiple/levels/file") # and all empty directories above it os.removedirs("test/multiple/levels")

removedirs 函式會刪除所給路徑中最後一個目錄下所有的空目錄. 而 mkdir 和 rmdir 函式只能處理單個目錄級. 如 Example 1-31 所示.

1.4.2.4. Example 1-31. 使用 os 模組建立/刪除目錄

File: os-example-7.py import os os.mkdir("test") os.rmdir("test") os.rmdir("samples") # this will fail Traceback (innermost last): File "os-example-7", line 6, in ? OSError: [Errno 41] Directory not empty: 'samples'

如果需要刪除非空目錄, 你可以使用 shutil 模組中的 rmtree 函式.

1.4.3. 處理檔案屬性

stat 函式可以用來獲取一個存在檔案的資訊, 如 Example 1-32 所示. 它返回一個類元組物件(stat_result物件, 包含 10 個元素), 依次是st_mode (許可權模式), st_ino (inode number), st_dev (device), st_nlink (number of hard links), st_uid (所有者使用者 ID), st_gid (所有者所在組 ID ), st_size (檔案大小, 位元組), st_atime (最近一次訪問時間), st_mtime (最近修改時間), st_ctime (平臺相關; Unix下的最近一次後設資料/metadata修改時間, 或者 Windows 下的建立時間) - 以上專案也可作為屬性訪問.

[!Feather 注: 原文為 9 元元組. 另,返回物件並非元組型別,為 struct.]

1.4.3.1. Example 1-32. 使用 os 模組獲取檔案屬性

File: os-example-1.py import os import time file = "samples/sample.jpg" def dump(st): mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime = st print "- size:", size, "bytes" print "- owner:", uid, gid print "- created:", time.ctime(ctime) print "- last accessed:", time.ctime(atime) print "- last modified:", time.ctime(mtime) print "- mode:", oct(mode) print "- inode/dev:", ino, dev # # get stats for a filename st = os.stat(file) print "stat", file dump(st) print # # get stats for an open file fp = open(file) st = os.fstat(fp.fileno()) print "fstat", file dump(st) stat samples/sample.jpg - size: 4762 bytes - owner: 0 0 - created: Tue Sep 07 22:45:58 1999 - last accessed: Sun Sep 19 00:00:00 1999 - last modified: Sun May 19 01:42:16 1996 - mode: 0100666 - inode/dev: 0 2 fstat samples/sample.jpg - size: 4762 bytes - owner: 0 0 - created: Tue Sep 07 22:45:58 1999 - last accessed: Sun Sep 19 00:00:00 1999 - last modified: Sun May 19 01:42:16 1996 - mode: 0100666 - inode/dev: 0 0

返回物件中有些屬性在非 Unix 平臺下是無意義的, 比如 (st_inode , st_dev)為 Unix 下的為每個檔案提供了唯一標識, 但在其他平臺可能為任意無意義資料 .

stat 模組包含了很多可以處理該返回物件的常量及函式. 下面的程式碼展示了其中的一些.

可以使用 chmod 和 utime 函式修改檔案的許可權模式和時間屬性,如 Example 1-33 所示.

1.4.3.2. Example 1-33. 使用 os 模組修改檔案的許可權和時間戳

File: os-example-2.py import os import stat, time infile = "samples/sample.jpg" outfile = "out.jpg" # copy contents fi = open(infile, "rb") fo = open(outfile, "wb") while 1: s = fi.read(10000) if not s: break fo.write(s) fi.close() fo.close() # copy mode and timestamp st = os.stat(infile) os.chmod(outfile, stat.S_IMODE(st[stat.ST_MODE])) os.utime(outfile, (st[stat.ST_ATIME], st[stat.ST_MTIME])) print "original", "=>" print "mode", oct(stat.S_IMODE(st[stat.ST_MODE])) print "atime", time.ctime(st[stat.ST_ATIME]) print "mtime", time.ctime(st[stat.ST_MTIME]) print "copy", "=>" st = os.stat(outfile) print "mode", oct(stat.S_IMODE(st[stat.ST_MODE])) print "atime", time.ctime(st[stat.ST_ATIME]) print "mtime", time.ctime(st[stat.ST_MTIME]) original => mode 0666 atime Thu Oct 14 15:15:50 1999 mtime Mon Nov 13 15:42:36 1995 copy => mode 0666 atime Thu Oct 14 15:15:50 1999 mtime Mon Nov 13 15:42:36 1995

1.4.4. 處理程式

system 函式在當前程式下執行一個新命令, 並等待它完成, 如 Example 1-34 所示.

1.4.4.1. Example 1-34. 使用 os 執行作業系統命令

File: os-example-8.py import os if os.name == "nt": command = "dir" else: command = "ls -l" os.system(command) -rwxrw-r-- 1 effbot effbot 76 Oct 9 14:17 README -rwxrw-r-- 1 effbot effbot 1727 Oct 7 19:00 SimpleAsyncHTTP.py -rwxrw-r-- 1 effbot effbot 314 Oct 7 20:29 aifc-example-1.py -rwxrw-r-- 1 effbot effbot 259 Oct 7 20:38 anydbm-example-1.py ...

命令通過作業系統的標準 shell 執行, 並返回 shell 的退出狀態. 需要注意的是在 Windows 95/98 下, shell 通常是 command.com , 它的推出狀態總是 0.

由於 11os.system11 直接將命令傳遞給 shell , 所以如果你不檢查傳入引數的時候會很危險 (比如命令 os.system("viewer %s" % file), 將 file 變數設定為 "sample.jpg; rm -rf $HOME" ....). 如果不確定引數的安全性, 那麼最好使用 exec 或 spawn 代替(稍後介紹).

exec 函式會使用新程式替換當前程式(或者說是"轉到程式"). 在 Example 1-35 中, 字串 "goodbye" 永遠不會被列印.

1.4.4.2. Example 1-35. 使用 os 模組啟動新程式

File: os-exec-example-1.py import os import sys program = "python" arguments = ["hello.py"] print os.execvp(program, (program,) + tuple(arguments)) print "goodbye" hello again, and welcome to the show

Python 提供了很多表現不同的 exec 函式. Example 1-35 使用的是 execvp 函式, 它會從標準路徑搜尋執行程式, 把第二個引數(元組)作為單獨的引數傳遞給程式, 並使用當前的環境變數來執行程式. 其他七個同型別函式請參閱 Python Library Reference .

在 Unix 環境下, 你可以通過組合使用 exec , fork 以及 wait 函式來從當前程式呼叫另一個程式, 如 Example 1-36 所示. fork 函式複製當前程式, wait函式會等待一個子程式執行結束.

1.4.4.3. Example 1-36. 使用 os 模組呼叫其他程式 (Unix)

File: os-exec-example-2.py import os import sys def run(program, *args): pid = os.fork() if not pid: os.execvp(program, (program,) + args) return os.wait()[0] run("python", "hello.py") print "goodbye" hello again, and welcome to the show goodbye

fork 函式在子程式返回中返回 0 (這個程式首先從 fork 返回值), 在父程式中返回一個非 0 的程式識別符號(子程式的 PID ). 也就是說, 只有當我們處於子程式的時候 "not pid" 才為真.

fork 和 wait 函式在 Windows 上是不可用的, 但是你可以使用 spawn 函式, 如 Example 1-37 所示. 不過, spawn 不會沿著路徑搜尋可執行檔案, 你必須自己處理好這些.

1.4.4.4. Example 1-37. 使用 os 模組呼叫其他程式 (Windows)

File: os-spawn-example-1.py import os import string def run(program, *args): # find executable for path in string.split(os.environ["PATH"], os.pathsep): file = os.path.join(path, program) + ".exe" try: return os.spawnv(os.P_WAIT, file, (file,) + args) except os.error: pass raise os.error, "cannot find executable" run("python", "hello.py") print "goodbye" hello again, and welcome to the show goodbye

spawn 函式還可用於在後臺執行一個程式. Example 1-38 給 run 函式新增了一個可選的 mode 引數; 當設定為 os.P_NOWAIT 時, 這個指令碼不會等待子程式結束, 預設值 os.P_WAIT 時 spawn 會等待子程式結束.

其它的標誌常量還有 os.P_OVERLAY ,它使得 spawn 的行為和 exec 類似, 以及 os.P_DETACH , 它在後臺執行子程式, 與當前控制檯和鍵盤焦點隔離.

1.4.4.5. Example 1-38. 使用 os 模組在後臺執行程式 (Windows)

File: os-spawn-example-2.py import os import string def run(program, *args, **kw): # find executable mode = kw.get("mode", os.P_WAIT) for path in string.split(os.environ["PATH"], os.pathsep): file = os.path.join(path, program) + ".exe" try: return os.spawnv(mode, file, (file,) + args) except os.error: pass raise os.error, "cannot find executable" run("python", "hello.py", mode=os.P_NOWAIT) print "goodbye" goodbye hello again, and welcome to the show

Example 1-39 提供了一個在 Unix 和 Windows 平臺上通用的 spawn 方法.

1.4.4.6. Example 1-39. 使用 spawn 或 fork/exec 呼叫其他程式

File: os-spawn-example-3.py import os import string if os.name in ("nt", "dos"): exefile = ".exe" else: exefile = "" def spawn(program, *args): try: # possible 2.0 shortcut! return os.spawnvp(program, (program,) + args) except AttributeError: pass try: spawnv = os.spawnv except AttributeError: # assume it's unix pid = os.fork() if not pid: os.execvp(program, (program,) + args) return os.wait()[0] else: # got spawnv but no spawnp: go look for an executable for path in string.split(os.environ["PATH"], os.pathsep): file = os.path.join(path, program) + exefile try: return spawnv(os.P_WAIT, file, (file,) + args) except os.error: pass raise IOError, "cannot find executable" # # try it out! spawn("python", "hello.py") print "goodbye" hello again, and welcome to the show goodbye

Example 1-39 首先嚐試呼叫 spawnvp 函式. 如果該函式不存在 (一些版本/平臺沒有這個函式), 它將繼續查詢一個名為 spawnv 的函式並且 開始查詢程式路徑. 作為最後的選擇, 它會呼叫 exec 和 fork 函式完成工作.

1.4.5. 處理守護程式(Daemon Processes)

Unix 系統中, 你可以使用 fork 函式把當前程式轉入後臺(一個"守護者/daemon"). 一般來說, 你需要派生(fork off)一個當前程式的副本, 然後終止原程式, 如Example 1-40 所示.

1.4.5.1. Example 1-40. 使用 os 模組使指令碼作為守護執行 (Unix)

File: os-example-14.py import os import time pid = os.fork() if pid: os._exit(0) # kill original print "daemon started" time.sleep(10) print "daemon terminated"

需要建立一個真正的後臺程式稍微有點複雜, 首先呼叫 setpgrp 函式建立一個 "程式組首領/process group leader". 否則, 向無關程式組傳送的訊號(同時)會引起守護程式的問題:

os.setpgrp()

為了確保守護程式建立的檔案能夠獲得程式指定的 mode flags(許可權模式標記?), 最好刪除 user mode mask:

os.umask(0)

然後, 你應該重定向 stdout/stderr 檔案, 而不能只是簡單地關閉它們(如果你的程式需要 stdout 或 stderr 寫入內容的時候, 可能會出現意想不到的問題).

class NullDevice: def write(self, s): pass sys.stdin.close() sys.stdout = NullDevice() sys.stderr = NullDevice()

換言之, 由於 Python 的 print 和 C 中的 printf/fprintf 在裝置(device) 沒有連線後不會關閉你的程式, 此時守護程式中的 sys.stdout.write() 會丟擲一個 IOError 異常, 而你的程式依然在後臺執行的很好....

另外, 先前例子中的 _exit 函式會終止當前程式. 而 sys.exit 不同, 如果呼叫者(caller) 捕獲了 SystemExit 異常, 程式仍然會繼續執行. 如 Example 1-41所示.

1.4.5.2. Example 1-41. 使用 os 模組終止當前程式

File: os-example-9.py import os import sys try: sys.exit(1) except SystemExit, value: print "caught exit(%s)" % value try: os._exit(2) except SystemExit, value: print "caught exit(%s)" % value print "bye!" caught exit(1)

1.5. os.path 模組

os.path 模組包含了各種處理長檔名(路徑名)的函式. 先匯入 (import) os 模組, 然後就可以以 os.path 訪問該模組.

1.5.1. 處理檔名

os.path 模組包含了許多與平臺無關的處理長檔名的函式. 也就是說, 你不需要處理前後斜槓, 冒號等. 我們可以看看 Example 1-42 中的樣例程式碼.

1.5.1.1. Example 1-42. 使用 os.path 模組處理檔名

File: os-path-example-1.py import os filename = "my/little/pony" print "using", os.name, "..." print "split", "=>", os.path.split(filename) print "splitext", "=>", os.path.splitext(filename) print "dirname", "=>", os.path.dirname(filename) print "basename", "=>", os.path.basename(filename) print "join", "=>", os.path.join(os.path.dirname(filename), os.path.basename(filename)) using nt ... split => ('my/little', 'pony') splitext => ('my/little/pony', '') dirname => my/little basename => pony join => my/little/pony

注意這裡的 split 只分割出最後一項(不帶斜槓).

os.path 模組中還有許多函式允許你簡單快速地獲知檔名的一些特徵,如 Example 1-43 所示。

1.5.1.2. Example 1-43. 使用 os.path 模組檢查檔名的特徵

File: os-path-example-2.py import os FILES = ( os.curdir, "/", "file", "/file", "samples", "samples/sample.jpg", "directory/file", "../directory/file", "/directory/file" ) for file in FILES: print file, "=>", if os.path.exists(file): print "EXISTS", if os.path.isabs(file): print "ISABS", if os.path.isdir(file): print "ISDIR", if os.path.isfile(file): print "ISFILE", if os.path.islink(file): print "ISLINK", if os.path.ismount(file): print "ISMOUNT", print . => EXISTS ISDIR / => EXISTS ISABS ISDIR ISMOUNT file => /file => ISABS samples => EXISTS ISDIR samples/sample.jpg => EXISTS ISFILE directory/file => ../directory/file => /directory/file => ISABS

expanduser 函式以與大部分Unix shell相同的方式處理使用者名稱快捷符號(~, 不過在 Windows 下工作不正常), 如 Example 1-44 所示.

1.5.1.3. Example 1-44. 使用 os.path 模組將使用者名稱插入到檔名

File: os-path-expanduser-example-1.py import os print os.path.expanduser("~/.pythonrc") # /home/effbot/.pythonrc

expandvars 函式將檔名中的環境變數替換為對應值, 如 Example 1-45 所示.

1.5.1.4. Example 1-45. 使用 os.path 替換檔名中的環境變數

File: os-path-expandvars-example-1.py import os os.environ["USER"] = "user" print os.path.expandvars("/home/$USER/config") print os.path.expandvars("$USER/folders") /home/user/config user/folders

1.5.2. 搜尋檔案系統

walk 函式會幫你找出一個目錄樹下的所有檔案 (如 Example 1-46 所示). 它的引數依次是目錄名, 回撥函式, 以及傳遞給回撥函式的資料物件.

1.5.2.1. Example 1-46. 使用 os.path 搜尋檔案系統

File: os-path-walk-example-1.py import os def callback(arg, directory, files): for file in files: print os.path.join(directory, file), repr(arg) os.path.walk(".", callback, "secret message") ./aifc-example-1.py 'secret message' ./anydbm-example-1.py 'secret message' ./array-example-1.py 'secret message' ... ./samples 'secret message' ./samples/sample.jpg 'secret message' ./samples/sample.txt 'secret message' ./samples/sample.zip 'secret message' ./samples/articles 'secret message' ./samples/articles/article-1.txt 'secret message' ./samples/articles/article-2.txt 'secret message' ...

walk 函式的介面多少有點晦澀 (也許只是對我個人而言, 我總是記不住引數的順序). Example 1-47 中展示的 index 函式會返回一個檔名列表, 你可以直接使用 for-in 迴圈處理檔案.

1.5.2.2. Example 1-47. 使用 os.listdir 搜尋檔案系統

File: os-path-walk-example-2.py import os def index(directory): # like os.listdir, but traverses directory trees stack = [directory] files = [] while stack: directory = stack.pop() for file in os.listdir(directory): fullname = os.path.join(directory, file) files.append(fullname) if os.path.isdir(fullname) and not os.path.islink(fullname): stack.append(fullname) return files for file in index("."): print file ./aifc-example-1.py ./anydbm-example-1.py ./array-example-1.py ...

如果你不想列出所有的檔案 (基於效能或者是記憶體的考慮) , Example 1-48 展示了另一種方法. 這裡 DirectoryWalker 類的行為與序列物件相似, 一次返回一個檔案. (generator?)

1.5.2.3. Example 1-48. 使用 DirectoryWalker 搜尋檔案系統

File: os-path-walk-example-3.py import os class DirectoryWalker: # a forward iterator that traverses a directory tree def _ _init_ _(self, directory): self.stack = [directory] self.files = [] self.index = 0 def _ _getitem_ _(self, index): while 1: try: file = self.files[self.index] self.index = self.index + 1 except IndexError: # pop next directory from stack self.directory = self.stack.pop() self.files = os.listdir(self.directory) self.index = 0 else: # got a filename fullname = os.path.join(self.directory, file) if os.path.isdir(fullname) and not os.path.islink(fullname): self.stack.append(fullname) return fullname for file in DirectoryWalker("."): print file ./aifc-example-1.py ./anydbm-example-1.py ./array-example-1.py ...

注意 DirectoryWalker 類並不檢查傳遞給 _ _getitem_ _ 方法的索引值. 這意味著如果你越界訪問序列成員(索引數字過大)的話, 這個類將不能正常工作.

最後, 如果你需要處理檔案大小和時間戳, Example 1-49 給出了一個類, 它返回檔名和它的 os.stat 屬性(一個元組). 這個版本在每個檔案上都能節省一次或兩次 stat 呼叫( os.path.isdir 和 os.path.islink 內部都使用了 stat ), 並且在一些平臺上執行很快.

1.5.2.4. Example 1-49. 使用 DirectoryStatWalker 搜尋檔案系統

File: os-path-walk-example-4.py import os, stat class DirectoryStatWalker: # a forward iterator that traverses a directory tree, and # returns the filename and additional file information def _ _init_ _(self, directory): self.stack = [directory] self.files = [] self.index = 0 def _ _getitem_ _(self, index): while 1: try: file = self.files[self.index] self.index = self.index + 1 except IndexError: # pop next directory from stack self.directory = self.stack.pop() self.files = os.listdir(self.directory) self.index = 0 else: # got a filename fullname = os.path.join(self.directory, file) st = os.stat(fullname) mode = st[stat.ST_MODE] if stat.S_ISDIR(mode) and not stat.S_ISLNK(mode): self.stack.append(fullname) return fullname, st for file, st in DirectoryStatWalker("."): print file, st[stat.ST_SIZE] ./aifc-example-1.py 336 ./anydbm-example-1.py 244 ./array-example-1.py 526

1.6. stat 模組

Example 1-50 展示了 stat 模組的基本用法, 這個模組包含了一些 os.stat 函式中可用的常量和測試函式.

1.6.0.1. Example 1-50. Using the stat Module

File: stat-example-1.py import stat import os, time st = os.stat("samples/sample.txt") print "mode", "=>", oct(stat.S_IMODE(st[stat.ST_MODE])) print "type", "=>", if stat.S_ISDIR(st[stat.ST_MODE]): print "DIRECTORY", if stat.S_ISREG(st[stat.ST_MODE]): print "REGULAR", if stat.S_ISLNK(st[stat.ST_MODE]): print "LINK", print print "size", "=>", st[stat.ST_SIZE] print "last accessed", "=>", time.ctime(st[stat.ST_ATIME]) print "last modified", "=>", time.ctime(st[stat.ST_MTIME]) print "inode changed", "=>", time.ctime(st[stat.ST_CTIME]) mode => 0664 type => REGULAR size => 305 last accessed => Sun Oct 10 22:12:30 1999 last modified => Sun Oct 10 18:39:37 1999 inode changed => Sun Oct 10 15:26:38 1999

1.7. string 模組

string 模組提供了一些用於處理字串型別的函式, 如 Example 1-51 所示.

1.7.0.1. Example 1-51. 使用 string 模組

File: string-example-1.py import string text = "Monty Python's Flying Circus" print "upper", "=>", string.upper(text) print "lower", "=>", string.lower(text) print "split", "=>", string.split(text) print "join", "=>", string.join(string.split(text), "+") print "replace", "=>", string.replace(text, "Python", "Java") print "find", "=>", string.find(text, "Python"), string.find(text, "Java") print "count", "=>", string.count(text, "n") upper => MONTY PYTHON'S FLYING CIRCUS lower => monty python's flying circus split => ['Monty', "Python's", 'Flying', 'Circus'] join => Monty+Python's+Flying+Circus replace => Monty Java's Flying Circus find => 6 -1 count => 3

在 Python 1.5.2 以及更早版本中, string 使用 strop 中的函式來實現模組功能.

在 Python1.6 和後繼版本,更多的字串操作都可以作為字串方法來訪問, 如 Example 1-52 所示, string 模組中的許多函式只是對相對應字串方法的封裝.

1.7.0.2. Example 1-52. 使用字串方法替代 string 模組函式

File: string-example-2.py text = "Monty Python's Flying Circus" print "upper", "=>", text.upper() print "lower", "=>", text.lower() print "split", "=>", text.split() print "join", "=>", "+".join(text.split()) print "replace", "=>", text.replace("Python", "Perl") print "find", "=>", text.find("Python"), text.find("Perl") print "count", "=>", text.count("n")upper => MONTY PYTHON'S FLYING CIRCUS lower => monty python's flying circus split => ['Monty', "Python's", 'Flying', 'Circus'] join => Monty+Python's+Flying+Circus replace => Monty Perl's Flying Circus find => 6 -1 count => 3

為了增強模組對字元的處理能力, 除了字串方法, string 模組還包含了型別轉換函式用於把字串轉換為其他型別, (如 Example 1-53 所示).

1.7.0.3. Example 1-53. 使用 string 模組將字串轉為數字

File: string-example-3.py import string print int("4711"), print string.atoi("4711"), print string.atoi("11147", 8), # octal 八進位制 print string.atoi("1267", 16), # hexadecimal 十六進位制 print string.atoi("3mv", 36) # whatever... print string.atoi("4711", 0), print string.atoi("04711", 0), print string.atoi("0x4711", 0) print float("4711"), print string.atof("1"), print string.atof("1.23e5") 4711 4711 4711 4711 4711 4711 2505 18193 4711.0 1.0 123000.0

大多數情況下 (特別是當你使用的是1.6及更高版本時) ,你可以使用 int 和 float 函式代替 string 模組中對應的函式。

atoi 函式可以接受可選的第二個引數, 指定數基(number base). 如果數基為 0, 那麼函式將檢查字串的前幾個字元來決定使用的數基: 如果為 "0x," 數基將為 16 (十六進位制), 如果為 "0," 則數基為 8 (八進位制). 預設數基值為 10 (十進位制), 當你未傳遞引數時就使用這個值.

在 1.6 及以後版本中, int 函式和 atoi 一樣可以接受第二個引數. 與字串版本函式不一樣的是 , int 和 float 可以接受 Unicode 字串物件.


1.8. re 模組

"Some people, when confronted with a problem, think 'I know, I'll use regular expressions.' Now they have two problems."
- Jamie Zawinski, on comp.lang.emacs

re 模組提供了一系列功能強大的正規表示式 (regular expression) 工具, 它們允許你快速檢查給定字串是否與給定的模式匹配 (使用 match 函式), 或者包含這個模式 (使用 search 函式). 正規表示式是以緊湊(也很神祕)的語法寫出的字串模式.

match 嘗試從字串的起始匹配一個模式, 如 Example 1-54 所示. 如果模式匹配了某些內容 (包括空字串, 如果模式允許的話) , 它將返回一個匹配物件. 使用它的 group 方法可以找出匹配的內容.

1.8.0.1. Example 1-54. 使用 re 模組來匹配字串

File: re-example-1.py import re text = "The Attila the Hun Show" # a single character 單個字元 m = re.match(".", text) if m: print repr("."), "=>", repr(m.group(0)) # any string of characters 任何字串 m = re.match(".*", text) if m: print repr(".*"), "=>", repr(m.group(0)) # a string of letters (at least one) 只包含字母的字串(至少一個) m = re.match("/w+", text) if m: print repr("/w+"), "=>", repr(m.group(0)) # a string of digits 只包含數字的字串 m = re.match("/d+", text) if m: print repr("/d+"), "=>", repr(m.group(0)) '.' => 'T' '.*' => 'The Attila the Hun Show' '//w+' => 'The'

可以使用圓括號在模式中標記區域. 找到匹配後, group 方法可以抽取這些區域的內容, 如 Example 1-55 所示. group(1) 會返回第一組的內容,group(2) 返回第二組的內容, 這樣... 如果你傳遞多個組數給 group 函式, 它會返回一個元組.

1.8.0.2. Example 1-55. 使用 re 模組抽出匹配的子字串

File: re-example-2.py import re text ="10/15/99" m = re.match("(/d{2})/(/d{2})/(/d{2,4})", text) if m: print m.group(1, 2, 3)('10', '15', '99')

search 函式會在字串內查詢模式匹配, 如 Example 1-56 所示. 它在所有可能的字元位置嘗試匹配模式, 從最左邊開始, 一旦找到匹配就返回一個匹配物件. 如果沒有找到相應的匹配, 就返回 None .

1.8.0.3. Example 1-56. 使用 re 模組搜尋子字串

File: re-example-3.py import re text = "Example 3: There is 1 date 10/25/95 in here!" m = re.search("(/d{1,2})/(/d{1,2})/(/d{2,4})", text) print m.group(1), m.group(2), m.group(3) month, day, year = m.group(1, 2, 3) print month, day, year date = m.group(0) print date 10 25 95 10 25 95 10/25/95

Example 1-57 中展示了 sub 函式, 它可以使用另個字串替代匹配模式.

1.8.0.4. Example 1-57. 使用 re 模組替換子字串

File: re-example-4.py import re text = "you're no fun anymore..." # literal replace (string.replace is faster) # 文字替換 (string.replace 速度更快) print re.sub("fun", "entertaining", text) # collapse all non-letter sequences to a single dash  # 將所有非字母序列轉換為一個"-"(dansh,破折號) print re.sub("[^/w]+", "-", text) # convert all words to beeps  # 將所有單詞替換為 BEEP print re.sub("/S+", "-BEEP-", text) you're no entertaining anymore... you-re-no-fun-anymore- -BEEP- -BEEP- -BEEP- -BEEP-

你也可以通過回撥 (callback) 函式使用 sub 來替換指定模式. Example 1-58 展示瞭如何預編譯模式.

1.8.0.5. Example 1-58. 使用 re 模組替換字串(通過回撥函式)

File: re-example-5.py import re import string text = "a line of text//012another line of text//012etc..." def octal(match): # replace octal code with corresponding ASCII character # 使用對應 ASCII 字元替換八進位制程式碼 return chr(string.atoi(match.group(1), 8)) octal_pattern = re.compile(r"//(/d/d/d)") print text print octal_pattern.sub(octal, text) a line of text/012another line of text/012etc... a line of text another line of text etc...

如果你不編譯, re 模組會為你快取一個編譯後版本, 所有的小指令碼中, 通常不需要編譯正規表示式. Python1.5.2 中, 快取中可以容納 20 個匹配模式, 而在 2.0 中, 快取則可以容納 100 個匹配模式.

最後, Example 1-59 用一個模式列表匹配一個字串. 這些模式將會組合為一個模式, 並預編譯以節省時間.

1.8.0.6. Example 1-59. 使用 re 模組匹配多個模式中的一個

File: re-example-6.py import re, string def combined_pattern(patterns): p = re.compile( string.join(map(lambda x: "("+x+")", patterns), "|") ) def fixup(v, m=p.match, r=range(0,len(patterns))): try: regs = m(v).regs except AttributeError: return None # no match, so m.regs will fail else: for i in r: if regs[i+1] != (-1, -1): return i return fixup # # try it out! patterns = [ r"/d+", r"abc/d{2,4}", r"p/w+" ] p = combined_pattern(patterns) print p("129391") print p("abc800") print p("abc1600") print p("python") print p("perl") print p("tcl") 0 1 1 2 2 None

1.9. math 模組

math 模組實現了許多對浮點數的數學運算函式. 這些函式一般是對平臺 C 庫中同名函式的簡單封裝, 所以一般情況下, 不同平臺下計算的結果可能稍微地有所不同, 有時候甚至有很大出入. Example 1-60 展示瞭如何使用 math 模組.

1.9.0.1. Example 1-60. 使用 math 模組

File: math-example-1.py import math print "e", "=>", math.e print "pi", "=>", math.pi print "hypot", "=>", math.hypot(3.0, 4.0) # and many others... e => 2.71828182846 pi => 3.14159265359 hypot => 5.0

完整函式列表請參閱 Python Library Reference .


1.10. cmath 模組

Example 1-61 所展示的 cmath 模組包含了一些用於複數運算的函式.

1.10.0.1. Example 1-61. 使用 cmath 模組

File: cmath-example-1.py import cmath print "pi", "=>", cmath.pi print "sqrt(-1)", "=>", cmath.sqrt(-1) pi => 3.14159265359 sqrt(-1) => 1j

完整函式列表請參閱 Python Library Reference .


1.11. operator 模組

operator 模組為 Python 提供了一個 "功能性" 的標準操作符介面. 當使用 map 以及 filter 一類的函式的時候, operator 模組中的函式可以替換一些lambda 函式. 而且這些函式在一些喜歡寫晦澀程式碼的程式設計師中很流行. Example 1-62 展示了 operator 模組的一般用法.

1.11.0.1. Example 1-62. 使用 operator 模組

File: operator-example-1.py import operator sequence = 1, 2, 4 print "add", "=>", reduce(operator.add, sequence) print "sub", "=>", reduce(operator.sub, sequence) print "mul", "=>", reduce(operator.mul, sequence) print "concat", "=>", operator.concat("spam", "egg") print "repeat", "=>", operator.repeat("spam", 5) print "getitem", "=>", operator.getitem(sequence, 2) print "indexOf", "=>", operator.indexOf(sequence, 2) print "sequenceIncludes", "=>", operator.sequenceIncludes(sequence, 3) add => 7 sub => -5 mul => 8 concat => spamegg repeat => spamspamspamspamspam getitem => 4 indexOf => 1 sequenceIncludes => 0

Example 1-63 展示了一些可以用於檢查物件型別的 operator 函式.

1.11.0.2. Example 1-63. 使用 operator 模組檢查型別

File: operator-example-2.py import operator import UserList def dump(data): print type(data), "=>", if operator.isCallable(data): print "CALLABLE", if operator.isMappingType(data): print "MAPPING", if operator.isNumberType(data): print "NUMBER", if operator.isSequenceType(data): print "SEQUENCE", print dump(0) dump("string") dump("string"[0]) dump([1, 2, 3]) dump((1, 2, 3)) dump({"a": 1}) dump(len) # function 函式 dump(UserList) # module 模組 dump(UserList.UserList) # class 類 dump(UserList.UserList()) # instance 例項 <type 'int'> => NUMBER <type 'string'> => SEQUENCE <type 'string'> => SEQUENCE <type 'list'> => SEQUENCE <type 'tuple'> => SEQUENCE <type 'dictionary'> => MAPPING <type 'builtin_function_or_method'> => CALLABLE <type 'module'> => <type 'class'> => CALLABLE <type 'instance'> => MAPPING NUMBER SEQUENCE

這裡需要注意 operator 模組使用非常規的方法處理物件例項. 所以使用 isNumberType , isMappingType , 以及 isSequenceType 函式的時候要小心, 這很容易降低程式碼的擴充套件性.

同樣需要注意的是一個字串序列成員 (單個字元) 也是序列. 所以當在遞迴函式使用 isSequenceType 來截斷物件樹的時候, 別把普通字串作為引數(或者是任何包含字串的序列物件).


1.12. copy 模組

copy 模組包含兩個函式, 用來拷貝物件, 如 Example 1-64 所示.

copy(object) => object 建立給定物件的 "淺/淺層(shallow)" 拷貝(copy). 這裡 "淺/淺層(shallow)" 的意思是複製物件本身, 但當物件是一個容器 (Container) 時, 它的成員仍然指向原來的成員物件.

1.12.0.1. Example 1-64. 使用 copy 模組複製物件

File: copy-example-1.py import copy a = [[1],[2],[3]] b = copy.copy(a) print "before", "=>" print a print b # modify original a[0][0] = 0 a[1] = None print "after", "=>" print a print b before => [[1], [2], [3]] [[1], [2], [3]] after => [[0], None, [3]] [[0], [2], [3]]

你也可以使用[:]語句 (完整切片) 來對列表進行淺層複製, 也可以使用 copy 方法複製字典.

相反地, deepcopy(object) => object 建立一個物件的深層拷貝(deepcopy), 如 Example 1-65 所示, 當物件為一個容器時, 所有的成員都被遞迴地複製了。

1.12.0.2. Example 1-65. 使用 copy 模組複製集合(Collections)

File: copy-example-2.py import copy a = [[1],[2],[3]] b = copy.deepcopy(a) print "before", "=>" print a print b # modify original a[0][0] = 0 a[1] = None print "after", "=>" print a print b before => [[1], [2], [3]] [[1], [2], [3]] after => [[0], None, [3]] [[1], [2], [3]]

1.13. sys 模組

sys 模組提供了許多函式和變數來處理 Python 執行時環境的不同部分.

1.13.1. 處理命令列引數

在直譯器啟動後, argv 列表包含了傳遞給指令碼的所有引數, 如 Example 1-66 所示. 列表的第一個元素為指令碼自身的名稱.

1.13.1.1. Example 1-66. 使用sys模組獲得指令碼的引數

File: sys-argv-example-1.py import sys print "script name is", sys.argv[0] if len(sys.argv) > 1: print "there are", len(sys.argv)-1, "arguments:" for arg in sys.argv[1:]: print arg else: print "there are no arguments!" script name is sys-argv-example-1.py there are no arguments!

如果是從標準輸入讀入指令碼 (比如 "python < sys-argv-example-1.py"), 指令碼的名稱將被設定為空串. 如果把指令碼作為字串傳遞給python (使用 -c選項), 指令碼名會被設定為 "-c".

1.13.2. 處理模組

path 列表是一個由目錄名構成的列表, Python 從中查詢擴充套件模組( Python 源模組, 編譯模組,或者二進位制擴充套件). 啟動 Python 時,這個列表從根據內建規則, PYTHONPATH 環境變數的內容, 以及登錄檔( Windows 系統)等進行初始化. 由於它只是一個普通的列表, 你可以在程式中對它進行操作, 如 Example 1-67所示.

1.13.2.1. Example 1-67. 使用sys模組操作模組搜尋路徑

File: sys-path-example-1.py import sys print "path has", len(sys.path), "members" # add the sample directory to the path sys.path.insert(0, "samples") import sample # nuke the path sys.path = [] import random # oops! path has 7 members this is the sample module! Traceback (innermost last): File "sys-path-example-1.py", line 11, in ? import random # oops! ImportError: No module named random

builtin_module_names 列表包含 Python 直譯器中所有內建模組的名稱, Example 1-68 給出了它的樣例程式碼.

1.13.2.2. Example 1-68. 使用sys模組查詢內建模組

File: sys-builtin-module-names-example-1.py import sys def dump(module): print module, "=>", if module in sys.builtin_module_names: print "<BUILTIN>" else: module = _ _import_ _(module) print module._ _file_ _ dump("os") dump("sys") dump("string") dump("strop") dump("zlib") os => C:/python/lib/os.pyc sys => <BUILTIN> string => C:/python/lib/string.pyc strop => <BUILTIN> zlib => C:/python/zlib.pyd

modules 字典包含所有載入的模組. import 語句在從磁碟匯入內容之前會先檢查這個字典.

正如你在 Example 1-69 中所見到的, Python 在處理你的指令碼之前就已經匯入了很多模組.

1.13.2.3. Example 1-69. 使用sys模組查詢已匯入的模組

File: sys-modules-example-1.py import sys print sys.modules.keys() ['os.path', 'os', 'exceptions', '_ _main_ _', 'ntpath', 'strop', 'nt', 'sys', '_ _builtin_ _', 'site', 'signal', 'UserDict', 'string', 'stat']

1.13.3. 處理引用記數

getrefcount 函式 (如 Example 1-70 所示) 返回給定物件的引用記數 - 也就是這個物件使用次數. Python 會跟蹤這個值, 當它減少為0的時候, 就銷燬這個物件.

1.13.3.1. Example 1-70. 使用sys模組獲得引用記數

File: sys-getrefcount-example-1.py import sys variable = 1234 print sys.getrefcount(0) print sys.getrefcount(variable) print sys.getrefcount(None) 50 3 192

注意這個值總是比實際的數量大, 因為該函式本身在確定這個值的時候依賴這個物件.

== 檢查主機平臺===

Example 1-71 展示了 platform 變數, 它包含主機平臺的名稱.

1.13.3.2. Example 1-71. 使用sys模組獲得當前平臺

File: sys-platform-example-1.py import sys # # emulate "import os.path" (sort of)... if sys.platform == "win32": import ntpath pathmodule = ntpath elif sys.platform == "mac": import macpath pathmodule = macpath else: # assume it's a posix platform import posixpath pathmodule = posixpath print pathmodule

典型的平臺有Windows 9X/NT(顯示為 win32 ), 以及 Macintosh(顯示為 mac ) . 對於 Unix 系統而言, platform 通常來自 "uname -r" 命令的輸出, 例如irix6 , linux2 , 或者 sunos5 (Solaris).

1.13.4. 跟蹤程式

setprofiler 函式允許你配置一個分析函式(profiling function). 這個函式會在每次呼叫某個函式或方法時被呼叫(明確或隱含的), 或是遇到異常的時候被呼叫. 讓我們看看 Example 1-72 的程式碼.

1.13.4.1. Example 1-72. 使用sys模組配置分析函式

File: sys-setprofiler-example-1.py import sys def test(n): j = 0 for i in range(n): j = j + i return n def profiler(frame, event, arg): print event, frame.f_code.co_name, frame.f_lineno, "->", arg # profiler is activated on the next call, return, or exception # 分析函式將在下次函式呼叫, 返回, 或異常時啟用 sys.setprofile(profiler) # profile this function call # 分析這次函式呼叫 test(1) # disable profiler # 禁用分析函式 sys.setprofile(None) # don't profile this call # 不會分析這次函式呼叫 test(2) call test 3 -> None return test 7 -> 1

基於該函式, profile 模組提供了一個完整的分析器框架.

Example 1-73 中的 settrace 函式與此類似, 但是 trace 函式會在直譯器每執行到新的一行時被呼叫.

1.13.4.2. Example 1-73. 使用sys模組配置單步跟蹤函式

File: sys-settrace-example-1.py import sys def test(n): j = 0 for i in range(n): j = j + i return n def tracer(frame, event, arg): print event, frame.f_code.co_name, frame.f_lineno, "->", arg return tracer # tracer is activated on the next call, return, or exception # 跟蹤器將在下次函式呼叫, 返回, 或異常時啟用 sys.settrace(tracer) # trace this function call # 跟蹤這次函式呼叫 test(1) # disable tracing # 禁用跟蹤器 sys.settrace(None) # don't trace this call # 不會跟蹤這次函式呼叫 test(2) call test 3 -> None line test 3 -> None line test 4 -> None line test 5 -> None line test 5 -> None line test 6 -> None line test 5 -> None line test 7 -> None return test 7 -> 1

基於該函式提供的跟蹤功能, pdb 模組提供了完整的除錯( debug )框架.

1.13.5. 處理標準輸出/輸入

stdin , stdout , 以及 stderr 變數包含與標準 I/O 流對應的流物件. 如果需要更好地控制輸出,而 print 不能滿足你的要求, 它們就是你所需要的. 你也可以 替換 它們, 這時候你就可以重定向輸出和輸入到其它裝置( device ), 或者以非標準的方式處理它們. 如 Example 1-74 所示.

1.13.5.1. Example 1-74. 使用sys重定向輸出

File: sys-stdout-example-1.py import sys import string class Redirect: def _ _init_ _(self, stdout): self.stdout = stdout def write(self, s): self.stdout.write(string.lower(s)) # redirect standard output (including the print statement) # 重定向標準輸出(包括print語句) old_stdout = sys.stdout sys.stdout = Redirect(sys.stdout) print "HEJA SVERIGE", print "FRISKT HUM/303/226R" # restore standard output # 恢復標準輸出 sys.stdout = old_stdout print "M/303/205/303/205/303/205/303/205L!" heja sverige friskt hum/303/266r M/303/205/303/205/303/205/303/205L!

要重定向輸出只要建立一個物件, 並實現它的 write 方法.

(除非 C 型別的例項外:Python 使用一個叫做 softspace 的整數屬性來控制輸出中的空白. 如果沒有這個屬性, Python 將把這個屬性附加到這個物件上. 你不需要在使用 Python 物件時擔心, 但是在重定向到一個 C 型別時, 你應該確保該型別支援 softspace 屬性.)

1.13.6. 退出程式

執行至主程式的末尾時,直譯器會自動退出. 但是如果需要中途退出程式, 你可以呼叫 sys.exit 函式, 它帶有一個可選的整數引數返回給呼叫它的程式.Example 1-75 給出了範例.

1.13.6.1. Example 1-75. 使用sys模組退出程式

File: sys-exit-example-1.py import sys print "hello" sys.exit(1) print "there" hello

注意 sys.exit 並不是立即退出. 而是引發一個 SystemExit 異常. 這意味著你可以在主程式中捕獲對 sys.exit 的呼叫, 如 Example 1-76 所示.

1.13.6.2. Example 1-76. 捕獲sys.exit呼叫

File: sys-exit-example-2.py import sys print "hello" try: sys.exit(1) except SystemExit: pass print "there" hello there

如果準備在退出前自己清理一些東西(比如刪除臨時檔案), 你可以配置一個 "退出處理函式"(exit handler), 它將在程式退出的時候自動被呼叫. 如 Example 1-77 所示.

1.13.6.3. Example 1-77. 另一種捕獲sys.exit呼叫的方法

File: sys-exitfunc-example-1.py import sys def exitfunc(): print "world" sys.exitfunc = exitfunc print "hello" sys.exit(1) print "there" # never printed # 不會被 print hello world

在 Python 2.0 以後, 你可以使用 atexit 模組來註冊多個退出處理函式.


1.14. atexit 模組

(用於2.0版本及以上) atexit 模組允許你註冊一個或多個終止函式(暫且這麼叫), 這些函式將在直譯器終止前被自動呼叫.

呼叫 register 函式, 便可以將函式註冊為終止函式, 如 Example 1-78 所示. 你也可以新增更多的引數, 這些將作為 exit 函式的引數傳遞.

1.14.0.1. Example 1-78. 使用 atexit 模組

File: atexit-example-1.py import atexit def exit(*args): print "exit", args # register two exit handler atexit.register(exit) atexit.register(exit, 1) atexit.register(exit, "hello", "world") exit ('hello', 'world') exit (1,) exit ()

該模組其實是一個對 sys.exitfunc 鉤子( hook )的簡單封裝.


1.15. time 模組

time 模組提供了一些處理日期和一天內時間的函式. 它是建立在 C 執行時庫的簡單封裝.

給定的日期和時間可以被表示為浮點型(從參考時間, 通常是 1970.1.1 到現在經過的秒數. 即 Unix 格式), 或者一個表示時間的 struct (類元組).

1.15.1. 獲得當前時間

Example 1-79 展示瞭如何使用 time 模組獲取當前時間.

1.15.1.1. Example 1-79. 使用 time 模組獲取當前時間

File: time-example-1.py import time now = time.time() print now, "seconds since", time.gmtime(0)[:6] print print "or in other words:" print "- local time:", time.localtime(now) print "- utc:", time.gmtime(now) 937758359.77 seconds since (1970, 1, 1, 0, 0, 0) or in other words: - local time: (1999, 9, 19, 18, 25, 59, 6, 262, 1) - utc: (1999, 9, 19, 16, 25, 59, 6, 262, 0)

localtime 和 gmtime 返回的類元組包括年, 月, 日, 時, 分, 秒, 星期, 一年的第幾天, 日游標誌. 其中年是一個四位數(在有千年蟲問題的平臺上另有規定, 但還是四位數), 星期從星期一(數字 0 代表)開始, 1月1日是一年的第一天.

1.15.2. 將時間值轉換為字串

你可以使用標準的格式化字串把時間物件轉換為字串, 不過 time 模組已經提供了許多標準轉換函式, 如 Example 1-80 所示.

1.15.2.1. Example 1-80. 使用 time 模組格式化時間輸出

File: time-example-2.py import time now = time.localtime(time.time()) print time.asctime(now) print time.strftime("%y/%m/%d %H:%M", now) print time.strftime("%a %b %d", now) print time.strftime("%c", now) print time.strftime("%I %p", now) print time.strftime("%Y-%m-%d %H:%M:%S %Z", now) # do it by hand... year, month, day, hour, minute, second, weekday, yearday, daylight = now print "%04d-%02d-%02d" % (year, month, day) print "%02d:%02d:%02d" % (hour, minute, second) print ("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN")[weekday], yearday Sun Oct 10 21:39:24 1999 99/10/10 21:39 Sun Oct 10 Sun Oct 10 21:39:24 1999 09 PM 1999-10-10 21:39:24 CEST 1999-10-10 21:39:24 SUN 283

1.15.3. 將字串轉換為時間物件

在一些平臺上, time 模組包含了 strptime 函式, 它的作用與 strftime 相反. 給定一個字串和模式, 它返回相應的時間物件, 如 Example 1-81 所示.

1.15.3.1. Example 1-81. 使用 time.strptime 函式解析時間

File: time-example-6.py import time # make sure we have a strptime function! # 確認有函式 strptime try: strptime = time.strptime except AttributeError: from strptime import strptime print strptime("31 Nov 00", "%d %b %y") print strptime("1 Jan 70 1:30pm", "%d %b %y %I:%M%p")

只有在系統的 C 庫提供了相應的函式的時候, time.strptime 函式才可以使用. 對於沒有提供標準實現的平臺, Example 1-82 提供了一個不完全的實現.

1.15.3.2. Example 1-82. strptime 實現

File: strptime.py import re import string MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] SPEC = { # map formatting code to a regular expression fragment "%a": "(?P<weekday>[a-z]+)", "%A": "(?P<weekday>[a-z]+)", "%b": "(?P<month>[a-z]+)", "%B": "(?P<month>[a-z]+)", "%C": "(?P<century>/d/d?)", "%d": "(?P<day>/d/d?)", "%D": "(?P<month>/d/d?)/(?P<day>/d/d?)/(?P<year>/d/d)", "%e": "(?P<day>/d/d?)", "%h": "(?P<month>[a-z]+)", "%H": "(?P<hour>/d/d?)", "%I": "(?P<hour12>/d/d?)", "%j": "(?P<yearday>/d/d?/d?)", "%m": "(?P<month>/d/d?)", "%M": "(?P<minute>/d/d?)", "%p": "(?P<ampm12>am|pm)", "%R": "(?P<hour>/d/d?):(?P<minute>/d/d?)", "%S": "(?P<second>/d/d?)", "%T": "(?P<hour>/d/d?):(?P<minute>/d/d?):(?P<second>/d/d?)", "%U": "(?P<week>/d/d)", "%w": "(?P<weekday>/d)", "%W": "(?P<weekday>/d/d)", "%y": "(?P<year>/d/d)", "%Y": "(?P<year>/d/d/d/d)", "%%": "%" } class TimeParser: def _ _init_ _(self, format): # convert strptime format string to regular expression format = string.join(re.split("(?:/s|%t|%n)+", format)) pattern = [] try: for spec in re.findall("%/w|%%|.", format): if spec[0] == "%": spec = SPEC[spec] pattern.append(spec) except KeyError: raise ValueError, "unknown specificer: %s" % spec self.pattern = re.compile("(?i)" + string.join(pattern, "")) def match(self, daytime): # match time string match = self.pattern.match(daytime) if not match: raise ValueError, "format mismatch" get = match.groupdict().get tm = [0] * 9 # extract date elements y = get("year") if y: y = int(y) if y < 68: y = 2000 + y elif y < 100: y = 1900 + y tm[0] = y m = get("month") if m: if m in MONTHS: m = MONTHS.index(m) + 1 tm[1] = int(m) d = get("day") if d: tm[2] = int(d) # extract time elements h = get("hour") if h: tm[3] = int(h) else: h = get("hour12") if h: h = int(h) if string.lower(get("ampm12", "")) == "pm": h = h + 12 tm[3] = h m = get("minute") if m: tm[4] = int(m) s = get("second") if s: tm[5] = int(s) # ignore weekday/yearday for now return tuple(tm) def strptime(string, format="%a %b %d %H:%M:%S %Y"): return TimeParser(format).match(string) if _ _name_ _ == "_ _main_ _": # try it out import time print strptime("2000-12-20 01:02:03", "%Y-%m-%d %H:%M:%S") print strptime(time.ctime(time.time())) (2000, 12, 20, 1, 2, 3, 0, 0, 0) (2000, 11, 15, 12, 30, 45, 0, 0, 0)

1.15.4. 轉換時間值

將時間元組轉換回時間值非常簡單, 至少我們談論的當地時間 (local time) 如此. 只要把時間元組傳遞給 mktime 函式, 如 Example 1-83 所示.

1.15.4.1. Example 1-83. 使用 time 模組將本地時間元組轉換為時間值(整數)

File: time-example-3.py import time t0 = time.time() tm = time.localtime(t0) print tm print t0 print time.mktime(tm) (1999, 9, 9, 0, 11, 8, 3, 252, 1) 936828668.16 936828668.0

但是, 1.5.2 版本的標準庫沒有提供能將 UTC 時間 (Universal Time, Coordinated: 特林威治標準時間)轉換為時間值的函式 ( Python 和對應底層 C 庫都沒有提供). Example 1-84 提供了該函式的一個 Python 實現, 稱為 timegm .

1.15.4.2. Example 1-84. 將 UTC 時間元組轉換為時間值(整數)

File: time-example-4.py import time def _d(y, m, d, days=(0,31,59,90,120,151,181,212,243,273,304,334,365)): # map a date to the number of days from a reference point return (((y - 1901)*1461)/4 + days[m-1] + d + ((m > 2 and not y % 4 and (y % 100 or not y % 400)) and 1)) def timegm(tm, epoch=_d(1970,1,1)): year, month, day, h, m, s = tm[:6] assert year >= 1970 assert 1 <= month <= 12 return (_d(year, month, day) - epoch)*86400 + h*3600 + m*60 + s t0 = time.time() tm = time.gmtime(t0) print tm print t0 print timegm(tm) (1999, 9, 8, 22, 12, 12, 2, 251, 0) 936828732.48 936828732

從 1.6 版本開始, calendar 模組提供了一個類似的函式 calendar.timegm .

1.15.5. Timing 相關

time 模組可以計算 Python 程式的執行時間, 如 Example 1-85 所示. 你可以測量 "wall time" (real world time), 或是"程式時間" (消耗的 CPU 時間).

1.15.5.1. Example 1-85. 使用 time 模組評價演算法

File: time-example-5.py import time def procedure(): time.sleep(2.5) # measure process time t0 = time.clock() procedure() print time.clock() - t0, "seconds process time" # measure wall time t0 = time.time() procedure() print time.time() - t0, "seconds wall time" 0.0 seconds process time 2.50903499126 seconds wall time

並不是所有的系統都能測量真實的程式時間. 一些系統中(包括 Windows ), clock 函式通常測量從程式啟動到測量時的 wall time.

程式時間的精度受限制. 在一些系統中, 它超過 30 分鐘後程式會被清理. (原文: On many systems, it wraps around after just over 30 minutes.)

另參見 timing 模組( Windows 下的朋友不用忙活了,沒有地~), 它可以測量兩個事件之間的 wall time.


1.16. types 模組

types 模組包含了標準直譯器定義的所有型別的型別物件, 如 Example 1-86 所示. 同一型別的所有物件共享一個型別物件. 你可以使用 is 來檢查一個物件是不是屬於某個給定型別.

1.16.0.1. Example 1-86. 使用 types 模組

File: types-example-1.py import types def check(object): print object, if type(object) is types.IntType: print "INTEGER", if type(object) is types.FloatType: print "FLOAT", if type(object) is types.StringType: print "STRING", if type(object) is types.ClassType: print "CLASS", if type(object) is types.InstanceType: print "INSTANCE", print check(0) check(0.0) check("0") class A: pass class B: pass check(A) check(B) a = A() b = B() check(a) check(b) 0 INTEGER 0.0 FLOAT 0 STRING A CLASS B CLASS <A instance at 796960> INSTANCE <B instance at 796990> INSTANCE

注意所有的類都具有相同的型別, 所有的例項也是一樣. 要測試一個類或者例項所屬的類, 可以使用內建的 issubclass 和 isinstance 函式.

types 模組在第一次引入的時候會破壞當前的異常狀態. 也就是說, 不要在異常處理語句塊中匯入該模組 (或其他會匯入它的模組) .


1.17. gc 模組

(可選, 2.0 及以後版本) gc 模組提供了到內建迴圈垃圾收集器的介面.

Python 使用引用記數來跟蹤什麼時候銷燬一個物件; 一個物件的最後一個引用一旦消失, 這個物件就會被銷燬.

從 2.0 版開始, Python 還提供了一個迴圈垃圾收集器, 它每隔一段時間執行. 這個收集器查詢指向自身的資料結構, 並嘗試破壞迴圈. 如 Example 1-87 所示.

你可以使用 gc.collect 函式來強制完整收集. 這個函式將返回收集器銷燬的物件的數量.

1.17.0.1. Example 1-87. 使用 gc 模組收集迴圈引用垃圾

File: gc-example-1.py import gc # create a simple object that links to itself class Node: def _ _init_ _(self, name): self.name = name self.parent = None self.children = [] def addchild(self, node): node.parent = self self.children.append(node) def _ _repr_ _(self): return "<Node %s at %x>" % (repr(self.name), id(self)) # set up a self-referencing structure root = Node("monty") root.addchild(Node("eric")) root.addchild(Node("john")) root.addchild(Node("michael")) # remove our only reference del root print gc.collect(), "unreachable objects" print gc.collect(), "unreachable objects" 12 unreachable objects 0 unreachable objects

如果你確定你的程式不會建立自引用的資料結構, 你可以使用 gc.disable 函式禁用垃圾收集, 呼叫這個函式以後, Python 的工作方式將與 1.5.2 或更早的版本相同.


2. 更多標準模組

"Now, imagine that your friend kept complaining that she didn't want to visit you since she found it too hard to climb up the drain pipe, and you kept telling her to use the friggin' stairs like everyone else..."
- eff-bot, June 1998

2.1. 概覽

本章敘述了許多在 Python 程式中廣泛使用的模組. 當然, 在大型的 Python 程式中不使用這些模組也是可以的, 但如果使用會節省你不少時間.

2.1.1. 檔案與流

fileinput 模組可以讓你更簡單地向不同的檔案寫入內容. 該模組提供了一個簡單的封裝類, 一個簡單的 for-in 語句就可以迴圈得到一個或多個文字檔案的內容.

StringIO 模組 (以及 cStringIO 模組, 作為一個的變種) 實現了一個工作在記憶體的檔案物件. 你可以在很多地方用 StringIO 物件替換普通的檔案物件.

2.1.2. 型別封裝

UserDict , UserList , 以及 UserString 是對應內建型別的頂層簡單封裝. 和內建型別不同的是, 這些封裝是可以被繼承的. 這在你需要一個和內建型別行為相似但由額外新方法的類的時候很有用.

2.1.3. 隨機數字

random 模組提供了一些不同的隨機數字生成器. whrandom 模組與此相似, 但允許你建立多個生成器物件.

[!Feather 注: whrandom 在版本 2.1 時宣告不支援. 請使用 random 替代.]

2.1.4. 加密演算法

md5 和 sha 模組用於計算密寫的資訊標記( cryptographically strong message signatures , 所謂的 "message digests", 資訊摘要).

crypt 模組實現了 DES 樣式的單向加密. 該模組只在 Unix 系統下可用.

rotor 模組提供了簡單的雙向加密. 版本 2.4 以後的朋友可以不用忙活了.

[!Feather 注: 它在版本 2.3 時申明不支援, 因為它的加密運算不安全.]

2.2. fileinput 模組

fileinput 模組允許你迴圈一個或多個文字檔案的內容, 如 Example 2-1 所示.

2.2.0.1. Example 2-1. 使用 fileinput 模組迴圈一個文字檔案

File: fileinput-example-1.py import fileinput import sys for line in fileinput.input("samples/sample.txt"): sys.stdout.write("-> ") sys.stdout.write(line) -> We will perhaps eventually be writing only small -> modules which are identified by name as they are -> used to build larger ones, so that devices like -> indentation, rather than delimiters, might become -> feasible for expressing local structure in the -> source language. -> -- Donald E. Knuth, December 1974

你也可以使用 fileinput 模組獲得當前行的元資訊 (meta information). 其中包括 isfirstline , filename , lineno , 如 Example 2-2 所示.

2.2.0.2. Example 2-2. 使用 fileinput 模組處理多個文字檔案

File: fileinput-example-2.py import fileinput import glob import string, sys for line in fileinput.input(glob.glob("samples/*.txt")): if fileinput.isfirstline(): # first in a file? sys.stderr.write("-- reading %s --/n" % fileinput.filename()) sys.stdout.write(str(fileinput.lineno()) + " " + string.upper(line)) -- reading samples/sample.txt -- 1 WE WILL PERHAPS EVENTUALLY BE WRITING ONLY SMALL 2 MODULES WHICH ARE IDENTIFIED BY NAME AS THEY ARE 3 USED TO BUILD LARGER ONES, SO THAT DEVICES LIKE 4 INDENTATION, RATHER THAN DELIMITERS, MIGHT BECOME 5 FEASIBLE FOR EXPRESSING LOCAL STRUCTURE IN THE 6 SOURCE LANGUAGE. 7 -- DONALD E. KNUTH, DECEMBER 1974

文字檔案的替換操作很簡單. 只需要把 inplace 關鍵字引數設定為 1 , 傳遞給 input 函式, 該模組會幫你做好一切. Example 2-3 展示了這些.

2.2.0.3. Example 2-3. 使用 fileinput 模組將 CRLF 改為 LF

File: fileinput-example-3.py import fileinput, sys for line in fileinput.input(inplace=1): # convert Windows/DOS text files to Unix files if line[-2:] == "/r/n": line = line[:-2] + "/n" sys.stdout.write(line)

2.3. shutil 模組

shutil 實用模組包含了一些用於複製檔案和資料夾的函式. Example 2-4 中使用的 copy 函式使用和 Unix 下 cp 命令基本相同的方式複製一個檔案.

2.3.0.1. Example 2-4. 使用 shutil 複製檔案

File: shutil-example-1.py import shutil import os for file in os.listdir("."): if os.path.splitext(file)[1] == ".py": print file shutil.copy(file, os.path.join("backup", file)) aifc-example-1.py anydbm-example-1.py array-example-1.py ...

copytree 函式用於複製整個目錄樹 (與 cp -r 相同), 而 rmtree 函式用於刪除整個目錄樹 (與 rm -r ). 如 Example 2-5 所示.

2.3.0.2. Example 2-5. 使用 shutil 模組複製/刪除目錄樹

File: shutil-example-2.py import shutil import os SOURCE = "samples" BACKUP = "samples-bak" # create a backup directory shutil.copytree(SOURCE, BACKUP) print os.listdir(BACKUP) # remove it shutil.rmtree(BACKUP) print os.listdir(BACKUP)['sample.wav', 'sample.jpg', 'sample.au', 'sample.msg', 'sample.tgz', ... Traceback (most recent call last): File "shutil-example-2.py", line 17, in ? print os.listdir(BACKUP) os.error: No such file or directory

2.4. tempfile 模組

Example 2-6 中展示的 tempfile 模組允許你快速地建立名稱唯一的臨時檔案供使用.

2.4.0.1. Example 2-6. 使用 tempfile 模組建立臨時檔案

File: tempfile-example-1.py import tempfile import os tempfile = tempfile.mktemp() print "tempfile", "=>", tempfile file = open(tempfile, "w+b") file.write("*" * 1000) file.seek(0) print len(file.read()), "bytes" file.close() try: # must remove file when done os.remove(tempfile) except OSError: pass tempfile => C:/TEMP/~160-1 1000 bytes

TemporaryFile 函式會自動挑選合適的檔名, 並開啟檔案, 如 Example 2-7 所示. 而且它會確保該檔案在關閉的時候會被刪除. (在 Unix 下, 你可以刪除一個已開啟的檔案, 這 時檔案關閉時它會被自動刪除. 在其他平臺上, 這通過一個特殊的封裝類實現.)

2.4.0.2. Example 2-7. 使用 tempfile 模組開啟臨時檔案

File: tempfile-example-2.py import tempfile file = tempfile.TemporaryFile() for i in range(100): file.write("*" * 100) file.close() # removes the file!

2.5. StringIO 模組

Example 2-8 展示了 StringIO 模組的使用. 它實現了一個工作在記憶體的檔案物件 (記憶體檔案). 在大多需要標準檔案物件的地方都可以使用它來替換.

2.5.0.1. Example 2-8. 使用 StringIO 模組從記憶體檔案讀入內容

File: stringio-example-1.py import StringIO MESSAGE = "That man is depriving a village somewhere of a computer scientist." file = StringIO.StringIO(MESSAGE) print file.read() That man is depriving a village somewhere of a computer scientist.

StringIO 類實現了內建檔案物件的所有方法, 此外還有 getvalue 方法用來返回它內部的字串值. Example 2-9 展示了這個方法.

2.5.0.2. Example 2-9. 使用 StringIO 模組向記憶體檔案寫入內容

File: stringio-example-2.py import StringIO file = StringIO.StringIO() file.write("This man is no ordinary man. ") file.write("This is Mr. F. G. Superman.") print file.getvalue() This man is no ordinary man. This is Mr. F. G. Superman.

StringIO 可以用於重新定向 Python 直譯器的輸出, 如 Example 2-10 所示.

2.5.0.3. Example 2-10. 使用 StringIO 模組捕獲輸出

File: stringio-example-3.py import StringIO import string, sys stdout = sys.stdout sys.stdout = file = StringIO.StringIO() print """ According to Gbaya folktales, trickery and guile are the best ways to defeat the python, king of snakes, which was hatched from a dragon at the world's start. -- National Geographic, May 1997 """ sys.stdout = stdout print string.upper(file.getvalue())ACCORDING TO GBAYA FOLKTALES, TRICKERY AND GUILE ARE THE BEST WAYS TO DEFEAT THE PYTHON, KING OF SNAKES, WHICH WAS HATCHED FROM A DRAGON AT THE WORLD'S START. -- NATIONAL GEOGRAPHIC, MAY 1997

2.6. cStringIO 模組

cStringIO 是一個可選的模組, 是 StringIO 的更快速實現. 它的工作方式和 StringIO 基本相同, 但是它不可以被繼承. Example 2-11 展示了cStringIO 的用法, 另參考前一節.

2.6.0.1. Example 2-11. 使用 cStringIO 模組

File: cstringio-example-1.py import cStringIO MESSAGE = "That man is depriving a village somewhere of a computer scientist." file = cStringIO.StringIO(MESSAGE) print file.read() That man is depriving a village somewhere of a computer scientist.

為了讓你的程式碼儘可能快, 但同時保證相容低版本的 Python ,你可以使用一個小技巧在 cStringIO 不可用時啟用 StringIO 模組, 如 Example 2-12 所示.

2.6.0.2. Example 2-12. 後退至 StringIO

File: cstringio-example-2.py try: import cStringIO StringIO = cStringIO except ImportError: import StringIO print StringIO<module 'StringIO' (built-in)>

2.7. mmap 模組

(2.0 新增) mmap 模組提供了作業系統記憶體對映函式的介面, 如 Example 2-13 所示. 對映區域的行為和字串物件類似, 但資料是直接從檔案讀取的.

2.7.0.1. Example 2-13. 使用 mmap 模組

File: mmap-example-1.py import mmap import os filename = "samples/sample.txt" file = open(filename, "r+") size = os.path.getsize(filename) data = mmap.mmap(file.fileno(), size) # basics print data print len(data), size # use slicing to read from the file # 使用切片操作讀取檔案 print repr(data[:10]), repr(data[:10]) # or use the standard file interface # 或使用標準的檔案介面 print repr(data.read(10)), repr(data.read(10)) <mmap object at 008A2A10> 302 302 'We will pe' 'We will pe' 'We will pe' 'rhaps even'

在 Windows 下, 這個檔案必須以既可讀又可寫的模式開啟( `r+` , `w+` , 或 `a+` ), 否則 mmap 呼叫會失敗.

[!Feather 注: 經本人測試, a+ 模式是完全可以的, 原文只有 r+ 和 w+]

Example 2-14 展示了記憶體對映區域的使用, 在很多地方它都可以替換普通字串使用, 包括正規表示式和其他字串操作.

2.7.0.2. Example 2-14. 對對映區域使用字串方法和正規表示式

File: mmap-example-2.py import mmap import os, string, re def mapfile(filename): file = open(filename, "r+") size = os.path.getsize(filename) return mmap.mmap(file.fileno(), size) data = mapfile("samples/sample.txt") # search index = data.find("small") print index, repr(data[index-5:index+15]) # regular expressions work too! m = re.search("small", data) print m.start(), m.group() 43 'only small/015/012modules ' 43 small

2.8. UserDict 模組

UserDict 模組包含了一個可繼承的字典類 (事實上是對內建字典型別的 Python 封裝).

Example 2-15 展示了一個增強的字典類, 允許對字典使用 "加/+" 操作並提供了接受關鍵字引數的建構函式.

2.8.0.1. Example 2-15. 使用 UserDict 模組

File: userdict-example-1.py import UserDict class FancyDict(UserDict.UserDict): def _ _init_ _(self, data = {}, **kw): UserDict.UserDict._ _init_ _(self) self.update(data) self.update(kw) def _ _add_ _(self, other): dict = FancyDict(self.data) dict.update(b) return dict a = FancyDict(a = 1) b = FancyDict(b = 2) print a + b {'b': 2, 'a': 1}

2.9. UserList 模組

UserList 模組包含了一個可繼承的列表類 (事實上是對內建列表型別的 Python 封裝).

在 Example 2-16 中, AutoList 例項類似一個普通的列表物件, 但它允許你通過賦值為列表新增專案.

2.9.0.1. Example 2-16. 使用 UserList 模組

File: userlist-example-1.py import UserList class AutoList(UserList.UserList): def _ _setitem_ _(self, i, item): if i == len(self.data): self.data.append(item) else: self.data[i] = item list = AutoList() for i in range(10): list[i] = i print list[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

2.10. UserString 模組

(2.0 新增) UserString 模組包含兩個類, UserString 和 MutableString . 前者是對標準字串型別的封裝, 後者是一個變種, 允許你修改特定位置的字元(聯想下列表就知道了).

注意 MutableString 並不是效率很好, 許多操作是通過切片和字串連線實現的. 如果效能很對你的指令碼來說重要的話, 你最好使用字串片斷的列表或者array 模組. Example 2-17 展示了 UserString 模組.

2.10.0.1. Example 2-17. 使用 UserString 模組

File: userstring-example-1.py import UserString class MyString(UserString.MutableString): def append(self, s): self.data = self.data + s def insert(self, index, s): self.data = self.data[index:] + s + self.data[index:] def remove(self, s): self.data = self.data.replace(s, "") file = open("samples/book.txt") text = file.read() file.close() book = MyString(text) for bird in ["gannet", "robin", "nuthatch"]: book.remove(bird) print book ... C: The one without the ! P: The one without the -!!! They've ALL got the !! It's a Standard British Bird, the , it's in all the books!!! ...

2.11. traceback 模組

Example 2-18 展示了 traceback 模組允許你在程式裡列印異常的跟蹤返回 (Traceback)資訊, 類似未捕獲異常時直譯器所做的. 如 Example 2-18 所示.

2.11.0.1. Example 2-18. 使用 traceback 模組列印跟蹤返回資訊

File: traceback-example-1.py # note! importing the traceback module messes up the # exception state, so you better do that here and not # in the exception handler # 注意! 匯入 traceback 會清理掉異常狀態, 所以 # 最好別在異常處理程式碼中匯入該模組 import traceback try: raise SyntaxError, "example" except: traceback.print_exc() Traceback (innermost last): File "traceback-example-1.py", line 7, in ? SyntaxError: example

Example 2-19 使用 StringIO 模組將跟蹤返回資訊放在字串中.

2.11.0.2. Example 2-19. 使用 traceback 模組將跟蹤返回資訊複製到字串

File: traceback-example-2.py import traceback import StringIO try: raise IOError, "an i/o error occurred" except: fp = StringIO.StringIO() traceback.print_exc(file=fp) message = fp.getvalue() print "failure! the error was:", repr(message)failure! the error was: 'Traceback (innermost last):/012 File "traceback-example-2.py", line 5, in ?/012IOError: an i/o error occurred/012'

你可以使用 extract_tb 函式格式化跟蹤返回資訊, 得到包含錯誤資訊的列表, 如 Example 2-20 所示.

2.11.0.3. Example 2-20. 使用 traceback Module 模組編碼 Traceback 物件

File: traceback-example-3.py import traceback import sys def function(): raise IOError, "an i/o error occurred" try: function() except: info = sys.exc_info() for file, lineno, function, text in traceback.extract_tb(info[2]): print file, "line", lineno, "in", function print "=>", repr(text) print "** %s: %s" % info[:2] traceback-example-3.py line 8 in ? => 'function()' traceback-example-3.py line 5 in function => 'raise IOError, "an i/o error occurred"' ** exceptions.IOError: an i/o error occurred

2.12. errno 模組

errno 模組定義了許多的符號錯誤碼, 比如 ENOENT ("沒有該目錄入口") 以及 EPERM ("許可權被拒絕"). 它還提供了一個對映到對應平臺數字錯誤程式碼的字典.Example 2-21 展示瞭如何使用 errno 模組.

在大多情況下, IOError 異常會提供一個二元元組, 包含對應數值錯誤程式碼和一個說明字串. 如果你需要區分不同的錯誤程式碼, 那麼最好在可能的地方使用符號名稱.

2.12.0.1. Example 2-21. 使用 errno 模組

File: errno-example-1.py import errno try: fp = open("no.such.file") except IOError, (error, message): if error == errno.ENOENT: print "no such file" elif error == errno.EPERM: print "permission denied" else: print message no such file

Example 2-22 繞了些無用的彎子, 不過它很好地說明了如何使用 errorcode 字典把數字錯誤碼對映到符號名稱( symbolic name ).

2.12.0.2. Example 2-22. 使用 errorcode 字典

File: errno-example-2.py import errno try: fp = open("no.such.file") except IOError, (error, message): print error, repr(message) print errno.errorcode[error] # 2 'No such file or directory' # ENOENT

2.13. getopt 模組

getopt 模組包含用於抽出命令列選項和引數的函式, 它可以處理多種格式的選項. 如 Example 2-23 所示.

其中第 2 個引數指定了允許的可縮寫的選項. 選項名後的冒號(:) 意味這這個選項必須有額外的引數.

2.13.0.1. Example 2-23. 使用 getopt 模組

File: getopt-example-1.py import getopt import sys # simulate command-line invocation # 模仿命令列引數 sys.argv = ["myscript.py", "-l", "-d", "directory", "filename"] # process options # 處理選項 opts, args = getopt.getopt(sys.argv[1:], "ld:") long = 0 directory = None for o, v in opts: if o == "-l": long = 1 elif o == "-d": directory = v print "long", "=", long print "directory", "=", directory print "arguments", "=", args long = 1 directory = directory arguments = ['filename']

為了讓 getopt 查詢長的選項, 如 Example 2-24 所示, 傳遞一個描述選項的列表做為第 3 個引數. 如果一個選項名稱以等號(=) 結尾, 那麼它必須有一個附加引數.

2.13.0.2. Example 2-24. 使用 getopt 模組處理長選項

File: getopt-example-2.py import getopt import sys # simulate command-line invocation # 模仿命令列引數 sys.argv = ["myscript.py", "--echo", "--printer", "lp01", "message"] opts, args = getopt.getopt(sys.argv[1:], "ep:", ["echo", "printer="]) # process options # 處理選項 echo = 0 printer = None for o, v in opts: if o in ("-e", "--echo"): echo = 1 elif o in ("-p", "--printer"): printer = v print "echo", "=", echo print "printer", "=", printer print "arguments", "=", args echo = 1 printer = lp01 arguments = ['message']
[!Feather 注: 我不知道大家明白沒, 可以自己試下: myscript.py -e -p lp01 message myscript.py --echo --printer=lp01 message ]

2.14. getpass 模組

getpass 模組提供了平臺無關的在命令列下輸入密碼的方法. 如 Example 2-25 所示.

getpass(prompt) 會顯示提示字串, 關閉鍵盤的螢幕反饋, 然後讀取密碼. 如果提示引數省略, 那麼它將列印出 "Password:".

getuser() 獲得當前使用者名稱, 如果可能的話.

2.14.0.1. Example 2-25. 使用 getpass 模組

File: getpass-example-1.py import getpass usr = getpass.getuser() pwd = getpass.getpass("enter password for user %s: " % usr) print usr, pwd enter password for user mulder: mulder trustno1

2.15. glob 模組

glob 根據給定模式生成滿足該模式的檔名列表, 和 Unix shell 相同.

這裡的模式和正規表示式類似, 但更簡單. 星號(*) 匹配零個或更多個字元, 問號(?) 匹配單個字元. 你也可以使用方括號來指定字元範圍, 例如 [0-9] 代表一個數字. 其他所有字元都代表它們本身.

glob(pattern) 返回滿足給定模式的所有檔案的列表. Example 2-26 展示了它的用法.

2.15.0.1. Example 2-26. 使用 glob 模組

File: glob-example-1.py import glob for file in glob.glob("samples/*.jpg"): print file samples/sample.jpg

注意這裡的 glob 返回完整路徑名, 這點和 os.listdir 函式不同. glob 事實上使用了 fnmatch 模組來完成模式匹配.


2.16. fnmatch 模組

fnmatch 模組使用模式來匹配檔名. 如 Example 2-27 所示.

模式語法和 Unix shell 中所使用的相同. 星號(*) 匹配零個或更多個字元, 問號(?) 匹配單個字元. 你也可以使用方括號來指定字元範圍, 例如 [0-9] 代表一個數字. 其他所有字元都匹配它們本身.

2.16.0.1. Example 2-27. 使用 fnmatch 模組匹配檔案

File: fnmatch-example-1.py import fnmatch import os for file in os.listdir("samples"): if fnmatch.fnmatch(file, "*.jpg"): print file sample.jpg

Example 2-28 中的 translate 函式可以將一個檔案匹配模式轉換為正規表示式.

2.16.0.2. Example 2-28. 使用 fnmatch 模組將模式轉換為正規表示式

File: fnmatch-example-2.py import fnmatch import os, re pattern = fnmatch.translate("*.jpg") for file in os.listdir("samples"): if re.match(pattern, file): print file print "(pattern was %s)" % pattern sample.jpg (pattern was .*/.jpg$)

glob 和 find 模組在內部使用 fnmatch 模組來實現.


2.17. random 模組

"Anyone who considers arithmetical methods of producing random digits is, of course, in a state of sin."
- John von Neumann, 1951

random 模組包含許多隨機數生成器.

基本隨機數生成器(基於 Wichmann 和 Hill , 1982 的數學運算理論) 可以通過很多方法訪問, 如 Example 2-29 所示.

2.17.0.1. Example 2-29. 使用 random 模組獲得隨機數字

File: random-example-1.py import random for i in range(5): # random float: 0.0 <= number < 1.0 print random.random(), # random float: 10 <= number < 20 print random.uniform(10, 20), # random integer: 100 <= number <= 1000 print random.randint(100, 1000), # random integer: even numbers in 100 <= number < 1000 print random.randrange(100, 1000, 2) 0.946842713956 19.5910069381 709 172 0.573613195398 16.2758417025 407 120 0.363241598013 16.8079747714 916 580 0.602115173978 18.386796935 531 774 0.526767588533 18.0783794596 223 344

注意這裡的 randint 函式可以返回上界, 而其他函式總是返回小於上界的值. 所有函式都有可能返回下界值.

Example 2-30 展示了 choice 函式, 它用來從一個序列裡分揀出一個隨機專案. 它可以用於列表, 元組, 以及其他序列(當然, 非空的).

2.17.0.2. Example 2-30. 使用 random 模組從序列取出隨機項

File: random-example-2.py import random # random choice from a list for i in range(5): print random.choice([1, 2, 3, 5, 9]) 2 3 1 9 1

在 2.0 及以後版本, shuffle 函式可以用於打亂一個列表的內容 (也就是生成一個該列表的隨機全排列). Example 2-31 展示瞭如何在舊版本中實現該函式.

2.17.0.3. Example 2-31. 使用 random 模組打亂一副牌

File: random-example-4.py import random try: # available in 2.0 and later shuffle = random.shuffle except AttributeError: def shuffle(x): for i in xrange(len(x)-1, 0, -1): # pick an element in x[:i+1] with which to exchange x[i] j = int(random.random() * (i+1)) x[i], x[j] = x[j], x[i] cards = range(52) shuffle(cards) myhand = cards[:5] print myhand [4, 8, 40, 12, 30]

random 模組也包含了非恆定分佈的隨機生成器函式. Example 2-32 使用了 gauss (高斯)函式來生成滿足高斯分的布隨機數字.

2.17.0.4. Example 2-32. 使用 random 模組生成高斯分佈隨機數

File: random-example-3.py import random histogram = [0] * 20 # calculate histogram for gaussian # noise, using average=5, stddev=1 for i in range(1000): i = int(random.gauss(5, 1) * 2) histogram[i] = histogram[i] + 1 # print the histogram m = max(histogram) for v in histogram: print "*" * (v * 50 / m) **** ********** ************************* *********************************** ************************************************ ************************************************** ************************************* *************************** ************* *** *

你可以在 Python Library Reference 找到更多關於非恆定分佈隨機生成器函式的資訊.

標準庫中提供的隨機數生成器都是偽隨機數生成器. 不過這對於很多目的來說已經足夠了, 比如模擬, 數值分析, 以及遊戲. 可以確定的是它不適合密碼學用途.

2.18. whrandom 模組

這個模組早在 2.1 就被宣告不贊成, 早廢了. 請使用 random 代替. 
- Feather

Example 2-33 展示了 whrandom , 它提供了一個偽隨機數生成器. (基於 Wichmann 和 Hill, 1982 的數學運算理論). 除非你需要不共享狀態的多個生成器(如多執行緒程式), 請使用 random 模組代替.

2.18.0.1. Example 2-33. 使用 whrandom 模組

File: whrandom-example-1.py import whrandom # same as random print whrandom.random() print whrandom.choice([1, 2, 3, 5, 9]) print whrandom.uniform(10, 20) print whrandom.randint(100, 1000) 0.113412062346 1 16.8778954689 799

Example 2-34 展示瞭如何使用 whrandom 類例項建立多個生成器.

2.18.0.2. Example 2-34. 使用 whrandom 模組建立多個隨機生成器

File: whrandom-example-2.py import whrandom # initialize all generators with the same seed rand1 = whrandom.whrandom(4,7,11) rand2 = whrandom.whrandom(4,7,11) rand3 = whrandom.whrandom(4,7,11) for i in range(5): print rand1.random(), rand2.random(), rand3.random() 0.123993532536 0.123993532536 0.123993532536 0.180951499518 0.180951499518 0.180951499518 0.291924111809 0.291924111809 0.291924111809 0.952048889363 0.952048889363 0.952048889363 0.969794283643 0.969794283643 0.969794283643

2.19. md5 模組

md5 (Message-Digest Algorithm 5)模組用於計算資訊密文(資訊摘要).

md5 演算法計算一個強壯的128位密文. 這意味著如果兩個字串是不同的, 那麼有極高可能它們的 md5 也不同. 也就是說, 給定一個 md5 密文, 那麼幾乎沒有可能再找到另個字串的密文與此相同. Example 2-35 展示瞭如何使用 md5 模組.

2.19.0.1. Example 2-35. 使用 md5 模組

File: md5-example-1.py import md5 hash = md5.new() hash.update("spam, spam, and eggs") print repr(hash.digest())'L/005J/243/266/355/243u`/305r/203/267/020F/303'

注意這裡的校驗和是一個二進位制字串. Example 2-36 展示瞭如何獲得一個十六進位制或 base64 編碼的字串.

2.19.0.2. Example 2-36. 使用 md5 模組獲得十六進位制或 base64 編碼的 md5 值

File: md5-example-2.py import md5 import string import base64 hash = md5.new() hash.update("spam, spam, and eggs") value = hash.digest() print hash.hexdigest() # before 2.0, the above can be written as # 在 2.0 前, 以上應該寫做: # print string.join(map(lambda v: "%02x" % ord(v), value), "") print base64.encodestring(value) 4c054aa3b6eda37560c57283b71046c3 TAVKo7bto3VgxXKDtxBGww==

Example 2-37 展示瞭如何使用 md5 校驗和來處理口令的傳送與應答的驗證(不過我們將稍候討論這裡使用隨機數字所帶來的問題).

2.19.0.3. Example 2-37. 使用 md5 模組來處理口令的傳送與應答的驗證

File: md5-example-3.py import md5 import string, random def getchallenge(): # generate a 16-byte long random string. (note that the built- # in pseudo-random generator uses a 24-bit seed, so this is not # as good as it may seem...) # 生成一個 16 位元組長的隨機字串. 注意內建的偽隨機生成器 # 使用的是 24 位的種子(seed), 所以這裡這樣用並不好.. challenge = map(lambda i: chr(random.randint(0, 255)), range(16)) return string.join(challenge, "") def getresponse(password, challenge): # calculate combined digest for password and challenge # 計算密碼和質詢(challenge)的聯合密文 m = md5.new() m.update(password) m.update(challenge) return m.digest() # # server/client communication # 伺服器/客戶端通訊 # 1. client connects. server issues challenge. # 1. 客戶端連線, 伺服器釋出質詢(challenge) print "client:", "connect" challenge = getchallenge() print "server:", repr(challenge) # 2. client combines password and challenge, and calculates # the response. # 2. 客戶端計算密碼和質詢(challenge)的組合後的密文 client_response = getresponse("trustno1", challenge) print "client:", repr(client_response) # 3. server does the same, and compares the result with the # client response. the result is a safe login in which the # password is never sent across the communication channel. # 3. 伺服器做同樣的事, 然後比較結果與客戶端的返回,  # 判斷是否允許使用者登陸. 這樣做密碼沒有在通訊中明文傳輸. server_response = getresponse("trustno1", challenge) if server_response == client_response: print "server:", "login ok" client: connect server: '/334/352/227Z#/272/273/212KG/330/265/032>/311o' client: "l'/305/240-x/245/237/035/225A/254/233/337/225/001" server: login ok

Example 2-38 提供了 md5 的一個變種, 你可以通過標記資訊來判斷它是否在網路傳輸過程中被修改(丟失).

2.19.0.4. Example 2-38. 使用 md5 模組檢查資料完整性

File: md5-example-4.py import md5 import array class HMAC_MD5: # keyed md5 message authentication def _ _init_ _(self, key): if len(key) > 64: key = md5.new(key).digest() ipad = array.array("B", [0x36] * 64) opad = array.array("B", [0x5C] * 64) for i in range(len(key)): ipad[i] = ipad[i] ^ ord(key[i]) opad[i] = opad[i] ^ ord(key[i]) self.ipad = md5.md5(ipad.tostring()) self.opad = md5.md5(opad.tostring()) def digest(self, data): ipad = self.ipad.copy() opad = self.opad.copy() ipad.update(data) opad.update(ipad.digest()) return opad.digest() # # simulate server end # 模擬伺服器端 key = "this should be a well-kept secret" message = open("samples/sample.txt").read() signature = HMAC_MD5(key).digest(message) # (send message and signature across a public network) # (經過由網路傳送資訊和簽名) # # simulate client end #模擬客戶端 key = "this should be a well-kept secret" client_signature = HMAC_MD5(key).digest(message) if client_signature == signature: print "this is the original message:" print print message else: print "someone has modified the message!!!"

copy 方法會對這個內部物件狀態做一個快照( snapshot ). 這允許你預先計算部分密文摘要(例如 Example 2-38 中的 padded key).

該演算法的細節請參閱 HMAC-MD5:Keyed-MD5 for Message Authentication ( http://www.research.ibm.com/security/draft-ietf-ipsec-hmac-md5-00.txt ) by Krawczyk, 或其他.

千萬別忘記內建的偽隨機生成器對於加密操作而言並不合適. 千萬小心.

2.20. sha 模組

sha 模組提供了計算資訊摘要(密文)的另種方法, 如 Example 2-39 所示. 它與 md5 模組類似, 但生成的是 160 位簽名.

2.20.0.1. Example 2-39. 使用 sha 模組

File: sha-example-1.py import sha hash = sha.new() hash.update("spam, spam, and eggs") print repr(hash.digest()) print hash.hexdigest() '/321/333/003/026I/331/272-j/303/247/240/345/343Tvq/364/346/311' d1db031649d9ba2d6ac3a7a0e5e3547671f4e6c9

關於 sha 密文的使用, 請參閱 md5 中的例子.


2.21. crypt 模組

(可選, 只用於 Unix) crypt 模組實現了單向的 DES 加密, Unix 系統使用這個加密演算法來儲存密碼, 這個模組真正也就只在檢查這樣的密碼時有用.

Example 2-40 展示瞭如何使用 crypt.crypt 來加密一個密碼, 將密碼和 salt 組合起來然後傳遞給函式, 這裡的 salt 包含兩位隨機字元. 現在你可以扔掉原密碼而只儲存加密後的字串了.

2.21.0.1. Example 2-40. 使用 crypt 模組

File: crypt-example-1.py import crypt import random, string def getsalt(chars = string.letters + string.digits): # generate a random 2-character 'salt' # 生成隨機的 2 字元 'salt' return random.choice(chars) + random.choice(chars) print crypt.crypt("bananas", getsalt()) 'py8UGrijma1j6'

確認密碼時, 只需要用新密碼呼叫加密函式, 並取加密後字串的前兩位作為 salt 即可. 如 果結果和加密後字串匹配, 那麼密碼就是正確的. Example 2-41使用 pwd 模組來獲取已知使用者的加密後密碼.

2.21.0.2. Example 2-41. 使用 crypt 模組身份驗證

File: crypt-example-2.py import pwd, crypt def login(user, password): "Check if user would be able to log in using password" try: pw1 = pwd.getpwnam(user)[1] pw2 = crypt.crypt(password, pw1[:2]) return pw1 == pw2 except KeyError: return 0 # no such user user = raw_input("username:") password = raw_input("password:") if login(user, password): print "welcome", user else: print "login failed"

關於其他實現驗證的方法請參閱 md5 模組一節.


2.22. rotor 模組

這個模組在 2.3 時被宣告不贊成, 2.4 時廢了. 因為它的加密演算法不安全. 
- Feather

(可選) rotor 模組實現了一個簡單的加密演算法. 如 Example 2-42 所示. 它的演算法基於 WWII Enigma engine.

2.22.0.1. Example 2-42. 使用 rotor 模組

File: rotor-example-1.py import rotor SECRET_KEY = "spam" MESSAGE = "the holy grail" r = rotor.newrotor(SECRET_KEY) encoded_message = r.encrypt(MESSAGE) decoded_message = r.decrypt(encoded_message) print "original:", repr(MESSAGE) print "encoded message:", repr(encoded_message) print "decoded message:", repr(decoded_message) original: 'the holy grail' encoded message: '/227/271/244/015/305sw/3340/337/252/237/340U' decoded message: 'the holy grail'

2.23. zlib 模組

(可選) zlib 模組為 "zlib" 壓縮提供支援. (這種壓縮方法是 "deflate".)

Example 2-43 展示瞭如何使用 compress 和 decompress 函式接受字串引數.

2.23.0.1. Example 2-43. 使用 zlib 模組壓縮字串

File: zlib-example-1.py import zlib MESSAGE = "life of brian" compressed_message = zlib.compress(MESSAGE) decompressed_message = zlib.decompress(compressed_message) print "original:", repr(MESSAGE) print "compressed message:", repr(compressed_message) print "decompressed message:", repr(decompressed_message) original: 'life of brian' compressed message: 'x/234/313/311LKU/310OSH*/312L/314/003/000!/010/004/302' decompressed message: 'life of brian'

檔案的內容決定了壓縮比率, Example 2-44 說明了這點.

2.23.0.2. Example 2-44. 使用 zlib 模組壓縮多個不同型別檔案

File: zlib-example-2.py import zlib import glob for file in glob.glob("samples/*"): indata = open(file, "rb").read() outdata = zlib.compress(indata, zlib.Z_BEST_COMPRESSION) print file, len(indata), "=>", len(outdata), print "%d%%" % (len(outdata) * 100 / len(indata)) samples/sample.au 1676 => 1109 66% samples/sample.gz 42 => 51 121% samples/sample.htm 186 => 135 72% samples/sample.ini 246 => 190 77% samples/sample.jpg 4762 => 4632 97% samples/sample.msg 450 => 275 61% samples/sample.sgm 430 => 321 74% samples/sample.tar 10240 => 125 1% samples/sample.tgz 155 => 159 102% samples/sample.txt 302 => 220 72% samples/sample.wav 13260 => 10992 82%

你也可以實時地壓縮或解壓縮資料, 如 Example 2-45 所示.

2.23.0.3. Example 2-45. 使用 zlib 模組解壓縮流

File: zlib-example-3.py import zlib encoder = zlib.compressobj() data = encoder.compress("life") data = data + encoder.compress(" of ") data = data + encoder.compress("brian") data = data + encoder.flush() print repr(data) print repr(zlib.decompress(data)) 'x/234/313/311LKU/310OSH*/312L/314/003/000!/010/004/302' 'life of brian'

Example 2-46 把解碼物件封裝到了一個類似檔案物件的類中, 實現了一些檔案物件的方法, 這樣使得讀取壓縮檔案更方便.

2.23.0.4. Example 2-46. 壓縮流的仿檔案訪問方式

File: zlib-example-4.py import zlib import string, StringIO class ZipInputStream: def _ _init_ _(self, file): self.file = file self._ _rewind() def _ _rewind(self): self.zip = zlib.decompressobj() self.pos = 0 # position in zipped stream self.offset = 0 # position in unzipped stream self.data = "" def _ _fill(self, bytes): if self.zip: # read until we have enough bytes in the buffer while not bytes or len(self.data) < bytes: self.file.seek(self.pos) data = self.file.read(16384) if not data: self.data = self.data + self.zip.flush() self.zip = None # no more data break self.pos = self.pos + len(data) self.data = self.data + self.zip.decompress(data) def seek(self, offset, whence=0): if whence == 0: position = offset elif whence == 1: position = self.offset + offset else: raise IOError, "Illegal argument" if position < self.offset: raise IOError, "Cannot seek backwards" # skip forward, in 16k blocks while position > self.offset: if not self.read(min(position - self.offset, 16384)): break def tell(self): return self.offset def read(self, bytes = 0): self._ _fill(bytes) if bytes: data = self.data[:bytes] self.data = self.data[bytes:] else: data = self.data self.data = "" self.offset = self.offset + len(data) return data def readline(self): # make sure we have an entire line while self.zip and "/n" not in self.data: self._ _fill(len(self.data) + 512) i = string.find(self.data, "/n") + 1 if i <= 0: return self.read() return self.read(i) def readlines(self): lines = [] while 1: s = self.readline() if not s: break lines.append(s) return lines # # try it out data = open("samples/sample.txt").read() data = zlib.compress(data) file = ZipInputStream(StringIO.StringIO(data)) for line in file.readlines(): print line[:-1] We will perhaps eventually be writing only small modules which are identified by name as they are used to build larger ones, so that devices like indentation, rather than delimiters, might become feasible for expressing local structure in the source language. -- Donald E. Knuth, December 1974

2.24. code 模組

code 模組提供了一些用於模擬標準互動直譯器行為的函式.

compile_command 與內建 compile 函式行為相似, 但它會通過測試來保證你傳遞的是一個完成的 Python 語句.

在 Example 2-47 中, 我們一行一行地編譯一個程式, 編譯完成後會執行所得到的程式碼物件 (code object). 程式程式碼如下:

a = ( 1, 2, 3 ) print a

注意只有我們到達第 2 個括號, 元組的賦值操作能編譯完成.

2.24.0.1. Example 2-47. 使用 code 模組編譯語句

File: code-example-1.py import code import string #  SCRIPT = [ "a = (", " 1,", " 2,", " 3 ", ")", "print a" ] script = "" for line in SCRIPT: script = script + line + "/n" co = code.compile_command(script, "<stdin>", "exec") if co: # got a complete statement. execute it! print "-"*40 print script, print "-"*40 exec co script = "" ---------------------------------------- a = ( 1, 2, 3  ) ---------------------------------------- ---------------------------------------- print a ---------------------------------------- (1, 2, 3)

InteractiveConsole 類實現了一個互動控制檯, 類似你啟動的 Python 直譯器互動模式.

控制檯可以是活動的(自動呼叫函式到達下一行) 或是被動的(當有新資料時呼叫 push 方法). 預設使用內建的 raw_input 函式. 如果你想使用另個輸入函式, 你可以使用相同的名稱過載這個方法. Example 2-48 展示瞭如何使用 code 模組來模擬互動直譯器.

2.24.0.2. Example 2-48. 使用 code 模組模擬互動直譯器

File: code-example-2.py import code console = code.InteractiveConsole() console.interact() Python 1.5.2 Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam (InteractiveConsole) >>> a = ( ... 1, ... 2, ... 3 ... ) >>> print a (1, 2, 3)

Example 2-49 中的指令碼定義了一個 keyboard 函式. 它允許你在程式中手動控制互動直譯器.

2.24.0.3. Example 2-49. 使用 code 模組實現簡單的 Debugging

File: code-example-3.py def keyboard(banner=None): import code, sys # use exception trick to pick up the current frame try: raise None except: frame = sys.exc_info()[2].tb_frame.f_back # evaluate commands in current namespace namespace = frame.f_globals.copy() namespace.update(frame.f_locals) code.interact(banner=banner, local=namespace) def func(): print "START" a = 10 keyboard() print "END" func() START Python 1.5.2 Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam (InteractiveConsole) >>> print a 10 >>> print keyboard <function keyboard at 9032c8> ^Z END

3. 執行緒和程式

"Well, since you last asked us to stop, this thread has moved from discussing languages suitable for professional programmers via accidental users to computer-phobic users. A few more iterations can make this thread really interesting..."
- eff-bot, June 1996

3.1. 概覽

本章將介紹標準 Python 直譯器中所提供的執行緒支援模組. 注意執行緒支援模組是可選的, 有可能在一些 Python 直譯器中不可用.

本章還涵蓋了一些 Unix 和 Windows 下用於執行外部程式的模組.

3.1.1. 執行緒

執行 Python 程式的時候, 是按照從主模組頂端向下執行的. 迴圈用於重複執行部分程式碼, 函式和方法會將控制臨時移交到程式的另一部分.

通過執行緒, 你的程式可以在同時處理多個任務. 每個執行緒都有它自己的控制流. 所以你可以在一個執行緒裡從檔案讀取資料, 另個向螢幕輸出內容.

為了保證兩個執行緒可以同時訪問相同的內部資料, Python 使用了 global interpreter lock (全域性直譯器鎖). 在同一時間只可能有一個執行緒執行 Python 程式碼; Python 實際上是自動地在一段很短的時間後切換到下個執行緒執行, 或者等待 一個執行緒執行一項需要時間的操作(例如等待通過 socket 傳輸的資料, 或是從檔案中讀取資料).

全域性鎖事實上並不能避免你程式中的問題. 多個執行緒嘗試訪問相同的資料會導致異常 狀態. 例如以下的程式碼:

def getitem(key): item = cache.get(key) if item is None: # not in cache; create a new one item = create_new_item(key) cache[key] = item return item

如果不同的執行緒先後使用相同的 key 呼叫這裡的 getitem 方法, 那麼它們很可能會導致相同的引數呼叫兩次 create_new_item . 大多時候這樣做沒有問題, 但在某些時候會導致嚴重錯誤.

不過你可以使用 lock objects 來同步執行緒. 一個執行緒只能擁有一個 lock object , 這樣就可以確保某個時刻 只有一個執行緒執行 getitem 函式.

3.1.2. 程式

在大多現代作業系統中, 每個程式在它自身的程式( process )內執行. 我們通過在 shell 中鍵入命令或直接在選單中選擇來執行一個程式/程式. Python 允許你在一個指令碼內執行一個新的程式.

大多程式相關函式通過 os 模組定義. 相關內容請參閱 第 1.4.4 小節 .


3.2. threading 模組

(可選) threading 模組為執行緒提供了一個高階介面, 如 Example 3-1 所示. 它源自 Java 的執行緒實現. 和低階的 thread 模組相同, 只有你在編譯直譯器時開啟了執行緒支援才可以使用它 .

你只需要繼承 Thread 類, 定義好 run 方法, 就可以建立一 個新的執行緒. 使用時首先建立該類的一個或多個例項, 然後呼叫 start 方法. 這樣每個例項的run 方法都會執行在它自己的執行緒裡.

3.2.0.1. Example 3-1. 使用 threading 模組

File: threading-example-1.py import threading import time, random class Counter: def _ _init_ _(self): self.lock = threading.Lock() self.value = 0 def increment(self): self.lock.acquire() # critical section self.value = value = self.value + 1 self.lock.release() return value counter = Counter() class Worker(threading.Thread): def run(self): for i in range(10): # pretend we're doing something that takes 10�00 ms value = counter.increment() # increment global counter time.sleep(random.randint(10, 100) / 1000.0) print self.getName(), "-- task", i, "finished", value # # try it for i in range(10): Worker().start() # start a worker Thread-1 -- task 0 finished 1 Thread-3 -- task 0 finished 3 Thread-7 -- task 0 finished 8 Thread-1 -- task 1 finished 7 Thread-4 -- task 0 Thread-5 -- task 0 finished 4 finished 5 Thread-8 -- task 0 Thread-6 -- task 0 finished 9 finished 6 ... Thread-6 -- task 9 finished 98 Thread-4 -- task 9 finished 99 Thread-9 -- task 9 finished 100

Example 3-1 使用了 Lock 物件來在全域性 Counter 物件裡建立臨界區 (critical section). 如果刪除了 acquire 和 release 語句, 那麼 Counter 很可能不會到達 100.


3.3. Queue 模組

Queue 模組提供了一個執行緒安全的佇列 (queue) 實現, 如 Example 3-2 所示. 你可以通過它在多個執行緒裡安全訪問同個物件.

3.3.0.1. Example 3-2. 使用 Queue 模組

File: queue-example-1.py import threading import Queue import time, random WORKERS = 2 class Worker(threading.Thread): def _ _init_ _(self, queue): self._ _queue = queue threading.Thread._ _init_ _(self) def run(self): while 1: item = self._ _queue.get() if item is None: break # reached end of queue # pretend we're doing something that takes 10�00 ms time.sleep(random.randint(10, 100) / 1000.0) print "task", item, "finished" # # try it queue = Queue.Queue(0) for i in range(WORKERS): Worker(queue).start() # start a worker for i in range(10): queue.put(i) for i in range(WORKERS): queue.put(None) # add end-of-queue markers task 1 finished task 0 finished task 3 finished task 2 finished task 4 finished task 5 finished task 7 finished task 6 finished task 9 finished task 8 finished

Example 3-3 展示瞭如何限制佇列的大小. 如果佇列滿了, 那麼控制主執行緒 (producer threads) 被阻塞, 等待專案被彈出 (pop off).

3.3.0.2. Example 3-3. 使用限制大小的 Queue 模組

File: queue-example-2.py import threading import Queue import time, random WORKERS = 2 class Worker(threading.Thread): def _ _init_ _(self, queue): self._ _queue = queue threading.Thread._ _init_ _(self) def run(self): while 1: item = self._ _queue.get() if item is None: break # reached end of queue # pretend we're doing something that takes 10�00 ms time.sleep(random.randint(10, 100) / 1000.0) print "task", item, "finished" # # run with limited queue queue = Queue.Queue(3) for i in range(WORKERS): Worker(queue).start() # start a worker for item in range(10): print "push", item queue.put(item) for i in range(WORKERS): queue.put(None) # add end-of-queue markers push 0 push 1 push 2 push 3 push 4 push 5 task 0 finished push 6 task 1 finished push 7 task 2 finished push 8 task 3 finished push 9 task 4 finished task 6 finished task 5 finished task 7 finished task 9 finished task 8 finished

你可以通過繼承 Queue 類來修改它的行為. Example 3-4 為我們展示了一個簡單的具有優先順序的佇列. 它接受一個元組作為引數, 元組的第一個成員表示優先順序(數值越小優先順序越高).

3.3.0.3. Example 3-4. 使用 Queue 模組實現優先順序佇列

File: queue-example-3.py import Queue import bisect Empty = Queue.Empty class PriorityQueue(Queue.Queue): "Thread-safe priority queue" def _put(self, item): # insert in order bisect.insort(self.queue, item) # # try it queue = PriorityQueue(0) # add items out of order queue.put((20, "second")) queue.put((10, "first")) queue.put((30, "third")) # print queue contents try: while 1: print queue.get_nowait() except Empty: pass third second first

Example 3-5 展示了一個簡單的堆疊 (stack) 實現 (末尾新增, 頭部彈出, 而非頭部新增, 頭部彈出).

3.3.0.4. Example 3-5. 使用 Queue 模組實現一個堆疊

File: queue-example-4.py import Queue Empty = Queue.Empty class Stack(Queue.Queue): "Thread-safe stack" def _put(self, item): # insert at the beginning of queue, not at the end self.queue.insert(0, item) # method aliases push = Queue.Queue.put pop = Queue.Queue.get pop_nowait = Queue.Queue.get_nowait # # try it stack = Stack(0) # push items on stack stack.push("first") stack.push("second") stack.push("third") # print stack contents try: while 1: print stack.pop_nowait() except Empty: pass third second first

3.4. thread 模組

(可選) thread 模組提為執行緒提供了一個低階 (low_level) 的介面, 如 Example 3-6 所示. 只有你在編譯直譯器時開啟了執行緒支援才可以使用它. 如果沒有特殊需要, 最好使用高階介面 threading 模組替代.

3.4.0.1. Example 3-6. 使用 thread 模組

File: thread-example-1.py import thread import time, random def worker(): for i in range(50): # pretend we're doing something that takes 10�00 ms time.sleep(random.randint(10, 100) / 1000.0) print thread.get_ident(), "-- task", i, "finished" # # try it out! for i in range(2): thread.start_new_thread(worker, ()) time.sleep(1) print "goodbye!" 311 -- task 0 finished 265 -- task 0 finished 265 -- task 1 finished 311 -- task 1 finished ... 265 -- task 17 finished 311 -- task 13 finished 265 -- task 18 finished goodbye!

注意當主程式退出的時候, 所有的執行緒也隨著退出. 而 threading 模組不存在這個問題 . (該行為可改變)


3.5. commands 模組

(只用於 Unix) commands 模組包含一些用於執行外部命令的函式. Example 3-7 展示了這個模組.

3.5.0.1. Example 3-7. 使用 commands 模組

File: commands-example-1.py import commands stat, output = commands.getstatusoutput("ls -lR") print "status", "=>", stat print "output", "=>", len(output), "bytes" status => 0 output => 171046 bytes

3.6. pipes 模組

(只用於 Unix) pipes 模組提供了 "轉換管道 (conversion pipelines)" 的支援. 你可以建立包含許多外部工具呼叫的管道來處理多個檔案. 如 Example 3-8 所示.

3.6.0.1. Example 3-8. 使用 pipes 模組

File: pipes-example-1.py import pipes t = pipes.Template() # create a pipeline # 這裡 " - " 代表從標準輸入讀入內容 t.append("sort", "--") t.append("uniq", "--") # filter some text # 這裡空字串代表標準輸出 t.copy("samples/sample.txt", "")Alan Jones (sensible party) Kevin Phillips-Bong (slightly silly) Tarquin Fin-tim-lin-bin-whin-bim-lin-bus-stop-F'tang-F'tang-Olé-Biscuitbarrel

3.7. popen2 模組

popen2 模組允許你執行外部命令, 並通過流來分別訪問它的 stdin 和 stdout ( 可能還有 stderr ).

在 python 1.5.2 以及之前版本, 該模組只存在於 Unix 平臺上. 2.0 後, Windows 下也實現了該函式. Example 3-9 展示瞭如何使用該模組來給字串排序.

3.7.0.1. Example 3-9. 使用 popen2 模組對字串排序Module to Sort Strings

File: popen2-example-1.py import popen2, string fin, fout = popen2.popen2("sort") fout.write("foo/n") fout.write("bar/n") fout.close() print fin.readline(), print fin.readline(), fin.close() bar foo

Example 3-10 展示瞭如何使用該模組控制應用程式 .

3.7.0.2. Example 3-10. 使用 popen2 模組控制 gnuchess

File: popen2-example-2.py import popen2 import string class Chess: "Interface class for chesstool-compatible programs" def _ _init_ _(self, engine = "gnuchessc"): self.fin, self.fout = popen2.popen2(engine) s = self.fin.readline() if s != "Chess/n": raise IOError, "incompatible chess program" def move(self, move): self.fout.write(move + "/n") self.fout.flush() my = self.fin.readline() if my == "Illegal move": raise ValueError, "illegal move" his = self.fin.readline() return string.split(his)[2] def quit(self): self.fout.write("quit/n") self.fout.flush() # # play a few moves g = Chess() print g.move("a2a4") print g.move("b2b3") g.quit() b8c6 e7e5

3.8. signal 模組

你可以使用 signal 模組配置你自己的訊號處理器 (signal handler), 如 Example 3-11 所示. 當直譯器收到某個訊號時, 訊號處理器會立即執行.

3.8.0.1. Example 3-11. 使用 signal 模組

File: signal-example-1.py import signal import time def handler(signo, frame): print "got signal", signo signal.signal(signal.SIGALRM, handler) # wake me up in two seconds signal.alarm(2) now = time.time() time.sleep(200) print "slept for", time.time() - now, "seconds" got signal 14 slept for 1.99262607098 seconds

4. 資料表示

"PALO ALTO, Calif. - Intel says its Pentium Pro and new Pentium II chips have a flaw that can cause computers to sometimes make mistakes but said the problems could be fixed easily with rewritten software."
- Reuters telegram

4.1. 概覽

本章描述了一些用於在 Python 物件和其他資料表示型別間相互轉換的模組. 這些模組通常用於讀寫特定的檔案格式或是儲存/取出 Python 變數.

4.1.1. 二進位制資料

Python 提供了一些用於二進位制資料解碼/編碼的模組. struct 模組用於在 二進位制資料結構(例如 C 中的 struct )和 Python 元組間轉換. array 模組將二進位制資料陣列 ( C arrays )封裝為 Python 序列物件.

4.1.2. 自描述格式

marshal 和 pickle 模組用於在不同的 Python 程式間共享/傳遞資料.

marshal 模組使用了簡單的自描述格式( Self-Describing Formats ), 它支援大多的內建資料型別, 包括 code 物件. Python 自身也使用了這個格式來儲存編譯後程式碼( .pyc 檔案).

pickle 模組提供了更復雜的格式, 它支援使用者定義的類, 自引用資料結構等等. pickle 是用 Python 寫的, 相對來說速度較慢, 不過還有一個 cPickle 模組, 使用 C 實現了相同的功能, 速度和 marshal 不相上下.

4.1.3. 輸出格式

一些模組提供了增強的格式化輸出, 用來補充內建的 repr 函式和 % 字串格式化操作符.

pprint 模組幾乎可以將任何 Python 資料結構很好地列印出來(提高可讀性).

repr 模組可以用來替換內建同名函式. 該模組與內建函式不同的是它限制了很多輸出形式: 他只會 輸出字串的前 30 個字元, 它只列印巢狀資料結構的幾個等級, 等等.

4.1.4. 編碼二進位制資料

Python 支援大部分常見二進位制編碼, 例如 base64 , binhex (一種 Macintosh 格式) , quoted printable , 以及 uu 編碼.


4.2. array 模組

array 模組實現了一個有效的陣列儲存型別. 陣列和列表類似, 但其中所有的專案必須為相同的 型別. 該型別在陣列建立時指定.

Examples 4-1 到 4-5 都是很簡單的範例. Example 4-1 建立了一個 array 物件, 然後使用 tostring 方法將內部緩衝區( internal buffer )複製到字串.

4.2.0.1. Example 4-1. 使用 array 模組將數列轉換為字串

File: array-example-1.py import array a = array.array("B", range(16)) # unsigned char b = array.array("h", range(16)) # signed short print a print repr(a.tostring()) print b print repr(b.tostring()) array('B', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) '/000/001/002/003/004/005/006/007/010/011/012/013/014/015/016/017' array('h', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) '/000/000/001/000/002/000/003/000/004/000/005/000/006/000/007/000 /010/000/011/000/012/000/013/000/014/000/015/000/016/000/017/000'

array 物件可以作為一個普通列表對待, 如 Example 4-2 所示. 不過, 你不能連線兩個不同型別的陣列.

4.2.0.2. Example 4-2. 作為普通序列操作陣列

File: array-example-2.py import array a = array.array("B", [1, 2, 3]) a.append(4) a = a + a a = a[2:-2] print a print repr(a.tostring()) for i in a: print i, array('B', [3, 4, 1, 2]) '/003/004/001/002' 3 4 1 2

該模組還提供了用於轉換原始二進位制資料到整數序列(或浮點數數列, 具體情況決定)的方法, 如 Example 4-3 所示.

4.2.0.3. Example 4-3. 使用陣列將字串轉換為整數列表

File: array-example-3.py import array a = array.array("i", "fish license") # signed integer print a print repr(a.tostring()) print a.tolist() array('i', [1752394086, 1667853344, 1702063717]) 'fish license' [1752394086, 1667853344, 1702063717]

最後, Example 4-4 展示瞭如何使用該模組判斷當前平臺的位元組序( endianess ) .

4.2.0.4. Example 4-4. 使用 array 模組判斷平臺位元組序

File: array-example-4.py import array def little_endian(): return ord(array.array("i",[1]).tostring()[0]) if little_endian(): print "little-endian platform (intel, alpha)" else: print "big-endian platform (motorola, sparc)" big-endian platform (motorola, sparc)

Python 2.0 以及以後版本提供了 sys.byteorder 屬性, 可以更簡單地判斷位元組序 (屬性值為 "little" 或 "big" ), 如 Example 4-5 所示.

4.2.0.5. Example 4-5. 使用 sys.byteorder 屬性判斷平臺位元組序( Python 2.0 及以後)

File: sys-byteorder-example-1.py import sys # 2.0 and later if sys.byteorder == "little": print "little-endian platform (intel, alpha)" else: print "big-endian platform (motorola, sparc)" big-endian platform (motorola, sparc)

4.3. struct 模組

struct 模組用於轉換二進位制字串和 Python 元組. pack 函式接受格式字串以及額外引數, 根據指定格式將額外引數轉換為二進位制字串. upack 函式接受一個字串作為引數, 返回一個元組. 如 Example 4-6 所示.

4.3.0.1. Example 4-6. 使用 struct 模組

File: struct-example-1.py import struct # native byteorder buffer = struct.pack("ihb", 1, 2, 3) print repr(buffer) print struct.unpack("ihb", buffer) # data from a sequence, network byteorder data = [1, 2, 3] buffer = apply(struct.pack, ("!ihb",) + tuple(data)) print repr(buffer) print struct.unpack("!ihb", buffer) # in 2.0, the apply statement can also be written as: # buffer = struct.pack("!ihb", *data) '/001/000/000/000/002/000/003' (1, 2, 3) '/000/000/000/001/000/002/003' (1, 2, 3)

4.4. xdrlib 模組

xdrlib 模組用於在 Python 資料型別和 Sun 的 external data representation (XDR) 間相互轉化, 如 Example 4-7 所示.

4.4.0.1. Example 4-7. 使用 xdrlib 模組

File: xdrlib-example-1.py import xdrlib # # create a packer and add some data to it p = xdrlib.Packer() p.pack_uint(1) p.pack_string("spam") data = p.get_buffer() print "packed:", repr(data) # # create an unpacker and use it to decode the data u = xdrlib.Unpacker(data) print "unpacked:", u.unpack_uint(), repr(u.unpack_string()) u.done() packed: '/000/000/000/001/000/000/000/004spam' unpacked: 1 'spam'

Sun 在 remote procedure call (RPC) 協議中使用了 XDR 格式. Example 4-8 雖然不完整, 但它展示瞭如何建立一個 RPC 請求包.

4.4.0.2. Example 4-8. 使用 xdrlib 模組傳送 RPC 呼叫包

File: xdrlib-example-2.py import xdrlib # some constants (see the RPC specs for details) RPC_CALL = 1 RPC_VERSION = 2 MY_PROGRAM_ID = 1234 # assigned by Sun MY_VERSION_ID = 1000 MY_TIME_PROCEDURE_ID = 9999 AUTH_NULL = 0 transaction = 1 p = xdrlib.Packer() # send a Sun RPC call package p.pack_uint(transaction) p.pack_enum(RPC_CALL) p.pack_uint(RPC_VERSION) p.pack_uint(MY_PROGRAM_ID) p.pack_uint(MY_VERSION_ID) p.pack_uint(MY_TIME_PROCEDURE_ID) p.pack_enum(AUTH_NULL) p.pack_uint(0) p.pack_enum(AUTH_NULL) p.pack_uint(0) print repr(p.get_buffer())'/000/000/000/001/000/000/000/001/000/000/000/002/000/000/004/322 /000/000/003/350/000/000/'/017/000/000/000/000/000/000/000/000/000 /000/000/000/000/000/000/000'

4.5. marshal 模組

marshal 模組可以把不連續的資料組合起來 - 與字串相互轉化, 這樣它們就可以寫入檔案或是在網路中傳輸. 如 Example 4-9 所示.

marshal 模組使用了簡單的自描述格式. 對於每個資料專案, 格式化後的字串都包含一個型別程式碼, 然後是一個或多個型別標識區域. 整數使用小位元組序( little-endian order )儲存, 字串儲存時和它自身內容長度相同(可能包含空位元組), 元組由組成它的物件組合表示.

4.5.0.1. Example 4-9. 使用 marshal 模組組合不連續資料

File: marshal-example-1.py import marshal value = ( "this is a string", [1, 2, 3, 4], ("more tuples", 1.0, 2.3, 4.5), "this is yet another string" ) data = marshal.dumps(value) # intermediate format print type(data), len(data) print "-"*50 print repr(data) print "-"*50 print marshal.loads(data) <type 'string'> 118 -------------------------------------------------- '(/004/000/000/000s/020/000/000/000this is a string [/004/000/000/000i/001/000/000/000i/002/000/000/000 i/003/000/000/000i/004/000/000/000(/004/000/000/000 s/013/000/000/000more tuplesf/0031.0f/0032.3f/0034. 5s/032/000/000/000this is yet another string' -------------------------------------------------- ('this is a string', [1, 2, 3, 4], ('more tuples', 1.0, 2.3, 4.5), 'this is yet another string')

marshal 模組還可以處理 code 物件(它用於儲存預編譯的 Python 模組). 如 Example 4-10 所示.

4.5.0.2. Example 4-10. 使用 marshal 模組處理程式碼

File: marshal-example-2.py import marshal script = """ print 'hello' """ code = compile(script, "<script>", "exec") data = marshal.dumps(code) # intermediate format print type(data), len(data) print "-"*50 print repr(data) print "-"*50 exec marshal.loads(data) <type 'string'> 81 -------------------------------------------------- 'c/000/000/000/000/001/000/000/000s/017/000/000/00 0/177/000/000/177/002/000d/000/000GHd/001/000S(/00 2/000/000/000s/005/000/000/000helloN(/000/000/000/ 000(/000/000/000/000s/010/000/000/000<script>s/001 /000/000/000?/002/000s/000/000/000/000' -------------------------------------------------- hello

4.6. pickle 模組

pickle 模組同 marshal 模組相同, 將資料連續化, 便於儲存傳輸. 它比 marshal 要慢一些, 但它可以處理類例項, 共享的元素, 以及遞迴資料結構等.

4.6.0.1. Example 4-11. 使用 pickle 模組

File: pickle-example-1.py import pickle value = ( "this is a string", [1, 2, 3, 4], ("more tuples", 1.0, 2.3, 4.5), "this is yet another string" ) data = pickle.dumps(value) # intermediate format print type(data), len(data) print "-"*50 print data print "-"*50 print pickle.loads(data) <type 'string'> 121 -------------------------------------------------- (S'this is a string' p0 (lp1 I1 aI2 aI3 aI4 a(S'more tuples' p2 F1.0 F2.3 F4.5 tp3 S'this is yet another string' p4 tp5 . -------------------------------------------------- ('this is a string', [1, 2, 3, 4], ('more tuples', 1.0, 2.3, 4.5), 'this is yet another string')

不過另一方面, pickle 不能處理 code 物件(可以參閱 copy_reg 模組來完成這個).

預設情況下, pickle 使用急於文字的格式. 你也可以使用二進位制格式, 這樣數字和二進位制 字串就會以緊密的格式儲存, 這樣檔案就會更小點. 如 Example 4-12 所示.

4.6.0.2. Example 4-12. 使用 pickle 模組的二進位制模式

File: pickle-example-2.py import pickle import math value = ( "this is a long string" * 100, [1.2345678, 2.3456789, 3.4567890] * 100 ) # text mode data = pickle.dumps(value) print type(data), len(data), pickle.loads(data) == value # binary mode data = pickle.dumps(value, 1) print type(data), len(data), pickle.loads(data) == value

4.7. cPickle 模組

(可選, 注意大小寫) cPickle 模組是針對 pickle 模組的一個更快的實現. 如 Example 4-13 所示.

4.7.0.1. Example 4-13. 使用 cPickle 模組

File: cpickle-example-1.py try: import cPickle pickle = cPickle except ImportError: import pickle

4.8. copy_reg 模組

你可以使用 copy_reg 模組註冊你自己的擴充套件型別. 這樣 pickle 和 copy 模組就會知道 如何處理非標準型別.

例如, 標準的 pickle 實現不能用來處理 Python code 物件, 如下所示:

File: copy-reg-example-1.py import pickle CODE = """ print 'good evening' """ code = compile(CODE, "<string>", "exec") exec code exec pickle.loads(pickle.dumps(code)) good evening Traceback (innermost last): ... pickle.PicklingError: can't pickle 'code' objects

我們可以註冊一個 code 物件處理器來完成目標. 處理器應包含兩個部分: 一個 pickler , 接受 code 物件 並返回一個只包含簡單資料型別的元組, 以及一個unpickler , 作用相反, 接受這樣的元組作為引數. 如 Example 4-14 所示.

4.8.0.1. Example 4-14. 使用 copy_reg 模組實現 code 物件的 pickle 操作

File: copy-reg-example-2.py import copy_reg import pickle, marshal, types # # register a pickle handler for code objects def code_unpickler(data): return marshal.loads(data) def code_pickler(code): return code_unpickler, (marshal.dumps(code),) copy_reg.pickle(types.CodeType, code_pickler, code_unpickler) # # try it out CODE = """ print "suppose he's got a pointed stick" """ code = compile(CODE, "<string>", "exec") exec code exec pickle.loads(pickle.dumps(code)) suppose he's got a pointed stick suppose he's got a pointed stick

如果你是在網路中傳輸 pickle 後的資料, 那麼請確保自定義的 unpickler 在資料接收端也是可用的.

Example 4-15 展示瞭如何實現 pickle 一個開啟的檔案物件.

4.8.0.2. Example 4-15. 使用 copy_reg 模組實現檔案物件的 pickle 操作

File: copy-reg-example-3.py import copy_reg import pickle, types import StringIO # # register a pickle handler for file objects def file_unpickler(position, data): file = StringIO.StringIO(data) file.seek(position) return file def file_pickler(code): position = file.tell() file.seek(0) data = file.read() file.seek(position) return file_unpickler, (position, data) copy_reg.pickle(types.FileType, file_pickler, file_unpickler) # # try it out file = open("samples/sample.txt", "rb") print file.read(120), print "<here>", print pickle.loads(pickle.dumps(file)).read() We will perhaps eventually be writing only small modules, which are identified by name as they are used to build larger <here> ones, so that devices like indentation, rather than delimiters, might become feasible for expressing local structure in the source language. -- Donald E. Knuth, December 1974

4.9. pprint 模組

pprint 模組( pretty printer )用於列印 Python 資料結構. 當你在命令列下列印 特定資料結構時你會發現它很有用(輸出格式比較整齊, 便於閱讀).

4.9.0.1. Example 4-16. 使用 pprint 模組

File: pprint-example-1.py import pprint data = ( "this is a string", [1, 2, 3, 4], ("more tuples", 1.0, 2.3, 4.5), "this is yet another string" ) pprint.pprint(data) ('this is a string', [1, 2, 3, 4], ('more tuples', 1.0, 2.3, 4.5), 'this is yet another string')

4.10. repr 模組

repr 模組提供了內建 repr 函式的另個版本. 它限制了很多(字串長度, 遞迴等). Example 4-17 展示瞭如何使用該模組.

4.10.0.1. Example 4-17. 使用 repr 模組

File: repr-example-1.py # note: this overrides the built-in 'repr' function from repr import repr # an annoyingly recursive data structure data = ( "X" * 100000, ) data = [data] data.append(data) print repr(data) [('XXXXXXXXXXXX...XXXXXXXXXXXXX',), [('XXXXXXXXXXXX...XXXXXXXXXX XXX',), [('XXXXXXXXXXXX...XXXXXXXXXXXXX',), [('XXXXXXXXXXXX...XX XXXXXXXXXXX',), [('XXXXXXXXXXXX...XXXXXXXXXXXXX',), [(...), [... ]]]]]]]

4.11. base64 模組

base64 編碼體系用於將任意二進位制資料轉換為純文字. 它將一個 3 位元組的二進位制位元組組 轉換為 4 個文字字元組儲存, 而且規定只允許以下集合中的字元出現:

ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789+/

另外, = 用於填充資料流的末尾.

Example 4-18 展示瞭如何使用 encode 和 decode 函式操作檔案物件.

4.11.0.1. Example 4-18. 使用 base64 模組編碼檔案

File: base64-example-1.py import base64 MESSAGE = "life of brian" file = open("out.txt", "w") file.write(MESSAGE) file.close() base64.encode(open("out.txt"), open("out.b64", "w")) base64.decode(open("out.b64"), open("out.txt", "w")) print "original:", repr(MESSAGE) print "encoded message:", repr(open("out.b64").read()) print "decoded message:", repr(open("out.txt").read()) original: 'life of brian' encoded message: 'bGlmZSBvZiBicmlhbg==/012' decoded message: 'life of brian'

Example 4-19 展示瞭如何使用 encodestring 和 decodestring 函式在字串間轉換. 它們是 encode 和 decode 函式的頂層封裝. 使用 StringIO 物件處理輸入和輸出.

4.11.0.2. Example 4-19. 使用 base64 模組編碼字串

File: base64-example-2.py import base64 MESSAGE = "life of brian" data = base64.encodestring(MESSAGE) original_data = base64.decodestring(data) print "original:", repr(MESSAGE) print "encoded data:", repr(data) print "decoded data:", repr(original_data) original: 'life of brian' encoded data: 'bGlmZSBvZiBicmlhbg==/012' decoded data: 'life of brian'

Example 4-20 展示瞭如何將使用者名稱和密碼轉換為 HTTP 基本身份驗證字串.

4.11.0.3. Example 4-20. 使用 base64 模組做基本驗證

File: base64-example-3.py import base64 def getbasic(user, password): # basic authentication (according to HTTP) return base64.encodestring(user + ":" + password)  print getbasic("Aladdin", "open sesame") 'QWxhZGRpbjpvcGVuIHNlc2FtZQ=='

最後, Example 4-21 展示了一個實用小工具, 它可以把 GIF 格式轉換為 Python 指令碼, 便於使用 Tkinter 庫.

4.11.0.4. Example 4-21. 使用 base64 為 Tkinter 封裝 GIF 格式

File: base64-example-4.py import base64, sys if not sys.argv[1:]: print "Usage: gif2tk.py giffile >pyfile" sys.exit(1) data = open(sys.argv[1], "rb").read() if data[:4] != "GIF8": print sys.argv[1], "is not a GIF file" sys.exit(1) print '# generated from', sys.argv[1], 'by gif2tk.py' print print 'from Tkinter import PhotoImage'  print print 'image = PhotoImage(data="""' print base64.encodestring(data), print '""")' # generated from samples/sample.gif by gif2tk.py from Tkinter import PhotoImage image = PhotoImage(data=""" R0lGODlhoAB4APcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgAQEBIwEBIyMBJRUlISE/LRUBAQE ... AjmQBFmQBnmQCJmQCrmQDNmQDvmQEBmREnkRAQEAOw== """)

4.12. binhex 模組

binhex 模組用於到 Macintosh BinHex 格式的相互轉化. 如 Example 4-22 所示.

4.12.0.1. Example 4-22. 使用 binhex 模組

File: binhex-example-1.py import binhex import sys infile = "samples/sample.jpg" binhex.binhex(infile, sys.stdout) (This file must be converted with BinHex 4.0) :#R0KEA"XC5jUF'F!2j!)!*!%%TS!N!4RdrrBrq!!%%T'58B!!3%!!!%!!3!!rpX !3`!)"JB("J8)"`F(#3N)#J`8$3`,#``C%K-2&"dD(aiG'K`F)#3Z*b!L,#-F(#J h+5``-63d0"mR16di-M`Z-c3brpX!3`%*#3N-#``B$3dB-L%F)6+3-[r!!"%)!)! !J!-")J!#%3%$%3(ra!!I!!!""3'3"J#3#!%#!`3&"JF)#3S,rm3!Y4!!!J%$!`) %!`8&"!3!!!&p!3)$!!34"4)K-8%'%e&K"b*a&$+"ND%))d+a`495dI!N-f*bJJN

該模組有兩個函式 binhex 和 hexbin .


4.13. quopri 模組

quopri 模組基於 MIME 標準實現了引用的可列印編碼( quoted printable encoding ).

這樣的編碼可以將不包含或只包含一部分U.S. ASCII 文字的資訊, 例如大多歐洲語言, 中文, 轉換為只包含 U.S. ASCII 的資訊. 在一些老式的 mail 代理中你會發現這很有用, 因為它們一般不支援特殊. 如 Example 4-23 所示.

4.13.0.1. Example 4-23. 使用 quopri 模組

File: quopri-example-1.py import quopri import StringIO # helpers (the quopri module only supports file-to-file conversion) def encodestring(instring, tabs=0): outfile = StringIO.StringIO() quopri.encode(StringIO.StringIO(instring), outfile, tabs) return outfile.getvalue() def decodestring(instring): outfile = StringIO.StringIO() quopri.decode(StringIO.StringIO(instring), outfile) return outfile.getvalue() # # try it out MESSAGE = "å i åa ä e ö!" encoded_message = encodestring(MESSAGE) decoded_message = decodestring(encoded_message) print "original:", MESSAGE print "encoded message:", repr(encoded_message) print "decoded message:", decoded_message original: å i åa ä e ö! encoded message: '=E5 i =E5a =E4 e =F6!/012' decoded message: å i åa ä e ö!

如 Example 4-23 所示, 非 U.S. 字元通過等號 (=) 附加兩個十六進位制字元來表示. 這裡需要注意等號也是使用這樣的方式( "=3D" )來表示的, 以及換行符( "=20" ). 其他字元不會被改變. 所以如果你沒有用太多的怪異字元的話, 編碼後字串依然可讀性很好.

(Europeans generally hate this encoding and strongly believe that certain U.S. programmers deserve to be slapped in the head with a huge great fish to the jolly music of Edward German....)


4.14. uu 模組

uu 編碼體系用於將任意二進位制資料轉換為普通文字格式. 該格式在新聞組中很流行, 但逐漸被 base64 編碼取代.

uu 編碼將每個 3 位元組( 24 位)的資料組轉換為 4 個可列印字元(每個字元 6 位), 使用從 chr(32) (空格) 到 chr(95) 的字元. uu 編碼通常會使資料大小增加 40% .

一個編碼後的資料流以一個新行開始, 它包含檔案的許可權( Unix 格式)和檔名, 以 end 行結尾:

begin 666 sample.jpg M_]C_X 02D9)1@ ! 0 0 ! #_VP!# @&!@<&!0@'!P<)'0@*#!0-# L+ ...more lines like this... end

uu 模組提供了兩個函式: encode 和 decode .

encode(infile, outfile, filename) 函式從編碼輸入檔案中的資料, 然後寫入到輸出檔案中. 如 Example 4-24 所示. infile 和 outfile 可以是檔名或檔案物件. filename 引數作為起始域的檔名寫入.

4.14.0.1. Example 4-24. 使用 uu 模組編碼二進位制檔案

File: uu-example-1.py import uu import os, sys infile = "samples/sample.jpg" uu.encode(infile, sys.stdout, os.path.basename(infile)) begin 666 sample.jpg M_]C_X 02D9)1@ ! 0 0 ! #_VP!# @&!@<&!0@'!P<)"0@*#!0-# L+ M#!D2$P/4'1H?'AT:'!P@)"XG("(L(QP<*#<I+# Q-#0T'R<Y/3@R/"XS-#+_ MVP!# 0D)"0P+#!@-#1@R(1PA,C(R,C(R,C(R,C(R,C(R,C(R,C(R,C(R,C(R M,C(R,C(R,C(R,C(R,C(R,C(R,C(R,C+_P 1" " ( # 2( A$! Q$!_/0  M'P 04! 0$! 0$ $" P0%!@<("0H+_/0 M1 @$# P($ P4%

decode(infile, outfile) 函式用來解碼 uu 編碼的資料. 同樣地, 引數可以是檔名也可以是檔案物件. 如 Example 4-25 所示.

4.14.0.2. Example 4-25. 使用 uu 模組解碼 uu 格式的檔案

File: uu-example-2.py import uu import StringIO infile = "samples/sample.uue" outfile = "samples/sample.jpg" # # decode fi = open(infile) fo = StringIO.StringIO() uu.decode(fi, fo) # # compare with original data file data = open(outfile, "rb").read() if fo.getvalue() == data: print len(data), "bytes ok"

4.15. binascii 模組

binascii 提供了多個編碼的支援函式, 包括 base64 , binhex , 以及 uu . 如 Example 4-26 所示.

2.0 及以後版本中, 你還可以使用它在二進位制資料和十六進位制字串中相互轉換.

4.15.0.1. Example 4-26. 使用 binascii 模組

File: binascii-example-1.py import binascii text = "hello, mrs teal" data = binascii.b2a_base64(text) text = binascii.a2b_base64(data) print text, "<=>", repr(data) data = binascii.b2a_uu(text) text = binascii.a2b_uu(data) print text, "<=>", repr(data) data = binascii.b2a_hqx(text) text = binascii.a2b_hqx(data)[0] print text, "<=>", repr(data) # 2.0 and newer data = binascii.b2a_hex(text) text = binascii.a2b_hex(data) print text, "<=>", repr(data) hello, mrs teal <=> 'aGVsbG8sIG1ycyB0ZWFs/012' hello, mrs teal <=> '/:&5L;&//L(&UR<R!T96%L/012' hello, mrs teal <=> 'D/'9XE/'mX)/'ebFb"dC@&X' hello, mrs teal <=> '68656c6c6f2c206d7273207465616c'

5. 檔案格式


5.1. 概覽

本章將描述用於處理不同檔案格式的模組.

5.1.1. Markup 語言

Python 提供了一些用於處理可擴充套件標記語言( Extensible Markup Language , XML ) 和超文字標記語言( Hypertext Markup Language , HTML )的擴充套件. Python 同樣提供了對 標準通用標記語言( Standard Generalized Markup Language , SGML )的支援.

所有這些格式都有著相同的結構, 因為 HTML 和 XML 都來自 SGML . 每個文件都是由 起始標籤( start tags ), 結束標籤( end tags ), 文字(又叫字元資料), 以及實體引用( entity references )構成:

<document name="sample.xml"> <header>This is a header</header> <body>This is the body text. The text can contain plain text (&quot;character data&quot;), tags, and entities. </body> </document>

在這個例子中, <document><header>, 以及 <body> 是起始標籤. 每個起始標籤都有一個對應的結束標籤, 使用斜線 "/" 標記. 起始標籤可以包含多個屬性, 比如這裡的 name 屬性.

起始標籤和它對應的結束標籤中的任何東西被稱為 元素( element ). 這裡 document 元素包含 header 和 body 兩個元素.

&quot; 是一個字元實體( character entity ). 字元實體用於在文字區域中表示特殊的保留字元, 使用 & 指示. 這裡它代表一個引號, 常見字元實體還有 " < ( &lt; )" 和 " > ( &gt; )" .

雖然 XML , HTML , SGML 使用相同的結構塊, 但它們還有一些不同點. 在 XML 中, 所有元素必須有起始和結束標籤, 所有標籤必須正確巢狀( well-formed ). 而且 XML 是區分大小寫的, 所以 <document> 和 <Document> 是不同的元素型別.

HTML 有很高靈活性, HTML 語法分析器一般會自動補全缺失標籤; 例如, 當遇到一個以 <P> 標籤開始的新段落, 卻沒有對應結束標籤, 語法分析器會自動新增一個 </P> 標籤. HTML 也是區分大小寫的. 另一方面, XML 允許你定義任何元素, 而 HTML 使用一些由 HTML 規範定義的固定元素.

SGML 有著更高的靈活性, 你可以使用自己的宣告( declaration ) 定義原始檔如何轉換到元素結構, DTD ( document type description , 檔案型別定義)可以用來 檢查結構並補全缺失標籤. 技術上來說, HTML 和 XML 都是 SGML 應用, 有各自的 SGML 宣告, 而且 HTML 有一個標準 DTD .

Python 提供了多個 makeup 語言分析器. 由於 SGML 是最靈活的格式, Python 的 sgmllib 事實上很簡單. 它不會去處理 DTD , 不過你可以繼承它來提供更復雜的功能.

Python 的 HTML 支援基於 SGML 分析器. htmllib 將具體的格式輸出工作交給 formatter 物件. formatter 模組包含一些標準格式化標誌.

Python 的 XML 支援模組很複雜. 先前是隻有與 sgmllib 類似的 xmllib , 後來加入了更高階的 expat 模組(可選). 而最新版本中已經準備廢棄 xmllib ,啟用 xml 包作為工具集.

5.1.2. 配置檔案

ConfigParser 模組用於讀取簡單的配置檔案, 類似 Windows 下的 INI 檔案.

netrc 模組用於讀取 .netrc 配置檔案, shlex 模組用於讀取類似 shell 指令碼語法的配置檔案.

5.1.3. 壓縮檔案格式

Python 的標準庫提供了對 GZIP 和 ZIP ( 2.0 及以後) 格式的支援. 基於 zlib 模組, gzip 和 zipfile 模組分別用來處理這類檔案.


5.2. xmllib 模組

xmllib 已在當前版本中申明不支援.

xmlib 模組提供了一個簡單的 XML 語法分析器, 使用正規表示式將 XML 資料分離, 如 Example 5-1 所示. 語法分析器只對文件做基本的檢查, 例如是否只有一個頂層元素, 所有的標籤是否匹配.

XML 資料一塊一塊地傳送給 xmllib 分析器(例如在網路中傳輸的資料). 分析器在遇到起始標籤, 資料區域, 結束標籤, 和實體的時候呼叫不同的方法.

如果你只是對某些標籤感興趣, 你可以定義特殊的 start_tag 和 end_tag 方法, 這裡 tag 是標籤名稱. 這些 start 函式使用它們對應標籤的屬性作為引數呼叫(傳遞時為一個字典).

5.2.0.1. Example 5-1. 使用 xmllib 模組獲取元素的資訊

File: xmllib-example-1.py import xmllib class Parser(xmllib.XMLParser): # get quotation number def _ _init_ _(self, file=None): xmllib.XMLParser._ _init_ _(self) if file: self.load(file) def load(self, file): while 1: s = file.read(512) if not s: break self.feed(s) self.close() def start_quotation(self, attrs): print "id =>", attrs.get("id") raise EOFError try: c = Parser() c.load(open("samples/sample.xml")) except EOFError: pass id => 031

Example 5-2 展示了一個簡單(不完整)的內容輸出引擎( rendering engine ). 分析器有一個元素堆疊( _ _tags ), 它連同文字片斷傳遞給輸出生成器. 生成器會在 style 字典中查詢當前標籤的層次, 如果不存在, 它將根據樣式表建立一個新的樣式描述.

5.2.0.2. Example 5-2. 使用 xmllib 模組

File: xmllib-example-2.py import xmllib import string, sys STYLESHEET = { # each element can contribute one or more style elements "quotation": {"style": "italic"}, "lang": {"weight": "bold"}, "name": {"weight": "medium"}, } class Parser(xmllib.XMLParser): # a simple styling engine def _ _init_ _(self, renderer): xmllib.XMLParser._ _init_ _(self) self._ _data = [] self._ _tags = [] self._ _renderer = renderer def load(self, file): while 1: s = file.read(8192) if not s: break self.feed(s) self.close() def handle_data(self, data): self._ _data.append(data) def unknown_starttag(self, tag, attrs): if self._ _data: text = string.join(self._ _data, "") self._ _renderer.text(self._ _tags, text) self._ _tags.append(tag) self._ _data = [] def unknown_endtag(self, tag): self._ _tags.pop() if self._ _data: text = string.join(self._ _data, "") self._ _renderer.text(self._ _tags, text) self._ _data = [] class DumbRenderer: def _ _init_ _(self): self.cache = {} def text(self, tags, text): # render text in the style given by the tag stack tags = tuple(tags) style = self.cache.get(tags) if style is None: # figure out a combined style style = {} for tag in tags: s = STYLESHEET.get(tag) if s: style.update(s) self.cache[tags] = style # update cache # write to standard output sys.stdout.write("%s =>/n" % style) sys.stdout.write(" " + repr(text) + "/n") # # try it out r = DumbRenderer() c = Parser(r) c.load(open("samples/sample.xml")) {'style': 'italic'} => 'I/'ve had a lot of developers come up to me and/012say, "I haven/'t had this much fun in a long time. It sure beats/012writing ' {'style': 'italic', 'weight': 'bold'} => 'Cobol' {'style': 'italic'} => '" -- ' {'style': 'italic', 'weight': 'medium'} => 'James Gosling' {'style': 'italic'} => ', on/012' {'weight': 'bold'} => 'Java' {'style': 'italic'} => '.'

5.3. xml.parsers.expat 模組

(可選) xml.parsers.expat 模組是 James Clark's Expat XML parser 的介面. Example 5-3 展示了這個功能完整且效能很好的語法分析器.

5.3.0.1. Example 5-3. 使用 xml.parsers.expat 模組

File: xml-parsers-expat-example-1.py from xml.parsers import expat class Parser: def _ _init_ _(self): self._parser = expat.ParserCreate() self._parser.StartElementHandler = self.start self._parser.EndElementHandler = self.end self._parser.CharacterDataHandler = self.data def feed(self, data): self._parser.Parse(data, 0) def close(self): self._parser.Parse("", 1) # end of data del self._parser # get rid of circular references def start(self, tag, attrs): print "START", repr(tag), attrs def end(self, tag): print "END", repr(tag) def data(self, data): print "DATA", repr(data) p = Parser() p.feed("<tag>data</tag>") p.close() START u'tag' {} DATA u'data' END u'tag'

注意即使你傳入的是普通的文字, 這裡的分析器仍然會返回 Unicode 字串. 預設情況下, 分析器將源文字作為 UTF-8 解析. 如果要使用其他編碼, 請確保 XML 檔案包含 encoding 說明. 如 Example 5-4 所示.

5.3.0.2. Example 5-4. 使用 xml.parsers.expat 模組讀取 ISO Latin-1 文字

File: xml-parsers-expat-example-2.py from xml.parsers import expat class Parser: def _ _init_ _(self): self._parser = expat.ParserCreate() self._parser.StartElementHandler = self.start self._parser.EndElementHandler = self.end self._parser.CharacterDataHandler = self.data def feed(self, data): self._parser.Parse(data, 0) def close(self): self._parser.Parse("", 1) # end of data del self._parser # get rid of circular references def start(self, tag, attrs): print "START", repr(tag), attrs def end(self, tag): print "END", repr(tag) def data(self, data): print "DATA", repr(data) p = Parser() p.feed("""/ <?xml version='1.0' encoding='iso-8859-1'?> <author> <name>fredrik lundh</name> <city>linköping</city> </author> """ ) p.close() START u'author' {} DATA u'/012' START u'name' {} DATA u'fredrik lundh' END u'name' DATA u'/012' START u'city' {} DATA u'link/366ping' END u'city' DATA u'/012' END u'author'

5.4. sgmllib 模組

sgmllib 模組, 提供了一個基本的 SGML 語法分析器. 它與 xmllib 分析器基本相同, 但限制更少(而且不是很完善). 如 Example 5-5 所示.

和在 xmllib 中一樣, 這個分析器在遇到起始標籤, 資料區域, 結束標籤以及實體時呼叫內部方法. 如果你只是對某些標籤感興趣, 那麼你可以定義特殊的方法.

5.4.0.1. Example 5-5. 使用 sgmllib 模組提取 Title 元素

File: sgmllib-example-1.py import sgmllib import string class FoundTitle(Exception): pass class ExtractTitle(sgmllib.SGMLParser): def _ _init_ _(self, verbose=0): sgmllib.SGMLParser._ _init_ _(self, verbose) self.title = self.data = None def handle_data(self, data): if self.data is not None: self.data.append(data) def start_title(self, attrs): self.data = [] def end_title(self): self.title = string.join(self.data, "") raise FoundTitle # abort parsing! def extract(file): # extract title from an HTML/SGML stream p = ExtractTitle() try: while 1: # read small chunks s = file.read(512) if not s: break p.feed(s) p.close() except FoundTitle: return p.title return None # # try it out print "html", "=>", extract(open("samples/sample.htm")) print "sgml", "=>", extract(open("samples/sample.sgm")) html => A Title. sgml => Quotations

過載 unknown_starttag 和 unknown_endtag 方法就可以處理所有的標籤. 如 Example 5-6 所示.

5.4.0.2. Example 5-6. 使用 sgmllib 模組格式化 SGML 文件

File: sgmllib-example-2.py import sgmllib import cgi, sys class PrettyPrinter(sgmllib.SGMLParser): # A simple SGML pretty printer def _ _init_ _(self): # initialize base class sgmllib.SGMLParser._ _init_ _(self) self.flag = 0 def newline(self): # force newline, if necessary if self.flag: sys.stdout.write("/n") self.flag = 0 def unknown_starttag(self, tag, attrs): # called for each start tag # the attrs argument is a list of (attr, value) # tuples. convert it to a string. text = "" for attr, value in attrs: text = text + " %s='%s'" % (attr, cgi.escape(value)) self.newline() sys.stdout.write("<%s%s>/n" % (tag, text)) def handle_data(self, text): # called for each text section sys.stdout.write(text) self.flag = (text[-1:] != "/n") def handle_entityref(self, text): # called for each entity sys.stdout.write("&%s;" % text) def unknown_endtag(self, tag): # called for each end tag self.newline() sys.stdout.write("<%s>" % tag) # # try it out file = open("samples/sample.sgm") p = PrettyPrinter() p.feed(file.read()) p.close() <chapter> <title> Quotations <title> <epigraph> <attribution> eff-bot, June 1997 <attribution> <para> <quote> Nobody expects the Spanish Inquisition! Amongst our weaponry are such diverse elements as fear, surprise, ruthless efficiency, and an almost fanatical devotion to Guido, and nice red uniforms &mdash; oh, damn! <quote> <para> <epigraph> <chapter>

Example 5-7 檢查 SGML 文件是否是如 XML 那樣 "正確格式化", 所有的元素是否正確巢狀, 起始和結束標籤是否匹配等.

我們使用列表儲存所有起始標籤, 然後檢查每個結束標籤是否匹配前個起始標籤. 最後確認到達檔案末尾時沒有未關閉的標籤.

5.4.0.3. Example 5-7. 使用 sgmllib 模組檢查格式

File: sgmllib-example-3.py import sgmllib class WellFormednessChecker(sgmllib.SGMLParser): # check that an SGML document is 'well-formed' # (in the XML sense). def _ _init_ _(self, file=None): sgmllib.SGMLParser._ _init_ _(self) self.tags = [] if file: self.load(file) def load(self, file): while 1: s = file.read(8192) if not s: break self.feed(s) self.close() def close(self): sgmllib.SGMLParser.close(self) if self.tags: raise SyntaxError, "start tag %s not closed" % self.tags[-1] def unknown_starttag(self, start, attrs): self.tags.append(start) def unknown_endtag(self, end): start = self.tags.pop() if end != start: raise SyntaxError, "end tag %s does't match start tag %s" %/ (end, start) try: c = WellFormednessChecker() c.load(open("samples/sample.htm")) except SyntaxError: raise # report error else: print "document is well-formed" Traceback (innermost last): ... SyntaxError: end tag head does't match start tag meta

最後, Example 5-8 中的類可以用來過濾 HTML 和 SGML 文件. 繼承這個類, 然後實現 start 和 end 方法即可.

5.4.0.4. Example 5-8. 使用 sgmllib 模組過濾 SGML 文件

File: sgmllib-example-4.py import sgmllib import cgi, string, sys class SGMLFilter(sgmllib.SGMLParser): # sgml filter. override start/end to manipulate # document elements def _ _init_ _(self, outfile=None, infile=None): sgmllib.SGMLParser._ _init_ _(self) if not outfile: outfile = sys.stdout self.write = outfile.write if infile: self.load(infile) def load(self, file): while 1: s = file.read(8192) if not s: break self.feed(s) self.close() def handle_entityref(self, name): self.write("&%s;" % name) def handle_data(self, data): self.write(cgi.escape(data)) def unknown_starttag(self, tag, attrs): tag, attrs = self.start(tag, attrs) if tag: if not attrs: self.write("<%s>" % tag) else: self.write("<%s" % tag) for k, v in attrs: self.write(" %s=%s" % (k, repr(v))) self.write(">") def unknown_endtag(self, tag): tag = self.end(tag) if tag: self.write("</%s>" % tag) def start(self, tag, attrs): return tag, attrs # override def end(self, tag): return tag # override class Filter(SGMLFilter): def fixtag(self, tag): if tag == "em": tag = "i" if tag == "string": tag = "b" return string.upper(tag) def start(self, tag, attrs): return self.fixtag(tag), attrs def end(self, tag): return self.fixtag(tag) c = Filter() c.load(open("samples/sample.htm"))

5.5. htmllib 模組

htmlib 模組包含了一個標籤驅動的( tag-driven ) HTML 語法分析器, 它會將資料傳送至一個格式化物件. 如 Example 5-9 所示. 更多關於如何解析 HTML 的例子請參閱 formatter 模組.

5.5.0.1. Example 5-9. 使用 htmllib 模組

File: htmllib-example-1.py import htmllib import formatter import string class Parser(htmllib.HTMLParser): # return a dictionary mapping anchor texts to lists # of associated hyperlinks def _ _init_ _(self, verbose=0): self.anchors = {} f = formatter.NullFormatter() htmllib.HTMLParser._ _init_ _(self, f, verbose) def anchor_bgn(self, href, name, type): self.save_bgn() self.anchor = href def anchor_end(self): text = string.strip(self.save_end()) if self.anchor and text: self.anchors[text] = self.anchors.get(text, []) + [self.anchor] file = open("samples/sample.htm") html = file.read() file.close() p = Parser() p.feed(html) p.close() for k, v in p.anchors.items(): print k, "=>", v print link => ['http://www.python.org']

如果你只是想解析一個 HTML 檔案, 而不是將它交給輸出裝置, 那麼 sgmllib 模組會是更好的選擇.


5.6. htmlentitydefs 模組

htmlentitydefs 模組包含一個由 HTML 中 ISO Latin-1 字元實體構成的字典. 如 Example 5-10 所示.

5.6.0.1. Example 5-10. 使用 htmlentitydefs 模組

File: htmlentitydefs-example-1.py import htmlentitydefs entities = htmlentitydefs.entitydefs for entity in "amp", "quot", "copy", "yen": print entity, "=", entities[entity] amp = & quot = " copy = /302/251 yen = /302/245

Example 5-11 展示瞭如何將正規表示式與這個字典結合起來翻譯字串中的實體 ( cgi.escape 的逆向操作).

5.6.0.2. Example 5-11. 使用 htmlentitydefs 模組翻譯實體

File: htmlentitydefs-example-2.py import htmlentitydefs import re import cgi pattern = re.compile("&(/w+?);") def descape_entity(m, defs=htmlentitydefs.entitydefs): # callback: translate one entity to its ISO Latin value try: return defs[m.group(1)] except KeyError: return m.group(0) # use as is def descape(string): return pattern.sub(descape_entity, string) print descape("&lt;spam&amp;eggs&gt;") print descape(cgi.escape("<spam&eggs>")) <spam&eggs> <spam&eggs>

最後, Example 5-12 展示瞭如何將 XML 保留字元和 ISO Latin-1 字元轉換為 XML 字串. 與 cgi.escape 相似, 但它會替換非 ASCII 字元.

5.6.0.3. Example 5-12. 轉義 ISO Latin-1 實體

File: htmlentitydefs-example-3.py import htmlentitydefs import re, string # this pattern matches substrings of reserved and non-ASCII characters pattern = re.compile(r"[&<>/"/x80-/xff]+") # create character map entity_map = {} for i in range(256): entity_map[chr(i)] = "&%d;" % i for entity, char in htmlentitydefs.entitydefs.items(): if entity_map.has_key(char): entity_map[char] = "&%s;" % entity def escape_entity(m, get=entity_map.get): return string.join(map(get, m.group()), "") def escape(string): return pattern.sub(escape_entity, string) print escape("<spam&eggs>") print escape("/303/245 i /303/245a /303/244 e /303/266") &lt;spam&amp;eggs&gt; &aring; i &aring;a &auml; e &ouml;

5.7. formatter 模組

formatter 模組提供了一些可用於 htmllib 的格式類( formatter classes ).

這些類有兩種, formatter 和 writer . formatter 將 HTML 解析器的標籤和資料流轉換為適合輸出裝置的事件流( event stream ), 而 writer 將事件流輸出到裝置上. 如 Example 5-13 所示.

大多情況下, 你可以使用 AbstractFormatter 類進行格式化. 它會根據不同的格式化事件呼叫 writer 物件的方法. AbstractWriter 類在每次方法呼叫時列印一條資訊.

5.7.0.1. Example 5-13. 使用 formatter 模組將 HTML 轉換為事件流

File: formatter-example-1.py import formatter import htmllib w = formatter.AbstractWriter() f = formatter.AbstractFormatter(w) file = open("samples/sample.htm") p = htmllib.HTMLParser(f) p.feed(file.read()) p.close() file.close() send_paragraph(1) new_font(('h1', 0, 1, 0)) send_flowing_data('A Chapter.') send_line_break() send_paragraph(1) new_font(None) send_flowing_data('Some text. Some more text. Some') send_flowing_data(' ') new_font((None, 1, None, None)) send_flowing_data('emphasized') new_font(None) send_flowing_data(' text. A') send_flowing_data(' link') send_flowing_data('[1]') send_flowing_data('.')

formatter 模組還提供了 NullWriter 類, 它會將任何傳遞給它的事件忽略; 以及 DumbWriter 類, 它會將事件流轉換為純文字文件. 如 Example 5-14 所示.

5.7.0.2. Example 5-14. 使用 formatter 模組將 HTML 轉換為純文字

File: formatter-example-2.py import formatter import htmllib w = formatter.DumbWriter() # plain text f = formatter.AbstractFormatter(w) file = open("samples/sample.htm") # print html body as plain text p = htmllib.HTMLParser(f) p.feed(file.read()) p.close() file.close() # print links print print i = 1 for link in p.anchorlist: print i, "=>", link i = i + 1 A Chapter. Some text. Some more text. Some emphasized text. A link[1]. 1 => http://www.python.org

Example 5-15 提供了一個自定義的 Writer , 它繼承自 DumbWriter 類, 會記錄當前字型樣式並根據字型美化輸出格式.

5.7.0.3. Example 5-15. 使用 formatter 模組自定義 Writer

File: formatter-example-3.py import formatter import htmllib, string class Writer(formatter.DumbWriter): def _ _init_ _(self): formatter.DumbWriter._ _init_ _(self) self.tag = "" self.bold = self.italic = 0 self.fonts = [] def new_font(self, font): if font is None: font = self.fonts.pop() self.tag, self.bold, self.italic = font else: self.fonts.append((self.tag, self.bold, self.italic)) tag, bold, italic, typewriter = font if tag is not None: self.tag = tag if bold is not None: self.bold = bold if italic is not None: self.italic = italic def send_flowing_data(self, data): if not data: return atbreak = self.atbreak or data[0] in string.whitespace for word in string.split(data): if atbreak: self.file.write(" ") if self.tag in ("h1", "h2", "h3"): word = string.upper(word) if self.bold: word = "*" + word + "*" if self.italic: word = "_" + word + "_" self.file.write(word) atbreak = 1 self.atbreak = data[-1] in string.whitespace w = Writer() f = formatter.AbstractFormatter(w) file = open("samples/sample.htm") # print html body as plain text p = htmllib.HTMLParser(f) p.feed(file.read()) p.close() _A_ _CHAPTER._ Some text. Some more text. Some *emphasized* text. A link[1].

5.8. ConfigParser 模組

ConfigParser 模組用於讀取配置檔案.

配置檔案的格式與 Windows INI 檔案類似, 可以包含一個或多個區域( section ), 每個區域可以有多個配置條目.

這裡有個樣例配置檔案, 在 Example 5-16 用到了這個檔案:

[book] title: The Python Standard Library author: Fredrik Lundh email: fredrik@pythonware.com version: 2.0-001115 [ematter] pages: 250 [hardcopy] pages: 350

Example 5-16 使用 ConfigParser 模組讀取這個配製檔案.

5.8.0.1. Example 5-16. 使用 ConfigParser 模組

File: configparser-example-1.py import ConfigParser import string config = ConfigParser.ConfigParser() config.read("samples/sample.ini") # print summary print print string.upper(config.get("book", "title")) print "by", config.get("book", "author"), print "(" + config.get("book", "email") + ")" print print config.get("ematter", "pages"), "pages" print # dump entire config file for section in config.sections(): print section for option in config.options(section): print " ", option, "=", config.get(section, option) THE PYTHON STANDARD LIBRARY by Fredrik Lundh (fredrik@pythonware.com) 250 pages book title = The Python Standard Library email = fredrik@pythonware.com author = Fredrik Lundh version = 2.0-001115 _ _name_ _ = book ematter _ _name_ _ = ematter pages = 250 hardcopy _ _name_ _ = hardcopy pages = 350

Python 2.0 以後, ConfigParser 模組也可以將配置資料寫入檔案, 如 Example 5-17 所示.

5.8.0.2. Example 5-17. 使用 ConfigParser 模組寫入配置資料

File: configparser-example-2.py import ConfigParser import sys config = ConfigParser.ConfigParser() # set a number of parameters config.add_section("book") config.set("book", "title", "the python standard library") config.set("book", "author", "fredrik lundh") config.add_section("ematter") config.set("ematter", "pages", 250) # write to screen config.write(sys.stdout) [book] title = the python standard library author = fredrik lundh [ematter] pages = 250

5.9. netrc 模組

netrc 模組可以用來解析 .netrc 配置檔案, 如 Example 5-18 所示. 該檔案用於在使用者的 home 目錄儲存 FTP 使用者名稱和密碼. (別忘記設定這個檔案的屬性為: "chmod 0600 ~/.netrc," 這樣只有當前使用者能訪問).

5.9.0.1. Example 5-18. 使用 netrc 模組

File: netrc-example-1.py import netrc # default is $HOME/.netrc info = netrc.netrc("samples/sample.netrc") login, account, password = info.authenticators("secret.fbi") print "login", "=>", repr(login) print "account", "=>", repr(account) print "password", "=>", repr(password) login => 'mulder' account => None password => 'trustno1'

5.10. shlex 模組

shlex 模組為基於 Unix shell 語法的語言提供了一個簡單的 lexer (也就是 tokenizer). 如 Example 5-19 所示.

5.10.0.1. Example 5-19. 使用 shlex 模組

File: shlex-example-1.py import shlex lexer = shlex.shlex(open("samples/sample.netrc", "r")) lexer.wordchars = lexer.wordchars + "._" while 1: token = lexer.get_token() if not token: break print repr(token) 'machine' 'secret.fbi' 'login' 'mulder' 'password' 'trustno1' 'machine' 'non.secret.fbi' 'login' 'scully' 'password' 'noway'

5.11. zipfile 模組

( 2.0 新增) zipfile 模組可以用來讀寫 ZIP 格式.

5.11.1. 列出內容

使用 namelist 和 infolist 方法可以列出壓縮檔的內容, 前者返回由檔名組成的列表, 後者返回由 ZipInfo 例項組成的列表. 如 Example 5-20 所示.

5.11.1.1. Example 5-20. 使用 zipfile 模組列出 ZIP 文件中的檔案

File: zipfile-example-1.py import zipfile file = zipfile.ZipFile("samples/sample.zip", "r") # list filenames for name in file.namelist(): print name, print # list file information for info in file.infolist(): print info.filename, info.date_time, info.file_size sample.txt sample.jpg sample.txt (1999, 9, 11, 20, 11, 8) 302 sample.jpg (1999, 9, 18, 16, 9, 44) 4762

5.11.2. 從 ZIP 檔案中讀取資料

呼叫 read 方法就可以從 ZIP 文件中讀取資料. 它接受一個檔名作為引數, 返回字串. 如 Example 5-21 所示.

5.11.2.1. Example 5-21. 使用 zipfile 模組從 ZIP 檔案中讀取資料

File: zipfile-example-2.py import zipfile file = zipfile.ZipFile("samples/sample.zip", "r") for name in file.namelist(): data = file.read(name) print name, len(data), repr(data[:10]) sample.txt 302 'We will pe' sample.jpg 4762 '/377/330/377/340/000/020JFIF'

5.11.3. 向 ZIP 檔案寫入資料

向壓縮檔加入檔案很簡單, 將檔名, 檔案在 ZIP 檔中的名稱傳遞給 write 方法即可.

Example 5-22 將 samples 目錄中的所有檔案打包為一個 ZIP 檔案.

5.11.3.1. Example 5-22. 使用 zipfile 模組將檔案儲存在 ZIP 檔案裡

File: zipfile-example-3.py import zipfile import glob, os # open the zip file for writing, and write stuff to it file = zipfile.ZipFile("test.zip", "w") for name in glob.glob("samples/*"): file.write(name, os.path.basename(name), zipfile.ZIP_DEFLATED) file.close() # open the file again, to see what's in it file = zipfile.ZipFile("test.zip", "r") for info in file.infolist(): print info.filename, info.date_time, info.file_size, info.compress_size sample.wav (1999, 8, 15, 21, 26, 46) 13260 10985 sample.jpg (1999, 9, 18, 16, 9, 44) 4762 4626 sample.au (1999, 7, 18, 20, 57, 34) 1676 1103 ...

write 方法的第三個可選引數用於控制是否使用壓縮. 預設為 zipfile.ZIP_STORED , 意味著只是將資料儲存在檔案裡而不進行任何壓縮. 如果安裝了zlib 模組, 那麼就可以使用 zipfile.ZIP_DEFLATED 進行壓縮.

zipfile 模組也可以向檔案中新增字串. 不過, 這需要一點技巧, 你需要建立一個 ZipInfo 例項, 並正確配置它. Example 5-23 提供了一種簡單的解決辦法.

5.11.3.2. Example 5-23. 使用 zipfile 模組在 ZIP 檔案中儲存字串

File: zipfile-example-4.py import zipfile import glob, os, time file = zipfile.ZipFile("test.zip", "w") now = time.localtime(time.time())[:6] for name in ("life", "of", "brian"): info = zipfile.ZipInfo(name) info.date_time = now info.compress_type = zipfile.ZIP_DEFLATED file.writestr(info, name*1000) file.close() # open the file again, to see what's in it file = zipfile.ZipFile("test.zip", "r") for info in file.infolist(): print info.filename, info.date_time, info.file_size, info.compress_size life (2000, 12, 1, 0, 12, 1) 4000 26 of (2000, 12, 1, 0, 12, 1) 2000 18 brian (2000, 12, 1, 0, 12, 1) 5000 31

5.12. gzip 模組

gzip 模組用來讀寫 gzip 格式的壓縮檔案, 如 Example 5-24 所示.

5.12.0.1. Example 5-24. 使用 gzip 模組讀取壓縮檔案

File: gzip-example-1.py import gzip file = gzip.GzipFile("samples/sample.gz") print file.read() Well it certainly looks as though we're in for a splendid afternoon's sport in this the 127th Upperclass Twit of the Year Show.

標準的實現並不支援 seek 和 tell 方法. 不過 Example 5-25 可以解決這個問題.

5.12.0.2. Example 5-25. 給 gzip 模組新增 seek/tell 支援

File: gzip-example-2.py import gzip class gzipFile(gzip.GzipFile): # adds seek/tell support to GzipFile offset = 0 def read(self, size=None): data = gzip.GzipFile.read(self, size) self.offset = self.offset + len(data) return data def seek(self, offset, whence=0): # figure out new position (we can only seek forwards) if whence == 0: position = offset elif whence == 1: position = self.offset + offset else: raise IOError, "Illegal argument" if position < self.offset: raise IOError, "Cannot seek backwards" # skip forward, in 16k blocks while position > self.offset: if not self.read(min(position - self.offset, 16384)): break def tell(self): return self.offset # # try it file = gzipFile("samples/sample.gz") file.seek(80) print file.read() this the 127th Upperclass Twit of the Year Show.

6. 郵件和新聞訊息處理

"To be removed from our list of future commercial postings by [SOME] PUBLISHING COMPANY an Annual Charge of Ninety Five dollars is required. Just send $95.00 with your Name, Address and Name of the Newsgroup to be removed from our list."
- Newsgroup spammer, July 1996
"想要退出 '某' 宣傳公司的未來商業廣告列表嗎, 您需要付 95 美元. 只要您支付95美元, 並且告訴我們您的姓名, 地址, 和需要退出的新聞組, 我們就會把您從列表中移除."
- 新聞組垃圾傳送者, 1996 年 7 月

6.1. 概覽

Python 有大量用於處理郵件和新聞組的模組, 其中包括了許多常見的郵件格式.


6.2. rfc822 模組

rfc822 模組包括了一個郵件和新聞組的解析器 (也可用於其它符合 RFC 822 標準的訊息, 比如 HTTP 頭).

通常, RFC 822 格式的訊息包含一些標頭欄位, 後面至少有一個空行, 然後是資訊主體.

For example, here's a short mail message. The first five lines make up the message header, and the actual message (a single line, in this case) follows after an empty line:

例如這裡的郵件資訊. 前五行組成了訊息標頭, 隔一個空行後是訊息主體.

Message-Id: <20001114144603.00abb310@oreilly.com> Date: Tue, 14 Nov 2000 14:55:07 -0500 To: "Fredrik Lundh" <fredrik@effbot.org> From: Frank Subject: Re: python library book! Where is it?

訊息解析器讀取標頭欄位後會返回一個以訊息標頭為鍵的類字典物件, 如 Example 6-1 所示.

6.2.0.1. Example 6-1. 使用 rfc822 模組

File: rfc822-example-1.py import rfc822 file = open("samples/sample.eml") message = rfc822.Message(file) for k, v in message.items(): print k, "=", v print len(file.read()), "bytes in body" subject = Re: python library book! from = "Frank" <your@editor> message-id = <20001114144603.00abb310@oreilly.com> to = "Fredrik Lundh" <fredrik@effbot.org> date = Tue, 14 Nov 2000 14:55:07 -0500 25 bytes in body

訊息物件( message object )還提供了一些用於解析地址欄位和資料的, 如 Example 6-2 所示.

6.2.0.2. Example 6-2. 使用 rfc822 模組解析標頭欄位

File: rfc822-example-2.py import rfc822 file = open("samples/sample.eml") message = rfc822.Message(file) print message.getdate("date") print message.getaddr("from") print message.getaddrlist("to") (2000, 11, 14, 14, 55, 7, 0, 0, 0) ('Frank', 'your@editor') [('Fredrik Lundh', 'fredrik@effbot.org')]

地址欄位被解析為 (實際名稱, 郵件地址) 這樣的元組. 資料欄位被解析為 9 元時間元組, 可以使用 time 模組處理.


6.3. mimetools 模組

多用途因特網郵件擴充套件 ( Multipurpose Internet Mail Extensions, MIME ) 標準定義瞭如何在 RFC 822 格式的訊息中儲存非 ASCII 文字, 影象以及其它資料.

mimetools 模組包含一些讀寫 MIME 資訊的工具. 它還提供了一個類似 rfc822 模組中 Message 的類, 用於處理 MIME 編碼的資訊. 如 Example 6-3 所示.

6.3.0.1. Example 6-3. 使用 mimetools 模組

File: mimetools-example-1.py import mimetools file = open("samples/sample.msg") msg = mimetools.Message(file) print "type", "=>", msg.gettype() print "encoding", "=>", msg.getencoding() print "plist", "=>", msg.getplist() print "header", "=>" for k, v in msg.items(): print " ", k, "=", v type => text/plain encoding => 7bit plist => ['charset="iso-8859-1"'] header => mime-version = 1.0 content-type = text/plain; charset="iso-8859-1" to = effbot@spam.egg date = Fri, 15 Oct 1999 03:21:15 -0400 content-transfer-encoding = 7bit from = "Fredrik Lundh" <fredrik@pythonware.com> subject = By the way... ...

6.4. MimeWriter 模組

MimeWriter 模組用於生成符合 MIME 郵件標準的 "多部分" 的資訊, 如 Example 6-4 所示.

6.4.0.1. Example 6-4. 使用 MimeWriter 模組

File: mimewriter-example-1.py import MimeWriter # data encoders # 資料編碼 import quopri import base64 import StringIO import sys TEXT = """ here comes the image you asked for. hope it's what you expected. </F>""" FILE = "samples/sample.jpg" file = sys.stdout # # create a mime multipart writer instance mime = MimeWriter.MimeWriter(file) mime.addheader("Mime-Version", "1.0") mime.startmultipartbody("mixed") # add a text message # 加入文字資訊 part = mime.nextpart() part.addheader("Content-Transfer-Encoding", "quoted-printable") part.startbody("text/plain") quopri.encode(StringIO.StringIO(TEXT), file, 0) # add an image # 加入圖片 part = mime.nextpart() part.addheader("Content-Transfer-Encoding", "base64") part.startbody("image/jpeg") base64.encode(open(FILE, "rb"), file) mime.lastpart()

輸出結果如下:

Content-Type: multipart/mixed; boundary='host.1.-852461.936831373.130.24813' --host.1.-852461.936831373.130.24813 Content-Type: text/plain Context-Transfer-Encoding: quoted-printable here comes the image you asked for. hope it's what you expected. </F> --host.1.-852461.936831373.130.24813 Content-Type: image/jpeg Context-Transfer-Encoding: base64 /9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRof HBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIy ... 1e5vLrSYbJnEVpEgjCLx5mPU0qsVK0UaxjdNlS+1U6pfzTR8IzEhj2HrVG6m8m18xc8cIKSC tCuFyC746j/Cq2pTia4WztfmKjGBXTCmo6IUpt== --host.1.-852461.936831373.130.24813--

[Example 6-5 #eg-6-5 ] 使用輔助類儲存每個子部分.

6.4.0.2. Example 6-5. MimeWriter 模組的輔助類

File: mimewriter-example-2.py import MimeWriter import string, StringIO, sys import re, quopri, base64 # check if string contains non-ascii characters must_quote = re.compile("[/177-/377]").search # # encoders def encode_quoted_printable(infile, outfile): quopri.encode(infile, outfile, 0) class Writer: def _ _init_ _(self, file=None, blurb=None): if file is None: file = sys.stdout self.file = file self.mime = MimeWriter.MimeWriter(file) self.mime.addheader("Mime-Version", "1.0") file = self.mime.startmultipartbody("mixed") if blurb: file.write(blurb) def close(self): "End of message" self.mime.lastpart() self.mime = self.file = None def write(self, data, mimetype="text/plain"): "Write data from string or file to message" # data is either an opened file or a string if type(data) is type(""): file = StringIO.StringIO(data) else: file = data data = None part = self.mime.nextpart() typ, subtyp = string.split(mimetype, "/", 1) if typ == "text": # text data encoding = "quoted-printable" encoder = lambda i, o: quopri.encode(i, o, 0) if data and not must_quote(data): # copy, don't encode encoding = "7bit" encoder = None else: # binary data (image, audio, application, ...) encoding = "base64" encoder = base64.encode # # write part headers if encoding: part.addheader("Content-Transfer-Encoding", encoding) part.startbody(mimetype) # # write part body if encoder: encoder(file, self.file) elif data: self.file.write(data) else: while 1: data = infile.read(16384) if not data: break outfile.write(data) # # try it out BLURB = "if you can read this, your mailer is not MIME-aware/n" mime = Writer(sys.stdout, BLURB) # add a text message mime.write("""/ here comes the image you asked for. hope it's what you expected. """, "text/plain") # add an image mime.write(open("samples/sample.jpg", "rb"), "image/jpeg") mime.close()

6.5. mailbox 模組

mailbox 模組用來處理各種不同型別的郵箱格式, 如 Example 6-6 所示. 大部分郵箱格式使用文字檔案儲存純 RFC 822 資訊, 用分割行區別不同的資訊.

6.5.0.1. Example 6-6. 使用 mailbox 模組

File: mailbox-example-1.py import mailbox mb = mailbox.UnixMailbox(open("/var/spool/mail/effbot")) while 1: msg = mb.next() if not msg: break for k, v in msg.items(): print k, "=", v body = msg.fp.read() print len(body), "bytes in body" subject = for he's a ... message-id = <199910150027.CAA03202@spam.egg> received = (from fredrik@pythonware.com) by spam.egg (8.8.7/8.8.5) id CAA03202 for effbot; Fri, 15 Oct 1999 02:27:36 +0200 from = Fredrik Lundh <fredrik@pythonware.com> date = Fri, 15 Oct 1999 12:35:36 +0200 to = effbot@spam.egg 1295 bytes in body

6.6. mailcap 模組

mailcap 模組用於處理 mailcap 檔案, 該檔案指定了不同的文件格式的處理方法( Unix 系統下). 如 Example 6-7 所示.

6.6.0.1. Example 6-7. 使用 mailcap 模組獲得 Capability 字典

File: mailcap-example-1.py import mailcap caps = mailcap.getcaps() for k, v in caps.items(): print k, "=", v image/* = [{'view': 'pilview'}] application/postscript = [{'view': 'ghostview'}]

Example 6-7 中, 系統使用 pilview 來預覽( view )所有型別的圖片, 使用 ghostscript viewer 預覽 PostScript 文件. Example 6-8 展示瞭如何使用 mailcap獲得特定操作的命令.

6.6.0.2. Example 6-8. 使用 mailcap 模組獲得開啟

File: mailcap-example-2.py import mailcap caps = mailcap.getcaps() command, info = mailcap.findmatch( caps, "image/jpeg", "view", "samples/sample.jpg" ) print command pilview samples/sample.jpg

6.7. mimetypes 模組

mimetypes 模組可以判斷給定 url ( uniform resource locator , 統一資源定位符) 的 MIME 型別. 它基於一個內建的表, 還可能搜尋 Apache 和 Netscape 的配置檔案. 如 Example 6-9 所示.

6.7.0.1. Example 6-9. 使用 mimetypes 模組

File: mimetypes-example-1.py import mimetypes import glob, urllib for file in glob.glob("samples/*"): url = urllib.pathname2url(file) print file, mimetypes.guess_type(url) samples/sample.au ('audio/basic', None) samples/sample.ini (None, None) samples/sample.jpg ('image/jpeg', None) samples/sample.msg (None, None) samples/sample.tar ('application/x-tar', None) samples/sample.tgz ('application/x-tar', 'gzip') samples/sample.txt ('text/plain', None) samples/sample.wav ('audio/x-wav', None) samples/sample.zip ('application/zip', None)

6.8. packmail 模組

(已廢棄) packmail 模組可以用來建立 Unix shell 檔案. 如果安裝了合適的工具, 那麼你就可以直接通過執行來解開這樣的檔案. Example 6-10 展示瞭如何打包單個檔案, Example 6-11 則打包了整個目錄樹.

6.8.0.1. Example 6-10. 使用 packmail 打包單個檔案

File: packmail-example-1.py import packmail import sys packmail.pack(sys.stdout, "samples/sample.txt", "sample.txt") echo sample.txt sed "s/^X//" >sample.txt <<"!" XWe will perhaps eventually be writing only small Xmodules, which are identified by name as they are Xused to build larger ones, so that devices like Xindentation, rather than delimiters, might become Xfeasible for expressing local structure in the Xsource language. X -- Donald E. Knuth, December 1974 !

====Example 6-11. 使用 packmail 打包整個目錄樹===[eg-6-11]

File: packmail-example-2.py import packmail import sys packmail.packtree(sys.stdout, "samples")

注意, 這個模組不能處理二進位制檔案, 例如聲音或者影象檔案.


6.9. mimify 模組

mimify 模組用於在 MIME 編碼的文字資訊和普通文字資訊(例如 ISO Latin 1 文字)間相互轉換. 它可以用作命令列工具, 或是特定郵件代理的轉換過濾器:

$ mimify.py -e raw-message mime-message $ mimify.py -d mime-message raw-message

作為模組使用, 如 Example 6-12 所示.

6.9.0.1. Example 6-12. 使用 mimify 模組解碼資訊

File: mimify-example-1.py import mimify import sys mimify.unmimify("samples/sample.msg", sys.stdout, 1)

這裡是一個包含兩部分的 MIME 資訊, 一個是引用的可列印資訊, 另個是 base64 編碼資訊. unmimify 的第三個引數決定是否自動解碼 base64 編碼的部分:

MIME-Version: 1.0 Content-Type: multipart/mixed; boundary='boundary' this is a multipart sample file. the two parts both contain ISO Latin 1 text, with different encoding techniques. --boundary Content-Type: text/plain Content-Transfer-Encoding: quoted-printable sillmj=F6lke! blindstyre! medisterkorv! --boundary Content-Type: text/plain Content-Transfer-Encoding: base64 a29tIG5lciBiYXJhLCBvbSBkdSB09nJzIQ== --boundary--

解碼結果如下 (可讀性相對來說更好些):

MIME-Version: 1.0 Content-Type: multipart/mixed; boundary= 'boundary' this is a multipart sample file. the two parts both contain ISO Latin 1 text, with different encoding techniques. --boundary Content-Type: text/plain sillmjölke! blindstyre! medisterkorv! --boundary Content-Type: text/plain kom ner bara, om du törs!

Example 6-13 展示瞭如何編碼資訊.

6.9.0.2. Example 6-13. 使用 mimify 模組編碼資訊

File: mimify-example-2.py import mimify import StringIO, sys # # decode message into a string buffer file = StringIO.StringIO() mimify.unmimify("samples/sample.msg", file, 1) # # encode message from string buffer file.seek(0) # rewind mimify.mimify(file, sys.stdout)

6.10. multifile 模組

multifile 模組允許你將一個多部分的 MIME 資訊的每部分作為單獨的檔案處理. 如 Example 6-14 所示.

6.10.0.1. Example 6-14. 使用 multifile 模組

File: multifile-example-1.py import multifile import cgi, rfc822 infile = open("samples/sample.msg") message = rfc822.Message(infile) # print parsed header for k, v in message.items(): print k, "=", v # use cgi support function to parse content-type header type, params = cgi.parse_header(message["content-type"]) if type[:10] == "multipart/": # multipart message boundary = params["boundary"] file = multifile.MultiFile(infile) file.push(boundary) while file.next(): submessage = rfc822.Message(file) # print submessage print "-" * 68 for k, v in submessage.items(): print k, "=", v print print file.read() file.pop() else: # plain message print infile.read()

7. 網路協議

"Increasingly, people seem to misinterpret complexity as sophistication, which is baffling - the incomprehensible should cause suspicion rather than admiration. Possibly this trend results from a mistaken belief that using a somewhat mysterious device confers an aura of power on the user."
- Niklaus Wirth

7.1. 概覽

本章描述了 Python 的 socket 協議支援以及其他建立在 socket 模組上的網路 模組. 這些包含了對大多流行 Internet 協議客戶端的支援, 以及一些可用來 實現 Internet 伺服器的框架.

對於那些本章中的底層的例子, 我將使用兩個協議作為樣例: Internet Time Protocol ( Internet 時間協議 ) 以及 Hypertext Transfer Protocol (超文字傳輸協議, HTTP 協議).

7.1.1. Internet 時間協議

Internet 時間協議 ( RFC 868, Postel 和 Harrenstien, 1983) 可以讓 一個網路客戶端獲得一個伺服器的當前時間.

因為這個協議是輕量級的, 許多 Unix 系統(但不是所有)都提供了這個服務. 它可能是最簡單的網路協議了. 伺服器等待連線請求並在連線後返回當前時間 ( 4 位元組整數, 自從 1900 年 1 月 1 日到當前的秒數).

協議很簡單, 這裡我們提供規格書給大家:

File: rfc868.txt Network Working Group J. Postel - ISI Request for Comments: 868 K. Harrenstien - SRI May 1983 Time Protocol This RFC specifies a standard for the ARPA Internet community. Hosts on the ARPA Internet that choose to implement a Time Protocol are expected to adopt and implement this standard. 本 RFC 規範提供了一個 ARPA Internet community 上的標準.  在 ARPA Internet 上的所有主機應當採用並實現這個標準. This protocol provides a site-independent, machine readable date and time. The Time service sends back to the originating source the time in seconds since midnight on January first 1900. 此協議提供了一個獨立於站點的, 機器可讀的日期和時間資訊.  時間服務返回的是從 1900 年 1 月 1 日午夜到現在的秒數. One motivation arises from the fact that not all systems have a date/time clock, and all are subject to occasional human or machine error. The use of time-servers makes it possible to quickly confirm or correct a system's idea of the time, by making a brief poll of several independent sites on the network. 設計這個協議的一個重要目的在於, 網路上的一些主機並沒有時鐘,  這有可能導致人工或者機器錯誤. 我們可以依靠時間伺服器快速確認或者修改 一個系統的時間. This protocol may be used either above the Transmission Control Protocol (TCP) or above the User Datagram Protocol (UDP). 該協議可以用在 TCP 協議或是 UDP 協議上. When used via TCP the time service works as follows: 通過 TCP 訪問時間伺服器的步驟: * S: Listen on port 37 (45 octal). * U: Connect to port 37. * S: Send the time as a 32 bit binary number. * U: Receive the time. * U: Close the connection. * S: Close the connection. * S: 監聽 37 ( 45 的八進位制) 埠. * U: 連線 37 埠. * S: 將時間作為 32 位二進位制數字傳送. * U: 接收時間. * U: 關閉連線. * S: 關閉連線.  The server listens for a connection on port 37. When the connection is established, the server returns a 32-bit time value and closes the connection. If the server is unable to determine the time at its site, it should either refuse the connection or close it without sending anything. 伺服器在 37 埠監聽. 當連線建立的時候, 伺服器返回一個 32 位的數字值 並關閉連線. 如果伺服器自己無法決定當前時間, 那麼它應該拒絕這個連線或者 不傳送任何資料立即關閉連線. When used via UDP the time service works as follows: 通過 TCP 訪問時間伺服器的步驟: S: Listen on port 37 (45 octal). U: Send an empty datagram to port 37. S: Receive the empty datagram. S: Send a datagram containing the time as a 32 bit binary number. U: Receive the time datagram. S: 監聽 37 ( 45 的八進位制) 埠. U: 傳送空資料包文到 37 埠. S: 接受空報文. S: 傳送包含時間( 32 位二進位制數字 )的報文. U: 接受時間報文. The server listens for a datagram on port 37. When a datagram arrives, the server returns a datagram containing the 32-bit time value. If the server is unable to determine the time at its site, it should discard the arriving datagram and make no reply. 伺服器在 37 埠監聽報文. 當報文到達時, 伺服器返回包含 32 位時間值 的報文. 如果伺服器無法決定當前時間, 那麼它應該丟棄到達的報文,  不做任何回覆. The Time 時間 The time is the number of seconds since 00:00 (midnight) 1 January 1900 GMT, such that the time 1 is 12:00:01 am on 1 January 1900 GMT; this base will serve until the year 2036. 時間是自 1900 年 1 月 1 日 0 時到當前的秒數,  這個協議標準會一直服務到2036年. 到時候數字不夠用再說. For example: the time 2,208,988,800 corresponds to 00:00 1 Jan 1970 GMT, 2,398,291,200 corresponds to 00:00 1 Jan 1976 GMT, 2,524,521,600 corresponds to 00:00 1 Jan 1980 GMT, 2,629,584,000 corresponds to 00:00 1 May 1983 GMT, and -1,297,728,000 corresponds to 00:00 17 Nov 1858 GMT. 例如: 時間值 2,208,988,800 對應 to 00:00 1 Jan 1970 GMT, 2,398,291,200 對應 to 00:00 1 Jan 1976 GMT, 2,524,521,600 對應 to 00:00 1 Jan 1980 GMT, 2,629,584,000 對應 to 00:00 1 May 1983 GMT, 最後 -1,297,728,000 對應 to 00:00 17 Nov 1858 GMT. RFC868.txt Translated By Andelf(gt: andelf@gmail.com ) 非商業用途, 轉載請保留作者資訊. Thx.

7.1.2. HTTP 協議

超文字傳輸協議 ( HTTP, RFC 2616 ) 是另個完全不同的東西. 最近的格式說明書( Version 1.1 )超過了 100 頁.

從它最簡單的格式來看, 這個協議是很簡單的. 客戶端傳送如下的請求到伺服器, 請求一個檔案:

GET /hello.txt HTTP/1.0 Host: hostname User-Agent: name [optional request body , 可選的請求正文]

伺服器返回對應的響應:

HTTP/1.0 200 OK Content-Type: text/plain Content-Length: 7 Hello

請求和響應的 headers (報頭)一般會包含更多的域, 但是請求 header 中的 Host 域/欄位是必須提供的.

header 行使用 "/r/n" 分割, 而且 header 後必須有一個空行, 即使沒有正文 (請求和響應都必須符合這條規則).

剩下的 HTTP 協議格式說明書細節, 例如內容協商, 快取機制, 保持連線, 等等, 請參閱 Hypertext TransferProtocol - HTTP/1.1 ( http://www.w3.org/Protocols).


7.2. socket 模組

socket 模組實現了到 socket 通訊層的介面. 你可以使用該模組建立 客戶端或是伺服器的 socket .

我們首先以一個客戶端為例, Example 7-1 中的客戶端連線到一個時間協議伺服器, 讀取 4 位元組的返回資料, 並把它轉換為一個時間值.

7.2.0.1. Example 7-1. 使用 socket 模組實現一個時間客戶端

File: socket-example-1.py import socket import struct, time # server HOST = "www.python.org" PORT = 37 # reference time (in seconds since 1900-01-01 00:00:00) TIME1970 = 2208988800L # 1970-01-01 00:00:00 # connect to server s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) # read 4 bytes, and convert to time value t = s.recv(4) t = struct.unpack("!I", t)[0] t = int(t - TIME1970) s.close() # print results print "server time is", time.ctime(t) print "local clock is", int(time.time()) - t, "seconds off" server time is Sat Oct 09 16:42:36 1999 local clock is 8 seconds off

socket 工廠函式( factory function )根據給定型別(該例子中為 Internet stream socket , 即就是 TCP socket )建立一個新的 socket . connect 方法嘗試將這個 socket 連線到指定伺服器上. 成功後, 就可以使用 recv 方法讀取資料.

建立一個伺服器 socket 使用的是相同的方法, 不過這裡不是連線到伺服器, 而是將 socket bind (繫結)到本機的一個埠上, 告訴它去監聽連線請求, 然後儘快處理每個到達的請求.

Example 7-2 建立了一個時間伺服器, 繫結到本機的 8037 埠( 1024 前的所有埠 是為系統服務保留的, Unix 系統下訪問它們你必須要有 root 許可權).

7.2.0.2. Example 7-2. 使用 socket 模組實現一個時間伺服器

File: socket-example-2.py import socket import struct, time # user-accessible port PORT = 8037 # reference time TIME1970 = 2208988800L # establish server service = socket.socket(socket.AF_INET, socket.SOCK_STREAM) service.bind(("", PORT)) service.listen(1) print "listening on port", PORT while 1: # serve forever channel, info = service.accept() print "connection from", info t = int(time.time()) + TIME1970 t = struct.pack("!I", t) channel.send(t) # send timestamp channel.close() # disconnect listening on port 8037 connection from ('127.0.0.1', 1469) connection from ('127.0.0.1', 1470) ...

listen 函式的呼叫告訴 socket 我們期望接受連線. 引數代表連線 的佇列(用於在程式沒有處理前保持連線)大小. 最後 accept 迴圈將當前時間返回 給每個連線的客戶端.

注意這裡的 accept 函式返回一個新的 socket 物件, 這個物件是直接連線到客戶端 的. 而原 socket 只是用來保持連線; 所有後來的資料傳輸操作都使用新的 socket .

我們可以使用 Example 7-3 , ( Example 7-1 的通用化版本)來測試這個伺服器, .

7.2.0.3. Example 7-3. 一個時間協議客戶端

File: timeclient.py import socket import struct, sys, time # default server host = "localhost" port = 8037 # reference time (in seconds since 1900-01-01 00:00:00) TIME1970 = 2208988800L # 1970-01-01 00:00:00 def gettime(host, port): # fetch time buffer from stream server s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) t = s.recv(4) s.close() t = struct.unpack("!I", t)[0] return int(t - TIME1970) if _ _name_ _ == "_ _main_ _": # command-line utility if sys.argv[1:]: host = sys.argv[1] if sys.argv[2:]: port = int(sys.argv[2]) else: port = 37 # default for public servers t = gettime(host, port) print "server time is", time.ctime(t) print "local clock is", int(time.time()) - t, "seconds off" server time is Sat Oct 09 16:58:50 1999 local clock is 0 seconds off

Example 7-3 所示的指令碼也可以作為模組使用; 你只需要匯入 timeclient 模組, 然後呼叫它的 gettime 函式.

目前為止, 我們已經使用了流( TCP ) socket . 時間協議還提到了 UDP sockets (報文). 流 socket 的工作模式和電話線類似; 你會知道在遠端 是否有人拿起接聽器, 在對方結束通話的時候你也會注意到. 相比之下, 傳送報文更像 是在一間黑屋子裡大聲喊. 可能某人會在那裡, 但你只有在他回覆的時候才會知道.

如 Example 7-4 所示, 你不需要在通過報文 socket 傳送資料時連線遠端機器. 只需使用 sendto 方法, 它接受資料和接收者地址作為引數. 讀取報文的時候使用 recvfrom 方法.

7.2.0.4. Example 7-4. 使用 socket 模組實現一個報文時間客戶端

File: socket-example-4.py import socket import struct, time # server HOST = "localhost" PORT = 8037 # reference time (in seconds since 1900-01-01 00:00:00) TIME1970 = 2208988800L # 1970-01-01 00:00:00 # connect to server s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # send empty packet s.sendto("", (HOST, PORT)) # read 4 bytes from server, and convert to time value t, server = s.recvfrom(4) t = struct.unpack("!I", t)[0] t = int(t - TIME1970) s.close() print "server time is", time.ctime(t) print "local clock is", int(time.time()) - t, "seconds off" server time is Sat Oct 09 16:42:36 1999 local clock is 8 seconds off

這裡的 recvfrom 返回兩個值: 資料和傳送者的地址. 後者用於傳送回覆資料.

Example 7-5 展示了對應的伺服器程式碼.

Example 7-5. 使用 socket 模組實現一個報文時間伺服器

File: socket-example-5.py import socket import struct, time # user-accessible port PORT = 8037 # reference time TIME1970 = 2208988800L # establish server service = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) service.bind(("", PORT)) print "listening on port", PORT while 1: # serve forever data, client = service.recvfrom(0) print "connection from", client t = int(time.time()) + TIME1970 t = struct.pack("!I", t) service.sendto(t, client) # send timestamp listening on port 8037 connection from ('127.0.0.1', 1469) connection from ('127.0.0.1', 1470) ...

最主要的不同在於伺服器使用 bind 來分配一個已知埠給 socket , 根據 recvfrom 函式返回的地址向客戶端傳送資料.


7.3. select 模組

select 模組允許你檢查一個或多個 socket , 管道, 以及其他流相容物件所接受的資料, 如 Example 7-6 所示.

你可以將一個或更多 socket 傳遞給 select 函式, 然後等待它們狀態改變(可讀, 可寫, 或是傳送錯誤訊號):

  • 如果某人在呼叫了 listen 函式後連線, 當遠端資料到達時, socket 就成為可讀的(這意味著 accept 不會阻塞). 或者是 socket 被關閉或重置時(在此情況下, recv 會返回一個空字串).
  • 當非阻塞呼叫 connect 方法後建立連線或是資料可以被寫入到 socket 時, socket 就成為可寫的.
  • 當非阻塞呼叫 connect 方法後連線失敗後, socket 會發出一個錯誤訊號.

7.3.0.1. Example 7-6. 使用 select 模組等待經 socket 傳送的資料

File: select-example-1.py import select import socket import time PORT = 8037 TIME1970 = 2208988800L service = socket.socket(socket.AF_INET, socket.SOCK_STREAM) service.bind(("", PORT)) service.listen(1) print "listening on port", PORT while 1: is_readable = [service] is_writable = [] is_error = [] r, w, e = select.select(is_readable, is_writable, is_error, 1.0) if r: channel, info = service.accept() print "connection from", info t = int(time.time()) + TIME1970 t = chr(t>>24&255) + chr(t>>16&255) + chr(t>>8&255) + chr(t&255) channel.send(t) # send timestamp channel.close() # disconnect else: print "still waiting" listening on port 8037 still waiting still waiting connection from ('127.0.0.1', 1469) still waiting connection from ('127.0.0.1', 1470) ...

在 Example 7-6 中, 我們等待監聽 socket 變成可讀狀態, 這代表有一個連線請求到達. 我們用和之前一樣的方法處理 channel socket , 因為它不可能因為等待 4 位元組而填充網路 緩衝區. 如果你需要向客戶端傳送大量的資料, 那麼你應該在迴圈的頂端把資料加入到 is_writable 列表中, 並且只在 select 允許的情況下寫入.

如果你設定 socket 為非阻塞模式(通過呼叫 setblocking 方法), 那麼你就可以使用 select 來等待 socket 連線. 不過 asyncore 模組(參見下一節)提供了一個強大的框架, 它自動為你處理好了這一切. 所以我不準備在這裡多說什麼, 看下一節吧.


7.4. asyncore 模組

asyncore 模組提供了一個 "反饋性的( reactive )" socket 實現. 該模組允許你定義特定過程完成後所執行的程式碼, 而不是建立 socket 物件, 呼叫它們的方法. 你只需要繼承 dispatcher 類, 然後過載如下方法 (可以選擇過載某一個或多個)就可以實現非同步的 socket 處理器.

  • handle_connect : 一個連線成功建立後被呼叫.
  • handle_expt : 連線失敗後被呼叫.
  • handle_accept : 連線請求建立到一個監聽 socket 上時被呼叫. 回撥時( callback )應該使用 accept 方法來獲得客戶端 socket .
  • handle_read : 有來自 socket 的資料等待讀取時被呼叫. 回撥時應該使用 recv 方法來獲得資料.
  • handle_write : socket 可以寫入資料的時候被呼叫. 使用 send 方法寫入資料.
  • handle_close : 當 socket 被關閉或復位時被呼叫.
  • handle_error(type, value, traceback) 在任何一個回撥函式發生 Python 錯誤時被呼叫. 預設的實現會列印跟蹤返回訊息到 sys.stdout .

Example 7-7 展示了一個時間客戶端, 和 socket 模組中的那個類似.

7.4.0.1. Example 7-7. 使用 asyncore 模組從時間伺服器獲得時間

File: asyncore-example-1.py import asyncore import socket, time # reference time (in seconds since 1900-01-01 00:00:00) TIME1970 = 2208988800L # 1970-01-01 00:00:00 class TimeRequest(asyncore.dispatcher): # time requestor (as defined in RFC 868) def _ _init_ _(self, host, port=37): asyncore.dispatcher._ _init_ _(self) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.connect((host, port)) def writable(self): return 0 # don't have anything to write def handle_connect(self): pass # connection succeeded def handle_expt(self): self.close() # connection failed, shutdown def handle_read(self): # get local time here = int(time.time()) + TIME1970 # get and unpack server time s = self.recv(4) there = ord(s[3]) + (ord(s[2])<<8) + (ord(s[1])<<16) + (ord(s[0])<<24L) self.adjust_time(int(here - there)) self.handle_close() # we don't expect more data def handle_close(self): self.close() def adjust_time(self, delta): # override this method! print "time difference is", delta # # try it out request = TimeRequest("www.python.org") asyncore.loop() log: adding channel <TimeRequest at 8cbe90> time difference is 28 log: closing channel 192:<TimeRequest connected at 8cbe90>

如果你不想記錄任何資訊, 那麼你可以在你的 dispatcher 類裡過載 log 方法.

Example 7-8 展示了對應的時間伺服器. 注意這裡它使用了兩個 dispatcher 子類, 一個用於監聽 socket , 另個用於與客戶端通訊.

7.4.0.2. Example 7-8. 使用 asyncore 模組實現時間伺服器

File: asyncore-example-2.py import asyncore import socket, time # reference time TIME1970 = 2208988800L class TimeChannel(asyncore.dispatcher): def handle_write(self): t = int(time.time()) + TIME1970 t = chr(t>>24&255) + chr(t>>16&255) + chr(t>>8&255) + chr(t&255) self.send(t) self.close() class TimeServer(asyncore.dispatcher): def _ _init_ _(self, port=37): self.port = port self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.bind(("", port)) self.listen(5) print "listening on port", self.port def handle_accept(self): channel, addr = self.accept() TimeChannel(channel) server = TimeServer(8037) asyncore.loop() log: adding channel <TimeServer at 8cb940> listening on port 8037 log: adding channel <TimeChannel at 8b2fd0> log: closing channel 52:<TimeChannel connected at 8b2fd0>

除了 dispatcher 外, 這個模組還包含一個 dispatcher_with_send 類. 你可以使用這個類傳送大量的資料而不會阻塞網路通訊緩衝區.

Example 7-9 中的模組通過繼承 dispatcher_with_send 類定義了一個 AsyncHTTP 類. 當你建立一個它的例項後, 它會發出一個 HTTP GET 請求並把 接受到的資料傳送到一個 "consumer" 目標物件

7.4.0.3. Example 7-9. 使用 asyncore 模組傳送 HTTP 請求

File: SimpleAsyncHTTP.py import asyncore import string, socket import StringIO import mimetools, urlparse class AsyncHTTP(asyncore.dispatcher_with_send): # HTTP requester def _ _init_ _(self, uri, consumer): asyncore.dispatcher_with_send._ _init_ _(self) self.uri = uri self.consumer = consumer # turn the uri into a valid request scheme, host, path, params, query, fragment = urlparse.urlparse(uri) assert scheme == "http", "only supports HTTP requests" try: host, port = string.split(host, ":", 1) port = int(port) except (TypeError, ValueError): port = 80 # default port if not path: path = "/" if params: path = path + ";" + params if query: path = path + "?" + query self.request = "GET %s HTTP/1.0/r/nHost: %s/r/n/r/n" % (path, host) self.host = host self.port = port self.status = None self.header = None self.data = "" # get things going! self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.connect((host, port)) def handle_connect(self): # connection succeeded self.send(self.request) def handle_expt(self): # connection failed; notify consumer (status is None) self.close() try: http_header = self.consumer.http_header except AttributeError: pass else: http_header(self) def handle_read(self): data = self.recv(2048) if not self.header: self.data = self.data + data try: i = string.index(self.data, "/r/n/r/n") except ValueError: return # continue else: # parse header fp = StringIO.StringIO(self.data[:i+4]) # status line is "HTTP/version status message" status = fp.readline() self.status = string.split(status, " ", 2) # followed by a rfc822-style message header self.header = mimetools.Message(fp) # followed by a newline, and the payload (if any) data = self.data[i+4:] self.data = "" # notify consumer (status is non-zero) try: http_header = self.consumer.http_header except AttributeError: pass else: http_header(self) if not self.connected: return # channel was closed by consumer self.consumer.feed(data) def handle_close(self): self.consumer.close() self.close()

Example 7-10 中的小指令碼展示瞭如何使用這個類.

7.4.0.4. Example 7-10. 使用 SimpleAsyncHTTP 類

File: asyncore-example-3.py import SimpleAsyncHTTP import asyncore class DummyConsumer: size = 0 def http_header(self, request): # handle header if request.status is None: print "connection failed" else: print "status", "=>", request.status for key, value in request.header.items(): print key, "=", value def feed(self, data): # handle incoming data self.size = self.size + len(data) def close(self): # end of data print self.size, "bytes in body" # # try it out consumer = DummyConsumer() request = SimpleAsyncHTTP.AsyncHTTP( "http://www.pythonware.com", consumer ) asyncore.loop() log: adding channel <AsyncHTTP at 8e2850> status => ['HTTP/1.1', '200', 'OK/015/012'] server = Apache/Unix (Unix) content-type = text/html content-length = 3730 ... 3730 bytes in body log: closing channel 156:<AsyncHTTP connected at 8e2850>

這裡的 consumer 介面設計時是為了與 htmllib 和 xmllib 分析器相容的, 這樣你就可以直接方便地解析 HTML 或是 XML 資料. http_header 方法是可選的; 如果沒有定義它, 那麼它將被忽略.

Example 7-10 的一個問題是它不能很好地處理重定向資源. Example 7-11 加入了一個額外的 consumer 層, 它可以很好地處理重定向.

7.4.0.5. Example 7-11. 使用 SimpleAsyncHTTP 類處理重定向

File: asyncore-example-4.py import SimpleAsyncHTTP import asyncore class DummyConsumer: size = 0 def http_header(self, request): # handle header if request.status is None: print "connection failed" else: print "status", "=>", request.status for key, value in request.header.items(): print key, "=", value def feed(self, data): # handle incoming data self.size = self.size + len(data) def close(self): # end of data print self.size, "bytes in body" class RedirectingConsumer: def _ _init_ _(self, consumer): self.consumer = consumer def http_header(self, request): # handle header if request.status is None or/ request.status[1] not in ("301", "302"): try: http_header = self.consumer.http_header except AttributeError: pass else: return http_header(request) else: # redirect! uri = request.header["location"] print "redirecting to", uri, "..." request.close() SimpleAsyncHTTP.AsyncHTTP(uri, self) def feed(self, data): self.consumer.feed(data) def close(self): self.consumer.close() # # try it out consumer = RedirectingConsumer(DummyConsumer()) request = SimpleAsyncHTTP.AsyncHTTP( "http://www.pythonware.com/library", consumer ) asyncore.loop() log: adding channel <AsyncHTTP at 8e64b0> redirecting to http://www.pythonware.com/library/ ... log: closing channel 48:<AsyncHTTP connected at 8e64b0> log: adding channel <AsyncHTTP at 8ea790> status => ['HTTP/1.1', '200', 'OK/015/012'] server = Apache/Unix (Unix) content-type = text/html content-length = 387 ... 387 bytes in body log: closing channel 236:<AsyncHTTP connected at 8ea790>

如果伺服器返回狀態 301 (永久重定向) 或者是 302 (臨時重定向), 重定向的 consumer 會關閉當前請求並向新地址發出新請求. 所有對 consumer 的其他呼叫傳遞給原來的 consumer .


7.5. asynchat 模組

asynchat 模組是對 asyncore 的一個擴充套件. 它提供對面向行( line-oriented )的協議的額外支援. 它還提供了增強的緩衝區支援(通過 push 方法和 "producer" 機制.

Example 7-12 實現了一個很小的 HTTP 伺服器. 它只是簡單地返回包含 HTTP 請求資訊的 HTML 文件(瀏覽器視窗出現的輸出).

7.5.0.1. Example 7-12. 使用 asynchat 模組實現一個迷你 HTTP 伺服器

File: asynchat-example-1.py import asyncore, asynchat import os, socket, string PORT = 8000 class HTTPChannel(asynchat.async_chat): def _ _init_ _(self, server, sock, addr): asynchat.async_chat._ _init_ _(self, sock) self.set_terminator("/r/n") self.request = None self.data = "" self.shutdown = 0 def collect_incoming_data(self, data): self.data = self.data + data def found_terminator(self): if not self.request: # got the request line self.request = string.split(self.data, None, 2) if len(self.request) != 3: self.shutdown = 1 else: self.push("HTTP/1.0 200 OK/r/n") self.push("Content-type: text/html/r/n") self.push("/r/n") self.data = self.data + "/r/n" self.set_terminator("/r/n/r/n") # look for end of headers else: # return payload. self.push("<html><body><pre>/r/n") self.push(self.data) self.push("</pre></body></html>/r/n") self.close_when_done() class HTTPServer(asyncore.dispatcher): def _ _init_ _(self, port): self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.bind(("", port)) self.listen(5) def handle_accept(self): conn, addr = self.accept() HTTPChannel(self, conn, addr) # # try it out s = HTTPServer(PORT) print "serving at port", PORT, "..." asyncore.loop() GET / HTTP/1.1 Accept: */* Accept-Language: en, sv Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; Bruce/1.0) Host: localhost:8000 Connection: Keep-Alive

producer 介面允許你傳入( "push" )太大以至於無法在記憶體中儲存的物件. asyncore 在需要更多資料的時候自動呼叫 producer 的 more 方法. 另外, 它使用一個空字串標記檔案的末尾.

Example 7-13 實現了一個很簡單的基於檔案的 HTTP 伺服器, 它使用了一個簡單的 FileProducer 類來從檔案中讀取資料, 每次只讀取幾 kb .

7.5.0.2. Example 7-13. 使用 asynchat 模組實現一個簡單的 HTTP 伺服器

File: asynchat-example-2.py import asyncore, asynchat import os, socket, string, sys import StringIO, mimetools ROOT = "." PORT = 8000 class HTTPChannel(asynchat.async_chat): def _ _init_ _(self, server, sock, addr): asynchat.async_chat._ _init_ _(self, sock) self.server = server self.set_terminator("/r/n/r/n") self.header = None self.data = "" self.shutdown = 0 def collect_incoming_data(self, data): self.data = self.data + data if len(self.data) > 16384: # limit the header size to prevent attacks self.shutdown = 1 def found_terminator(self): if not self.header: # parse http header fp = StringIO.StringIO(self.data) request = string.split(fp.readline(), None, 2) if len(request) != 3: # badly formed request; just shut down self.shutdown = 1 else: # parse message header self.header = mimetools.Message(fp) self.set_terminator("/r/n") self.server.handle_request( self, request[0], request[1], self.header ) self.close_when_done() self.data = "" else: pass # ignore body data, for now def pushstatus(self, status, explanation="OK"): self.push("HTTP/1.0 %d %s/r/n" % (status, explanation)) class FileProducer: # a producer that reads data from a file object def _ _init_ _(self, file): self.file = file def more(self): if self.file: data = self.file.read(2048) if data: return data self.file = None return "" class HTTPServer(asyncore.dispatcher): def _ _init_ _(self, port=None, request=None): if not port: port = 80 self.port = port if request: self.handle_request = request # external request handler self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.bind(("", port)) self.listen(5) def handle_accept(self): conn, addr = self.accept() HTTPChannel(self, conn, addr) def handle_request(self, channel, method, path, header): try: # this is not safe! while path[:1] == "/": path = path[1:] filename = os.path.join(ROOT, path) print path, "=>", filename file = open(filename, "r") except IOError: channel.pushstatus(404, "Not found") channel.push("Content-type: text/html/r/n") channel.push("/r/n") channel.push("<html><body>File not found.</body></html>/r/n") else: channel.pushstatus(200, "OK") channel.push("Content-type: text/html/r/n") channel.push("/r/n") channel.push_with_producer(FileProducer(file)) # # try it out s = HTTPServer(PORT) print "serving at port", PORT asyncore.loop() serving at port 8000 log: adding channel <HTTPServer at 8e54d0> log: adding channel <HTTPChannel at 8e64a0> samples/sample.htm => ./samples/sample.htm log: closing channel 96:<HTTPChannel connected at 8e64a0>

7.6. urllib 模組

urlib 模組為 HTTP , FTP , 以及 gopher 提供了一個統一的客戶端介面. 它會自動地根據 URL 選擇合適的協議處理器.

從 URL 獲取資料是非常簡單的. 只需要呼叫 urlopen 方法, 然後從返回的流物件中讀取資料即可, 如 Example 7-14 所示.

7.6.0.1. Example 7-14. 使用 urllib 模組獲取遠端資源

File: urllib-example-1.py import urllib fp = urllib.urlopen("http://www.python.org") op = open("out.html", "wb") n = 0 while 1: s = fp.read(8192) if not s: break op.write(s) n = n + len(s) fp.close() op.close() for k, v in fp.headers.items(): print k, "=", v print "copied", n, "bytes from", fp.url server = Apache/1.3.6 (Unix) content-type = text/html accept-ranges = bytes date = Mon, 11 Oct 1999 20:11:40 GMT connection = close etag = "741e9-7870-37f356bf" content-length = 30832 last-modified = Thu, 30 Sep 1999 12:25:35 GMT copied 30832 bytes from http://www.python.org

這個流物件提供了一些非標準的屬性. headers 是一個 Message 物件(在 mimetools 模組中定義), url 是實際的 URL . 後者會根據伺服器的重定向而更新.

urlopen 函式實際上是一個輔助函式, 它會建立一個 FancyURLopener 類的例項並呼叫它的 open 方法. 你也可以繼承這個類來完成特殊的行為. 例如Example 7-15 中的類會自動地 在必要時登陸伺服器.

7.6.0.2. Example 7-15. 用 urllib 模組實現自動身份驗證

File: urllib-example-3.py import urllib class myURLOpener(urllib.FancyURLopener): # read an URL, with automatic HTTP authentication def setpasswd(self, user, passwd): self._ _user = user self._ _passwd = passwd def prompt_user_passwd(self, host, realm): return self._ _user, self._ _passwd urlopener = myURLOpener() urlopener.setpasswd("mulder", "trustno1") fp = urlopener.open("http://www.secretlabs.com") print fp.read()

7.7. urlparse 模組

urlparse 模組包含用於處理 URL 的函式, 可以在 URL 和平臺特定的檔名間相互轉換. 如 Example 7-16 所示.

7.7.0.1. Example 7-16. 使用 urlparse 模組

File: urlparse-example-1.py import urlparse print urlparse.urlparse("http://host/path;params?query#fragment") ('http', 'host', '/path', 'params', 'query', 'fragment')

一個常見用途就是把 HTTP URL 分割為主機名和路徑元件(一個 HTTP 請求會涉及到 主機名以及請求路徑), 如 Example 7-17 所示.

7.7.0.2. Example 7-17. 使用 urlparse 模組處理 HTTP 定位器( HTTP Locators )

File: urlparse-example-2.py import urlparse scheme, host, path, params, query, fragment =/ urlparse.urlparse("http://host/path;params?query#fragment") if scheme == "http": print "host", "=>", host if params: path = path + ";" + params if query: path = path + "?" + query print "path", "=>", path host => host path => /path;params?query

Example 7-18 展示瞭如何使用 urlunparse 函式將各組成部分合並回一個 URL .

7.7.0.3. Example 7-18. 使用 urlparse 模組處理 HTTP 定位器( HTTP Locators )

File: urlparse-example-3.py import urlparse scheme, host, path, params, query, fragment =/ urlparse.urlparse("http://host/path;params?query#fragment") if scheme == "http": print "host", "=>", host print "path", "=>", urlparse.urlunparse( (None, None, path, params, query, None) ) host => host path => /path;params?query

Example 7-19 使用 urljoin 函式將絕對路徑和相對路徑組合起來.

7.7.0.4. Example 7-19. 使用 urlparse 模組組合相對定位器

File: urlparse-example-4.py import urlparse base = "http://spam.egg/my/little/pony" for path in "/index", "goldfish", "../black/cat": print path, "=>", urlparse.urljoin(base, path) /index => http://spam.egg/index goldfish => http://spam.egg/my/little/goldfish ../black/cat => http://spam.egg/my/black/cat

7.8. cookie 模組

(2.0 中新增) 該模組為 HTTP 客戶端和伺服器提供了基本的 cookie 支援. Example 7-20 展示了它的使用.

7.8.0.1. Example 7-20. 使用 cookie 模組

File: cookie-example-1.py import Cookie import os, time cookie = Cookie.SimpleCookie() cookie["user"] = "Mimi" cookie["timestamp"] = time.time() print cookie # simulate CGI roundtrip os.environ["HTTP_COOKIE"] = str(cookie) print cookie = Cookie.SmartCookie() cookie.load(os.environ["HTTP_COOKIE"]) for key, item in cookie.items(): # dictionary items are "Morsel" instances # use value attribute to get actual value print key, repr(item.value) Set-Cookie: timestamp=736513200; Set-Cookie: user=Mimi; user 'Mimi' timestamp '736513200'

7.9. robotparser 模組

(2.0 中新增) robotparser 模組用來讀取 robots.txt 檔案, 該檔案用於 Robot Exclusion Protocol (搜尋機器人排除協議?http://info.webcrawler.com/mak/projects/robots/robots.html).

如果你實現的一個 HTTP 機器人會訪問網路上的任意站點(並不只是你自己的站點), 那麼最好還是用該模組檢查下你所做的一切是不是受歡迎的. Example 7-21 展示了該模組的使用.

7.9.0.1. Example 7-21. 使用 robotparser 模組

File: robotparser-example-1.py import robotparser r = robotparser.RobotFileParser() r.set_url("http://www.python.org/robots.txt") r.read() if r.can_fetch("*", "/index.html"): print "may fetch the home page" if r.can_fetch("*", "/tim_one/index.html"): print "may fetch the tim peters archive" may fetch the home page

7.10. ftplib 模組

ftplib 模組包含了一個 File Transfer Protocol (FTP , 檔案傳輸協議)客戶端的實現.

Example 7-22 展示瞭如何登陸並獲得登陸目錄的檔案列表. 注意這裡的檔案列表 (列目錄操作)格式與伺服器有關(一般和主機平臺的列目錄工具輸出格式相同, 例如 Unix 下的 ls 和 Windows/DOS 下的 dir ).

7.10.0.1. Example 7-22. 使用 ftplib 模組獲得目錄列表

File: ftplib-example-1.py import ftplib ftp = ftplib.FTP("www.python.org") ftp.login("anonymous", "ftplib-example-1") print ftp.dir() ftp.quit() total 34 drwxrwxr-x 11 root 4127 512 Sep 14 14:18 . drwxrwxr-x 11 root 4127 512 Sep 14 14:18 .. drwxrwxr-x 2 root 4127 512 Sep 13 15:18 RCS lrwxrwxrwx 1 root bin 11 Jun 29 14:34 README -> welcome.msg drwxr-xr-x 3 root wheel 512 May 19 1998 bin drwxr-sr-x 3 root 1400 512 Jun 9 1997 dev drwxrwxr-- 2 root 4127 512 Feb 8 1998 dup drwxr-xr-x 3 root wheel 512 May 19 1998 etc ...

下載檔案很簡單; 使用合適的 retr 函式即可. 注意當你下載文字檔案時, 你必須自己加上行結束符. Example 7-23 中使用了一個 lambda 表示式完成這項工作.

7.10.0.2. Example 7-23. 使用 ftplib 模組下載檔案

File: ftplib-example-2.py import ftplib import sys def gettext(ftp, filename, outfile=None): # fetch a text file if outfile is None: outfile = sys.stdout # use a lambda to add newlines to the lines read from the server ftp.retrlines("RETR " + filename, lambda s, w=outfile.write: w(s+"/n")) def getbinary(ftp, filename, outfile=None): # fetch a binary file if outfile is None: outfile = sys.stdout ftp.retrbinary("RETR " + filename, outfile.write) ftp = ftplib.FTP("www.python.org") ftp.login("anonymous", "ftplib-example-2") gettext(ftp, "README") getbinary(ftp, "welcome.msg") WELCOME to python.org, the Python programming language home site. You are number %N of %M allowed users. Ni! Python Web site: http://www.python.org/ CONFUSED FTP CLIENT? Try begining your login password with '-' dash. This turns off continuation messages that may be confusing your client. ...

最後, Example 7-24 將檔案複製到 FTP 伺服器上. 這個指令碼使用副檔名來 判斷檔案是文字檔案還是二進位制檔案.

7.10.0.3. Example 7-24. 使用 ftplib 模組上傳檔案

File: ftplib-example-3.py import ftplib import os def upload(ftp, file): ext = os.path.splitext(file)[1] if ext in (".txt", ".htm", ".html"): ftp.storlines("STOR " + file, open(file)) else: ftp.storbinary("STOR " + file, open(file, "rb"), 1024) ftp = ftplib.FTP("ftp.fbi.gov") ftp.login("mulder", "trustno1") upload(ftp, "trixie.zip") upload(ftp, "file.txt") upload(ftp, "sightings.jpg")

7.11. gopherlib 模組

gopherlib 模組包含了一個 gopher 客戶端實現, 如 Example 7-25 所示.

7.11.0.1. Example 7-25. 使用 gopherlib 模組

File: gopherlib-example-1.py import gopherlib host = "gopher.spam.egg" f = gopherlib.send_selector("1/", host) for item in gopherlib.get_directory(f): print item ['0', "About Spam.Egg's Gopher Server", "0/About's Spam.Egg's Gopher Server", 'gopher.spam.egg', '70', '+'] ['1', 'About Spam.Egg', '1/Spam.Egg', 'gopher.spam.egg', '70', '+'] ['1', 'Misc', '1/Misc', 'gopher.spam.egg', '70', '+'] ...

7.12. httplib 模組

httplib 模組提供了一個 HTTP 客戶端介面, 如 Example 7-26 所示.

7.12.0.1. Example 7-26. 使用 httplib 模組

File: httplib-example-1.py import httplib USER_AGENT = "httplib-example-1.py" class Error: # indicates an HTTP error def _ _init_ _(self, url, errcode, errmsg, headers): self.url = url self.errcode = errcode self.errmsg = errmsg self.headers = headers def _ _repr_ _(self): return ( "<Error for %s: %s %s>" % (self.url, self.errcode, self.errmsg) ) class Server: def _ _init_ _(self, host): self.host = host def fetch(self, path): http = httplib.HTTP(self.host) # write header http.putrequest("GET", path) http.putheader("User-Agent", USER_AGENT) http.putheader("Host", self.host) http.putheader("Accept", "*/*") http.endheaders() # get response errcode, errmsg, headers = http.getreply() if errcode != 200: raise Error(errcode, errmsg, headers) file = http.getfile() return file.read() if _ _name_ _ == "_ _main_ _": server = Server("www.pythonware.com") print server.fetch("/index.htm")

注意 httplib 提供的 HTTP 客戶端在等待伺服器回覆的時候會阻塞程式. 非同步的解決方法請參閱 asyncore 模組中的例子.

7.12.1. 將資料傳送給伺服器

httplib 可以用來傳送其他 HTTP 命令, 例如 POST , 如 Example 7-27 所示.

7.12.1.1. Example 7-27. 使用 httplib 傳送資料

File: httplib-example-2.py import httplib USER_AGENT = "httplib-example-2.py" def post(host, path, data, type=None): http = httplib.HTTP(host) # write header http.putrequest("PUT", path) http.putheader("User-Agent", USER_AGENT) http.putheader("Host", host) if type: http.putheader("Content-Type", type) http.putheader("Content-Length", str(len(size))) http.endheaders() # write body http.send(data) # get response errcode, errmsg, headers = http.getreply() if errcode != 200: raise Error(errcode, errmsg, headers) file = http.getfile() return file.read() if _ _name_ _ == "_ _main_ _": post("www.spam.egg", "/bacon.htm", "a piece of data", "text/plain")

7.13. poplib 模組

poplib 模組(如 Example 7-28 所示) 提供了一個 Post Office Protocol ( POP3 協議) 客戶端實現. 這個協議用來從郵件伺服器 "pop" (拷貝) 資訊到你的個人電腦.

7.13.0.1. Example 7-28. 使用 poplib 模組

File: poplib-example-1.py import poplib import string, random import StringIO, rfc822 SERVER = "pop.spam.egg" USER = "mulder" PASSWORD = "trustno1" # connect to server server = poplib.POP3(SERVER) # login server.user(USER) server.pass_(PASSWORD) # list items on server resp, items, octets = server.list() # download a random message id, size = string.split(random.choice(items)) resp, text, octets = server.retr(id) text = string.join(text, "/n") file = StringIO.StringIO(text) message = rfc822.Message(file) for k, v in message.items(): print k, "=", v print message.fp.read() subject = ANN: (the eff-bot guide to) The Standard Python Library message-id = <199910120808.KAA09206@spam.egg> received = (from fredrik@spam.egg) by spam.egg (8.8.7/8.8.5) id KAA09206 for mulder; Tue, 12 Oct 1999 10:08:47 +0200 from = Fredrik Lundh <fredrik@spam.egg> date = Tue, 12 Oct 1999 10:08:47 +0200 to = mulder@spam.egg ...

7.14. imaplib 模組

imaplib 模組提供了一個 Internet Message Access Protocol ( IMAP, Internet 訊息訪問協議) 的客戶端實現. 這個協議允許你訪問郵件伺服器的郵件目錄, 就好像是在本機訪問一樣. 如 Example 7-29 所示.

7.14.0.1. Example 7-29. 使用 imaplib 模組

File: imaplib-example-1.py import imaplib import string, random import StringIO, rfc822 SERVER = "imap.spam.egg" USER = "mulder" PASSWORD = "trustno1" # connect to server server = imaplib.IMAP4(SERVER) # login server.login(USER, PASSWORD) server.select() # list items on server resp, items = server.search(None, "ALL") items = string.split(items[0]) # fetch a random item id = random.choice(items) resp, data = server.fetch(id, "(RFC822)") text = data[0][1] file = StringIO.StringIO(text) message = rfc822.Message(file) for k, v in message.items(): print k, "=", v print message.fp.read() server.logout() subject = ANN: (the eff-bot guide to) The Standard Python Library message-id = <199910120816.KAA12177@larch.spam.egg> to = mulder@spam.egg date = Tue, 12 Oct 1999 10:16:19 +0200 (MET DST) from = <effbot@spam.egg> received = (effbot@spam.egg) by imap.algonet.se (8.8.8+Sun/8.6.12) id KAA12177 for effbot@spam.egg; Tue, 12 Oct 1999 10:16:19 +0200 (MET DST) body text for test 5

7.15. smtplib 模組

smtplib 模組提供了一個 Simple Mail Transfer Protocol ( SMTP , 簡單郵件傳輸協議) 客戶端實現. 該協議用於通過 Unix 郵件伺服器傳送郵件, 如 Example 7-30 所示.

讀取郵件請使用 poplib 或 imaplib 模組.

7.15.0.1. Example 7-30. 使用 smtplib 模組

File: smtplib-example-1.py import smtplib import string, sys HOST = "localhost" FROM = "effbot@spam.egg" TO = "fredrik@spam.egg" SUBJECT = "for your information!" BODY = "next week: how to fling an otter" body = string.join(( "From: %s" % FROM, "To: %s" % TO, "Subject: %s" % SUBJECT, "", BODY), "/r/n") print body server = smtplib.SMTP(HOST) server.sendmail(FROM, [TO], body) server.quit() From: effbot@spam.egg To: fredrik@spam.egg Subject: for your information! next week: how to fling an otter

7.16. telnetlib 模組

telnetlib 模組提供了一個 telnet 客戶端實現.

Example 7-31 連線到一臺 Unix 計算機, 登陸, 然後請求一個目錄的列表.

7.16.0.1. Example 7-31. 使用 telnetlib 模組登陸到遠端伺服器

File: telnetlib-example-1.py import telnetlib import sys HOST = "spam.egg" USER = "mulder" PASSWORD = "trustno1" telnet = telnetlib.Telnet(HOST) telnet.read_until("login: ") telnet.write(USER + "/n") telnet.read_until("Password: ") telnet.write(PASSWORD + "/n") telnet.write("ls librarybook/n") telnet.write("exit/n") print telnet.read_all() [spam.egg mulder]$ ls README os-path-isabs-example-1.py SimpleAsyncHTTP.py os-path-isdir-example-1.py aifc-example-1.py os-path-isfile-example-1.py anydbm-example-1.py os-path-islink-example-1.py array-example-1.py os-path-ismount-example-1.py ...

7.17. nntplib 模組

nntplib 模組提供了一個網路新聞傳輸協議( Network News Transfer Protocol, NNTP )客戶端的實現.

7.17.1. 列出訊息

從新聞伺服器上讀取訊息之前, 你必須連線這個伺服器並選擇一個新聞組. Example 7-32 中的指令碼會從伺服器下載一個完成的訊息列表, 然後根據列表做簡單的統計.

7.17.1.1. Example 7-32. 使用 nntplib 模組列出訊息

File: nntplib-example-1.py import nntplib import string SERVER = "news.spam.egg" GROUP = "comp.lang.python"  AUTHOR = "fredrik@pythonware.com" # eff-bots human alias # connect to server server = nntplib.NNTP(SERVER) # choose a newsgroup resp, count, first, last, name = server.group(GROUP) print "count", "=>", count print "range", "=>", first, last # list all items on the server resp, items = server.xover(first, last) # extract some statistics authors = {} subjects = {} for id, subject, author, date, message_id, references, size, lines in items: authors[author] = None if subject[:4] == "Re: ": subject = subject[4:] subjects[subject] = None if string.find(author, AUTHOR) >= 0: print id, subject print "authors", "=>", len(authors) print "subjects", "=>", len(subjects) count => 607 range => 57179 57971 57474 Three decades of Python! ... 57477 More Python books coming... authors => 257 subjects => 200

7.17.2. 下載訊息

下載訊息是很簡單的, 只需要呼叫 article方法, 如 Example 7-33 所示.

7.17.2.1. Example 7-33. 使用 nntplib 模組下載訊息

File: nntplib-example-2.py import nntplib import string SERVER = "news.spam.egg" GROUP = "comp.lang.python"  KEYWORD = "tkinter" # connect to server server = nntplib.NNTP(SERVER) resp, count, first, last, name = server.group(GROUP) resp, items = server.xover(first, last) for id, subject, author, date, message_id, references, size, lines in items: if string.find(string.lower(subject), KEYWORD) >= 0: resp, id, message_id, text = server.article(id) print author print subject print len(text), "lines in article" "Fredrik Lundh" <fredrik@pythonware.com> Re: Programming Tkinter (In Python) 110 lines in article ...

Example 7-34 展示瞭如何進一步處理這些訊息, 你可以把它封裝到一個 Message 物件中(使用 rfc822 模組).

7.17.2.2. Example 7-34. 使用 nntplib 和 rfc822 模組處理訊息

File: nntplib-example-3.py import nntplib import string, random import StringIO, rfc822 SERVER = "news.spam.egg" GROUP = "comp.lang.python" # connect to server server = nntplib.NNTP(SERVER) resp, count, first, last, name = server.group(GROUP) for i in range(10): try: id = random.randint(int(first), int(last)) resp, id, message_id, text = server.article(str(id)) except (nntplib.error_temp, nntplib.error_perm): pass # no such message (maybe it was deleted?) else: break # found a message! else: raise SystemExit text = string.join(text, "/n") file = StringIO.StringIO(text) message = rfc822.Message(file) for k, v in message.items(): print k, "=", v print message.fp.read() mime-version = 1.0 content-type = text/plain; charset="iso-8859-1" message-id = <008501bf1417$1cf90b70$f29b12c2@sausage.spam.egg> lines = 22 ... from = "Fredrik Lundh" <fredrik@pythonware.com> nntp-posting-host = parrot.python.org subject = ANN: (the eff-bot guide to) The Standard Python Library ... </F>

到這一步後, 你可以使用 htmllib , uu , 以及 base64 繼續處理這些訊息.


7.18. SocketServer 模組

SocketServer 為各種基於 socket 的伺服器提供了一個框架. 該模組提供了大量的類, 你可以用它們來建立不同的伺服器.

Example 7-35 使用該模組實現了一個 Internet 時間協議伺服器. 你可以用前邊的 timeclient 指令碼連線它.

7.18.0.1. Example 7-35. 使用 SocketServer 模組

File: socketserver-example-1.py import SocketServer import time # user-accessible port PORT = 8037 # reference time TIME1970 = 2208988800L class TimeRequestHandler(SocketServer.StreamRequestHandler): def handle(self): print "connection from", self.client_address t = int(time.time()) + TIME1970 b = chr(t>>24&255) + chr(t>>16&255) + chr(t>>8&255) + chr(t&255) self.wfile.write(b) server = SocketServer.TCPServer(("", PORT), TimeRequestHandler) print "listening on port", PORT server.serve_forever() connection from ('127.0.0.1', 1488) connection from ('127.0.0.1', 1489) ...

7.19. BaseHTTPServer 模組

這是一個建立在 SocketServer 框架上的基本框架, 用於 HTTP 伺服器.

Example 7-36 在每次重新載入頁面時會生成一條隨機資訊. path 變數包含當前 URL , 你可以使用它為不同的 URL 生成不同的內容 (訪問除根目錄的其他任何 path 該指令碼都會返回一個錯誤頁面).

7.19.0.1. Example 7-36. 使用 BaseHTTPServer 模組

File: basehttpserver-example-1.py import BaseHTTPServer import cgi, random, sys MESSAGES = [ "That's as maybe, it's still a frog.", "Albatross! Albatross! Albatross!", "It's Wolfgang Amadeus Mozart.", "A pink form from Reading.", "Hello people, and welcome to 'It's a Tree.'" "I simply stare at the brick and it goes to sleep.", ] class Handler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): if self.path != "/": self.send_error(404, "File not found") return self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() try: # redirect stdout to client stdout = sys.stdout sys.stdout = self.wfile self.makepage() finally: sys.stdout = stdout # restore def makepage(self): # generate a random message tagline = random.choice(MESSAGES) print "<html>" print "<body>" print "<p>Today's quote: " print "<i>%s</i>" % cgi.escape(tagline) print "</body>" print "</html>" PORT = 8000 httpd = BaseHTTPServer.HTTPServer(("", PORT), Handler) print "serving at port", PORT httpd.serve_forever()

更有擴充套件性的 HTTP 框架請參閱 SimpleHTTPServer 和 CGIHTTPServer 模組.


7.20. SimpleHTTPServer 模組

SimpleHTTPServer 模組是一個簡單的 HTTP 伺服器, 它提供了標準的 GET 和 HEAD 請求處理器. 客戶端請求的路徑名稱會被翻譯為一個相對檔名 (相對於伺服器啟動時的當前路徑). Example 7-37 展示了該模組的使用.

7.20.0.1. Example 7-37. 使用 SimpleHTTPServer 模組

File: simplehttpserver-example-1.py import SimpleHTTPServer import SocketServer # minimal web server. serves files relative to the # current directory. PORT = 8000 Handler = SimpleHTTPServer.SimpleHTTPRequestHandler httpd = SocketServer.TCPServer(("", PORT), Handler) print "serving at port", PORT httpd.serve_forever() serving at port 8000 localhost - - [11/Oct/1999 15:07:44] code 403, message Directory listing not sup ported localhost - - [11/Oct/1999 15:07:44] "GET / HTTP/1.1" 403 - localhost - - [11/Oct/1999 15:07:56] "GET /samples/sample.htm HTTP/1.1" 200 -

這個伺服器會忽略驅動器符號和相對路徑名(例如 `..`). 但它並沒有任何訪問驗證處理, 所以請小心使用.

Example 7-38 實現了個迷你的 web 代理. 傳送給代理的 HTTP 請求必須包含目標伺服器的完整 URI . 代理伺服器使用 urllib 來獲取目標伺服器的資料.

7.20.0.2. Example 7-38. 使用 SimpleHTTPServer 模組實現代理

File: simplehttpserver-example-2.py # a truly minimal HTTP proxy import SocketServer import SimpleHTTPServer import urllib PORT = 1234 class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_GET(self): self.copyfile(urllib.urlopen(self.path), self.wfile) httpd = SocketServer.ForkingTCPServer(('', PORT), Proxy) print "serving at port", PORT httpd.serve_forever()

7.21. CGIHTTPServer 模組

CGIHTTPServer 模組是一個可以通過公共閘道器介面( common gateway interface , CGI )呼叫外部指令碼的 HTTP 伺服器. 如 Example 7-39 所示.

7.21.0.1. Example 7-39. 使用 CGIHTTPServer 模組

File: cgihttpserver-example-1.py import CGIHTTPServer import BaseHTTPServer class Handler(CGIHTTPServer.CGIHTTPRequestHandler): cgi_directories = ["/cgi"] PORT = 8000 httpd = BaseHTTPServer.HTTPServer(("", PORT), Handler) print "serving at port", PORT httpd.serve_forever()

7.22. cgi 模組

cgi 模組為 CGI 指令碼提供了函式和類支援. 它還可以處理 CGI 表單資料.

Example 7-40 展示了一個簡單的 CGI 指令碼, 它返回給定目錄下的檔案列表 (相對於指令碼中指定的根目錄)

7.22.0.1. Example 7-40. 使用 cgi 模組

File: cgi-example-1.py import cgi import os, urllib ROOT = "samples" # header print "text/html" print query = os.environ.get("QUERY_STRING") if not query: query = "." script = os.environ.get("SCRIPT_NAME", "") if not script: script = "cgi-example-1.py" print "<html>" print "<head>" print "<title>file listing</title>" print "</head>" print "</html>" print "<body>" try: files = os.listdir(os.path.join(ROOT, query)) except os.error: files = [] for file in files: link = cgi.escape(file) if os.path.isdir(os.path.join(ROOT, query, file)): href = script + "?" + os.path.join(query, file) print "<p><a href= '%s'>%s</a>" % (href, cgi.escape(link)) else: print "<p>%s" % link print "</body>" print "</html>" text/html <html> <head> <title>file listing</title> </head> </html> <body> <p>sample.gif <p>sample.gz <p>sample.netrc ... <p>sample.txt <p>sample.xml <p>sample~ <p><a href='cgi-example-1.py?web'>web</a> </body> </html>

7.23. webbrowser 模組

(2.0 中新增) webbrowser 模組提供了一個到系統標準 web 瀏覽器的介面. 它提供了一個 open 函式, 接受檔名或 URL 作為引數, 然後在瀏覽器中開啟它. 如果你又一次呼叫 open 函式, 那麼它會嘗試在相同的視窗開啟新頁面. 如 Example 7-41 所示.

7.23.0.1. Example 7-41. 使用 webbrowser 模組

File: webbrowser-example-1.py import webbrowser import time webbrowser.open("http://www.pythonware.com") # wait a while, and then go to another page time.sleep(5) webbrowser.open( "http://www.pythonware.com/people/fredrik/librarybook.htm" )

在 Unix 下, 該模組支援 lynx , Netscape , Mosaic , Konquerer , 和 Grail . 在 Windows 和 Macintosh 下, 它會呼叫標準瀏覽器 (在登錄檔或是 Internet 選項皮膚中定義).


8. 國際化


8.1. locale 模組

locale 模組提供了 C 本地化( localization )函式的介面, 如 Example 8-1 所示. 同時提供相關函式, 實現基於當前 locale 設定的數字, 字串轉換. (而 int ,float , 以及 string 模組中的相關轉換函式不受 locale 設定的影響.)

====Example 8-1. 使用 locale 模組格式化資料=====[eg-8-1]

File: locale-example-1.py import locale print "locale", "=>", locale.setlocale(locale.LC_ALL, "") # integer formatting value = 4711 print locale.format("%d", value, 1), "==", print locale.atoi(locale.format("%d", value, 1)) # floating point value = 47.11 print locale.format("%f", value, 1), "==", print locale.atof(locale.format("%f", value, 1)) info = locale.localeconv() print info["int_curr_symbol"] locale => Swedish_Sweden.1252 4,711 == 4711 47,110000 == 47.11 SEK

Example 8-2 展示瞭如何使用 locale 模組獲得當前平臺 locale 設定.

8.1.0.1. Example 8-2. 使用 locale 模組獲得當前平臺 locale 設定

File: locale-example-2.py import locale language, encoding = locale.getdefaultlocale() print "language", language print "encoding", encoding language sv_SE encoding cp1252

8.2. unicodedata 模組

( 2.0 中新增) unicodedata 模組包含了 Unicode 字元的屬性, 例如字元類別, 分解資料, 以及數值. 如 Example 8-3 所示.

8.2.0.1. Example 8-3. 使用 unicodedata 模組

File: unicodedata-example-1.py import unicodedata for char in [u"A", u"-", u"1", u"/N{LATIN CAPITAL LETTER O WITH DIAERESIS}"]: print repr(char), print unicodedata.category(char), print repr(unicodedata.decomposition(char)), print unicodedata.decimal(char, None), print unicodedata.numeric(char, None) u'A' Lu '' None None u'-' Pd '' None None u'1' Nd '' 1 1.0 u'/303/226' Lu '004F 0308' None None

在 Python 2.0 中缺少 CJK 象形文字和韓語音節的屬性. 這影響到了 0x3400-0x4DB5 , 0x4E00-0x9FA5 , 以及 0xAC00-D7A3 中的字元, 不過每個區間內的第一個字元屬性是正確的, 我們可以把字元對映到起始 實現正常操作:

def remap(char): # fix for broken unicode property database in Python 2.0 c = ord(char) if 0x3400 <= c <= 0x4DB5: return unichr(0x3400) if 0x4E00 <= c <= 0x9FA5: return unichr(0x4E00) if 0xAC00 <= c <= 0xD7A3: return unichr(0xAC00) return char

Python 2.1 修復了這個 bug .


8.3. ucnhash 模組

(僅適用於 2.0 ) ucnhash 模組為一些 Unicode 字元程式碼提供了特定的命名. 你可以直接使用 /N{} 轉義符將 Unicode 字元名稱對映到字元程式碼上. 如Example 8-4 所示.

8.3.0.1. Example 8-4. 使用 ucnhash 模組

File: ucnhash-example-1.py # Python imports this module automatically, when it sees # the first /N{} escape # import ucnhash print repr(u"/N{FROWN}") print repr(u"/N{SMILE}") print repr(u"/N{SKULL AND CROSSBONES}") u'/u2322' u'/u2323' u'/u2620'

9. 多媒體相關模組

"Wot? No quote?"
- Guido van Rossum

9.1. 概覽

Python 提供了一些用於處理圖片和音訊檔案的模組.

另請參閱 Pythonware Image Library ( PIL , http://www.pythonware.com/products/pil/ ), 以及 PythonWare Sound Toolkit (PST ,http://www.pythonware.com/products/pst/ ).

譯註: 別參閱 PST 了, 廢了, 用 pymedia 代替吧.


9.2. imghdr 模組

imghdr 模組可識別不同格式的圖片檔案. 當前版本可以識別 bmp , gif , jpeg , pbm , pgm , png , ppm , rast (Sun raster), rgb (SGI), tiff , 以及 xbm影象. 如 Example 9-1 所示.

9.2.0.1. Example 9-1. 使用 imghdr 模組

File: imghdr-example-1.py import imghdr result = imghdr.what("samples/sample.jpg") if result: print "file format:", result else: print "cannot identify file" file format: jpeg
# 使用 PIL  import Image im = Image.open("samples/sample.jpg") print im.format, im.mode, im.size

9.3. sndhdr 模組

sndhdr 模組, 可來識別不同的音訊檔案格式, 並提取檔案內容相關資訊. 如 Example 9-2 所示.

執行成功後, what 函式將返回一個由檔案型別, 取樣頻率, 聲道數, 音軌數和每個取樣點位陣列成的元組. 具體含義請參考 help(sndhdr) .

9.3.0.1. Example 9-2. 使用 sndhdr 模組

File: sndhdr-example-1.py import sndhdr result = sndhdr.what("samples/sample.wav") if result: print "file format:", result else: print "cannot identify file" file format: ('wav', 44100, 1, -1, 16)

9.4. whatsound 模組

(已廢棄) whatsound 是 sndhdr 模組的一個別名. 如 Example 9-3 所示.

9.4.0.1. Example 9-3. 使用 whatsound 模組

File: whatsound-example-1.py import whatsound # same as sndhdr result = whatsound.what("samples/sample.wav") if result: print "file format:", result else: print "cannot identify file" file format: ('wav', 44100, 1, -1, 16)

9.5. aifc 模組

aifc 模組用於讀寫 AIFF 和 AIFC 音訊檔案(在 SGI 和 Macintosh 的計算機上使用). 如 Example 9-4 所示.

9.5.0.1. Example 9-4. 使用 aifc 模組

File: SimpleAsyncHTTP.py import asyncore import string, socket import StringIO import mimetools, urlparse class AsyncHTTP(asyncore.dispatcher_with_send): # HTTP requestor def _ _init_ _(self, uri, consumer): asyncore.dispatcher_with_send._ _init_ _(self) self.uri = uri self.consumer = consumer # turn the uri into a valid request scheme, host, path, params, query, fragment = urlparse.urlparse(uri) assert scheme == "http", "only supports HTTP requests" try: host, port = string.split(host, ":", 1) port = int(port) except (TypeError, ValueError): port = 80 # default port if not path: path = "/" if params: path = path + ";" + params if query: path = path + "?" + query self.request = "GET %s HTTP/1.0/r/nHost: %s/r/n/r/n" % (path, host) self.host = host self.port = port self.status = None self.header = None self.data = "" # get things going! self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.connect((host, port)) def handle_connect(self): # connection succeeded self.send(self.request) def handle_expt(self): # connection failed; notify consumer (status is None) self.close() try: http_header = self.consumer.http_header except AttributeError: pass else: http_header(self) def handle_read(self): data = self.recv(2048) if not self.header: self.data = self.data + data try: i = string.index(self.data, "/r/n/r/n") except ValueError: return # continue else: # parse header fp = StringIO.StringIO(self.data[:i+4]) # status line is "HTTP/version status message" status = fp.readline() self.status = string.split(status, " ", 2) # followed by a rfc822-style message header self.header = mimetools.Message(fp) # followed by a newline, and the payload (if any) data = self.data[i+4:] self.data = "" # notify consumer (status is non-zero) try: http_header = self.consumer.http_header except AttributeError: pass else: http_header(self) if not self.connected: return # channel was closed by consumer self.consumer.feed(data) def handle_close(self): self.consumer.close() self.close()

9.6. sunau 模組

sunau 模組用於讀寫 Sun AU 音訊檔案. 如 Example 9-5 所示.

9.6.0.1. Example 9-5. 使用 sunau 模組

File: sunau-example-1.py import sunau w = sunau.open("samples/sample.au", "r") if w.getnchannels() == 1: print "mono,", else: print "stereo,", print w.getsampwidth()*8, "bits,", print w.getframerate(), "Hz sampling rate" mono, 16 bits, 8012 Hz sampling rate

9.7. sunaudio 模組

sunaudio 模組用於識別 Sun AU 音訊檔案, 並提取其基本資訊. sunau 模組為 Sun AU 檔案提供了更完成的支援. 如 Example 9-6 所示

9.7.0.1. Example 9-6. 使用 sunaudio 模組

File: sunaudio-example-1.py import sunaudio file = "samples/sample.au" print sunaudio.gethdr(open(file, "rb")) (6761, 1, 8012, 1, 'sample.au')

9.8. wave 模組

wave 模組用於讀寫 Microsoft WAV 音訊檔案, 如 Example 9-7 所示.

9.8.0.1. Example 9-7. 使用 wave 模組

File: wave-example-1.py import wave w = wave.open("samples/sample.wav", "r") if w.getnchannels() == 1: print "mono,", else: print "stereo,", print w.getsampwidth()*8, "bits,", print w.getframerate(), "Hz sampling rate" mono, 16 bits, 44100 Hz sampling rate

9.9. audiodev 模組

(只用於 Unix) audiodev 為 Sun 和 SGI 計算機提供了音訊播放支援. 如 Example 9-8 所示.

9.9.0.1. Example 9-8. 使用 audiodev 模組

File: audiodev-example-1.py import audiodev import aifc sound = aifc.open("samples/sample.aiff", "r") player = audiodev.AudioDev() player.setoutrate(sound.getframerate()) player.setsampwidth(sound.getsampwidth()) player.setnchannels(sound.getnchannels()) bytes_per_frame = sound.getsampwidth() * sound.getnchannels() bytes_per_second = sound.getframerate() * bytes_per_frame while 1: data = sound.readframes(bytes_per_second) if not data: break player.writeframes(data) player.wait()

9.10. winsound 模組

(只用於 Windows ) winsound 模組允許你在 Winodws 平臺上播放 Wave 檔案. 如 Example 9-9 所示.

9.10.0.1. Example 9-9. 使用 winsound 模組

File: winsound-example-1.py import winsound file = "samples/sample.wav" winsound.PlaySound( file, winsound.SND_FILENAME|winsound.SND_NOWAIT, )

flag 變數說明:

  • SND_FILENAME - sound 是一個 wav 檔名
  • SND_ALIAS - sound 是一個登錄檔中指定的別名
  • SND_LOOP - 重複播放直到下一次 PlaySound ; 必須指定 SND_ASYNC
  • SND_MEMORY - sound 是一個 wav 檔案的記憶體映像
  • SND_PURGE - 停止指定 sound 的所有例項
  • SND_ASYNC - 非同步播放聲音, 聲音開始播放後函式立即返回
  • SND_NODEFAULT - 找不到 sound 時不播放預設的 beep 聲音
  • SND_NOSTOP - 不打斷當前播放中的任何 sound
  • SND_NOWAIT - sound 驅動忙時立即返回

10. 資料儲存

"Unlike mainstream component programming, scripts usually do not introduce new components but simply 'wire' existing ones. Scripts can be seen as introducing behavior but no new state ... Of course, there is nothing to stop a 'scripting' language from introducing persistent state — it then simply turns into a normal programming language."
- Clemens Szyperski, in Component Software

10.1. 概覽

Python 提供了多種相似資料庫管理( database manager )的驅動, 它們的模型都基於 Unix 的 dbm 庫. 這些資料庫和普通的字典物件類似, 但這裡需要注意的是它只能接受字串作為鍵和值. ( shelve 模組可以處理任何型別的值)


10.2. anydbm 模組

anydbm 模組為簡單資料庫驅動提供了統一標準的介面.

當第一次被匯入的時候, anydbm 模組會自動尋找一個合適的資料庫驅動, 按照 dbhash , gdbm , dbm , 或 dumbdbm 的順序嘗試. 如果沒有找到任何模組, 它將引發一個 ImportError 異常.

open 函式用於開啟或建立一個資料庫(使用匯入時找到的資料庫驅動), 如 Example 10-1 所示.

10.2.0.1. Example 10-1. 使用 anydbm 模組

File: anydbm-example-1.py import anydbm db = anydbm.open("database", "c") db["1"] = "one" db["2"] = "two" db["3"] = "three" db.close() db = anydbm.open("database", "r") for key in db.keys(): print repr(key), repr(db[key]) '2' 'two' '3' 'three' '1' 'one'

10.3. whichdb 模組

whichdb 模組可以判斷給定資料庫檔案的格式, 如 Example 10-2 所示.

10.3.0.1. Example 10-2. 使用 whichdb 模組

File: whichdb-example-1.py import whichdb filename = "database" result = whichdb.whichdb(filename) if result: print "file created by", result handler = _ _import_ _(result) db = handler.open(filename, "r") print db.keys() else: # cannot identify data base if result is None: print "cannot read database file", filename else: print "cannot identify database file", filename db = None

這個例子中使用了 _ _import_ _ 函式來匯入對應模組(還記得我們在第一章的例子麼?).


10.4. shelve 模組

shelve 模組使用資料庫驅動實現了字典物件的持久儲存. shelve 物件使用字串作為鍵, 但值可以是任意型別, 所有可以被 pickle 模組處理的物件都可以作為它的值. 如 Example 10-3 所示.

10.4.0.1. Example 10-3. 使用 shelve 模組

File: shelve-example-1.py import shelve db = shelve.open("database", "c") db["one"] = 1 db["two"] = 2 db["three"] = 3 db.close() db = shelve.open("database", "r") for key in db.keys(): print repr(key), repr(db[key]) 'one' 1 'three' 3 'two' 2

Example 10-4 展示瞭如何使用 shelve 處理給定的資料庫驅動.

10.4.0.2. Example 10-4. 使用 shelve 模組處理給定資料庫

File: shelve-example-3.py import shelve import gdbm def gdbm_shelve(filename, flag="c"): return shelve.Shelf(gdbm.open(filename, flag)) db = gdbm_shelve("dbfile")

10.5. dbhash 模組

(可選) dbhash 模組為 bsddb 資料庫驅動提供了一個 dbm 相容的介面. 如 Example 10-5 所示.

10.5.0.1. Example 10-5. 使用 dbhash 模組

File: dbhash-example-1.py import dbhash db = dbhash.open("dbhash", "c") db["one"] = "the foot" db["two"] = "the shoulder" db["three"] = "the other foot" db["four"] = "the bridge of the nose" db["five"] = "the naughty bits" db["six"] = "just above the elbow" db["seven"] = "two inches to the right of a very naughty bit indeed" db["eight"] = "the kneecap" db.close() db = dbhash.open("dbhash", "r") for key in db.keys(): print repr(key), repr(db[key])

10.6. dbm 模組

(可選) dbm 模組提供了一個到 dbm 資料庫驅動的介面(在許多 Unix 平臺上都可用). 如 Example 10-6 所示.

10.6.0.1. Example 10-6. 使用 dbm 模組

File: dbm-example-1.py import dbm db = dbm.open("dbm", "c") db["first"] = "bruce" db["second"] = "bruce" db["third"] = "bruce" db["fourth"] = "bruce" db["fifth"] = "michael" db["fifth"] = "bruce" # overwrite db.close() db = dbm.open("dbm", "r") for key in db.keys(): print repr(key), repr(db[key]) 'first' 'bruce' 'second' 'bruce' 'fourth' 'bruce' 'third' 'bruce' 'fifth' 'bruce'

10.7. dumbdbm 模組

dumbdbm 模組是一個簡單的資料庫實現, 與 dbm 一類相似, 但使用純 Python 實現. 它使用兩個檔案: 一個二進位制檔案 (.dat) 用於儲存資料, 一個文字檔案 (.dir) 用於資料描述.

10.7.0.1. Example 10-7. 使用 dumbdbm 模組

File: dumbdbm-example-1.py import dumbdbm db = dumbdbm.open("dumbdbm", "c") db["first"] = "fear" db["second"] = "surprise" db["third"] = "ruthless efficiency" db["fourth"] = "an almost fanatical devotion to the Pope" db["fifth"] = "nice red uniforms" db.close() db = dumbdbm.open("dumbdbm", "r") for key in db.keys(): print repr(key), repr(db[key]) 'first' 'fear' 'third' 'ruthless efficiency' 'fifth' 'nice red uniforms' 'second' 'surprise' 'fourth' 'an almost fanatical devotion to the Pope'

10.8. gdbm 模組

(可選) gdbm 模組提供了到 GNU dbm 資料驅動的介面, 如 Example 10-8 所示.

10.8.0.1. Example 10-8. 使用 gdbm 模組

File: gdbm-example-1.py import gdbm db = gdbm.open("gdbm", "c") db["1"] = "call" db["2"] = "the" db["3"] = "next" db["4"] = "defendant" db.close() db = gdbm.open("gdbm", "r") keys = db.keys() keys.sort() for key in keys: print db[key], call the next defendant

11. 工具和實用程式

標準庫中有一些模組既可用作模組又可以作為命令列實用程式.


11.1. dis 模組

dis 模組是 Python 的反彙編器. 它可以把位元組碼轉換為更容易讓人看懂的格式.

你可以從命令列呼叫反彙編器. 它會編譯給定的指令碼並把反彙編後的位元組程式碼輸出到終端上:

$ dis.py hello.py 0 SET_LINENO 0 3 SET_LINENO 1 6 LOAD_CONST 0 ('hello again, and welcome to the show') 9 PRINT_ITEM 10 PRINT_NEWLINE 11 LOAD_CONST 1 (None) 14 RETURN_VALUE

當然 dis 也可以作為模組使用. dis 函式接受一個類, 方法, 函式, 或者 code 物件 作為單個引數. 如 Example 11-1 所示.

11.1.0.1. Example 11-1. 使用 dis 模組

File: dis-example-1.py import dis def procedure(): print 'hello' dis.dis(procedure) 0 SET_LINENO 3 3 SET_LINENO 4 6 LOAD_CONST 1 ('hello') 9 PRINT_ITEM 10 PRINT_NEWLINE 11 LOAD_CONST 0 (None) 14 RETURN_VALUE

11.2. pdb 模組

pdb 模組是標準 Python 偵錯程式( debugger ). 它基於 bdb 偵錯程式框架.

你可以從命令列呼叫偵錯程式 (鍵入 n  進入下一行程式碼, 鍵入 help 獲得可用命令列表):

$ pdb.py hello.py > hello.py(0)?() (Pdb) n > hello.py() (Pdb) n hello again, and welcome to the show --Return-- > hello.py(1)?()->None (Pdb)

Example 11-2 展示瞭如何從程式中啟動偵錯程式.

11.2.0.1. Example 11-2. 使用 pdb 模組

File: pdb-example-1.py import pdb def test(n): j = 0 for i in range(n): j = j + i return n db = pdb.Pdb() db.runcall(test, 1) > pdb-example-1.py(3)test() -> def test(n): (Pdb) s > pdb-example-1.py(4)test() -> j = 0 (Pdb) s > pdb-example-1.py(5)test() -> for i in range(n): ...

11.3. bdb 模組

bdb 模組為提供了一個偵錯程式框架. 你可以使用它來建立自定義的偵錯程式, 如 Example 11-3 所示.

你需要做的只是繼承 Bdb 類, 覆蓋它的 user 方法(在每次偵錯程式停止的時候被呼叫). 使用各種各樣的 set 方法可以控制偵錯程式.

11.3.0.1. Example 11-3. 使用 bdb 模組

File: bdb-example-1.py import bdb import time def spam(n): j = 0 for i in range(n): j = j + i return n def egg(n): spam(n) spam(n) spam(n) spam(n) def test(n): egg(n) class myDebugger(bdb.Bdb): run = 0 def user_call(self, frame, args): name = frame.f_code.co_name or "<unknown>" print "call", name, args self.set_continue() # continue def user_line(self, frame): if self.run: self.run = 0 self.set_trace() # start tracing else: # arrived at breakpoint name = frame.f_code.co_name or "<unknown>" filename = self.canonic(frame.f_code.co_filename) print "break at", filename, frame.f_lineno, "in", name print "continue..." self.set_continue() # continue to next breakpoint def user_return(self, frame, value): name = frame.f_code.co_name or "<unknown>" print "return from", name, value print "continue..." self.set_continue() # continue def user_exception(self, frame, exception): name = frame.f_code.co_name or "<unknown>" print "exception in", name, exception print "continue..." self.set_continue() # continue db = myDebugger() db.run = 1 db.set_break("bdb-example-1.py", 7) db.runcall(test, 1) continue... call egg None call spam None break at C:/ematter/librarybook/bdb-example-1.py 7 in spam continue... call spam None break at C:/ematter/librarybook/bdb-example-1.py 7 in spam continue... call spam None break at C:/ematter/librarybook/bdb-example-1.py 7 in spam continue... call spam None break at C:/ematter/librarybook/bdb-example-1.py 7 in spam continue...

11.4. profile 模組

profile 模組是標準 Python 分析器.

和反彙編器, 偵錯程式相同, 你可以從命令列呼叫分析器:

$ profile.py hello.py hello again, and welcome to the show 3 function calls in 0.785 CPU seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.001 0.001 0.002 0.002 <string>:1(?) 1 0.001 0.001 0.001 0.001 hello.py:1(?) 1 0.783 0.783 0.785 0.785 profile:0(execfile('hello.py')) 0 0.000 0.000 profile:0(profiler)

如 Example 11-4 所示, 我們還可以從程式中呼叫 profile 來對程式效能做分析.

11.4.0.1. Example 11-4. U使用 profile 模組

File: profile-example-1.py import profile def func1(): for i in range(1000): pass def func2(): for i in range(1000): func1() profile.run("func2()") 1003 function calls in 2.380 CPU seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 2.040 2.040 <string>:1(?) 1000 1.950 0.002 1.950 0.002 profile-example-1.py:3(func1) 1 0.090 0.090 2.040 2.040 profile-example-1.py:7(func2) 1 0.340 0.340 2.380 2.380 profile:0(func2()) 0 0.000 0.000 profile:0(profiler)

你可以使用 pstats 模組來修改結果報告的形式.


11.5. pstats 模組

pstats 模組用於分析 Python 分析器收集的資料. 如 Example 11-5 所示.

11.5.0.1. Example 11-5. 使用 pstats 模組

File: pstats-example-1.py import pstats import profile def func1(): for i in range(1000): pass def func2(): for i in range(1000): func1() p = profile.Profile() p.run("func2()") s = pstats.Stats(p) s.sort_stats("time", "name").print_stats() 1003 function calls in 1.574 CPU seconds Ordered by: internal time, function name ncalls tottime percall cumtime percall filename:lineno(function) 1000 1.522 0.002 1.522 0.002 pstats-example-1.py:4(func1) 1 0.051 0.051 1.573 1.573 pstats-example-1.py:8(func2) 1 0.001 0.001 1.574 1.574 profile:0(func2()) 1 0.000 0.000 1.573 1.573 <string>:1(?) 0 0.000 0.000 profile:0(profiler)

11.6. tabnanny 模組

(2.0 新增) tabnanny 模組用於檢查 Python 原始檔中的含糊的縮排. 當檔案混合了 tab 和空格兩種縮排時候, nanny (保姆)會立即給出提示.

在下邊使用的 badtabs.py 檔案中, if 語句後的第一行使用 4 個空格和 1 個 tab . 第二行只使用了空格.

$ tabnanny.py -v samples/badtabs.py ';samples/badtabs.py': *** Line 3: trouble in tab city! *** offending line: print "world" indent not equal e.g. at tab sizes 1, 2, 3, 5, 6, 7, 9

因為 Python 直譯器把 tab 作為 8 個空格來處理, 所以這個指令碼可以正常執行. 在所有符合程式碼標準(一個 tab 為 8 個空格)的編輯器中它也會正常顯示. 當然, 這些都騙不過 nanny .

Example 11-6 展示瞭如何在你自己的程式中使用 tabnanny .

11.6.0.1. Example 11-6. 使用 tabnanny 模組

File: tabnanny-example-1.py import tabnanny FILE = "samples/badtabs.py" file = open(FILE) for line in file.readlines(): print repr(line) # let tabnanny look at it tabnanny.check(FILE) 'if 1:/012' ' /011print "hello"/012' ' print "world"/012' samples/badtabs.py 3 ' print "world"'/012'

將 sys.stdout 重定向到一個 StringIO 物件就可以捕獲輸出.


12. 其他模組


12.1. 概覽

本章介紹了一些平臺相關的模組. 重點放在了適用於整個平臺家族的模組上. (比如 Unix , Windows 家族)


12.2. fcntl 模組

(只用於 Unix) fcntl 模組為 Unix上的 ioctl 和 fcntl 函式提供了一個介面. 它們用於檔案控制程式碼和 I/O 裝置控制程式碼的 "out of band" 操作, 包括讀取擴充套件屬性, 控制阻塞. 更改終端行為等等. (out of band management: 指使用分離的渠道進行裝置管理. 這使系統管理員能在機器關機的時候對伺服器, 網路進行監視和管理. 出處: http://en.wikipedia.org/wiki/Out-of-band_management )

關於如何在平臺上使用這些函式, 請查閱對應的 Unix man 手冊.

該模組同時提供了 Unix 檔案鎖定機制的介面. Example 12-1 展示瞭如何使用 flock 函式, 更新檔案時為檔案設定一個 advisory lock .

輸出結果是由同時執行 3 個副本得到的. 像這樣(都在一句命令列裡):

python fcntl-example-1.py& python fcntl-example-1.py& python fcntl-example-1.py&

如果你註釋掉對 flock 的呼叫, 那麼 counter 檔案不會正確地更新.

12.2.0.1. Example 12-1. Using the fcntl Module

File: fcntl-example-1.py import fcntl, FCNTL import os, time FILE = "counter.txt" if not os.path.exists(FILE): # create the counter file if it doesn't exist # 建立 counter 檔案 file = open(FILE, "w") file.write("0") file.close() for i in range(20): # increment the counter file = open(FILE, "r+") fcntl.flock(file.fileno(), FCNTL.LOCK_EX) counter = int(file.readline()) + 1 file.seek(0) file.write(str(counter)) file.close() # unlocks the file print os.getpid(), "=>", counter time.sleep(0.1) 30940 => 1 30942 => 2 30941 => 3 30940 => 4 30941 => 5 30942 => 6

12.3. pwd 模組

(只用於 Unix) pwd 提供了一個到 Unix 密碼/password "資料庫"( /etc/passwd 以及相關檔案 )的介面. 這個資料庫(一般是一個純文字檔案)包含本地機器使用者賬戶的資訊. 如 Example 12-2 所示.

12.3.0.1. Example 12-2. 使用 pwd 模組

File: pwd-example-1.py import pwd import os print pwd.getpwuid(os.getgid()) print pwd.getpwnam("root") ('effbot', 'dsWjk8', 4711, 4711, 'eff-bot', '/home/effbot', '/bin/bosh') ('root', 'hs2giiw', 0, 0, 'root', '/root', '/bin/bash')

getpwall 函式返回一個包含所有可用使用者資料庫入口的列表. 你可以使用它搜尋一個使用者.

當需要查詢很多名稱的時候, 你可以使用 getpwall 來預載入一個字典, 如 Example 12-3 所示.

12.3.0.2. Example 12-3. 使用 pwd 模組

File: pwd-example-2.py import pwd import os # preload password dictionary _pwd = {} for info in pwd.getpwall(): _pwd[info[0]] = _pwd[info[2]] = info def userinfo(uid): # name or uid integer return _pwd[uid] print userinfo(os.getuid()) print userinfo("root") ('effbot', 'dsWjk8', 4711, 4711, 'eff-bot', '/home/effbot', '/bin/bosh') ('root', 'hs2giiw', 0, 0, 'root', '/root', '/bin/bash')

12.4. grp 模組

(只用於 Unix) grp 模組提供了一個到 Unix 使用者組/group ( /etc/group )資料庫的介面. getgrgid 函式返回給定使用者組 id 的相關資料(參見 Example 12-4 ),getgrnam 返回給定使用者組名稱的相關資料.

12.4.0.1. Example 12-4. 使用 grp 模組

File: grp-example-1.py import grp import os print grp.getgrgid(os.getgid()) print grp.getgrnam("wheel") ('effbot', '', 4711, ['effbot']) ('wheel', '', 10, ['root', 'effbot', 'gorbot', 'timbot'])

getgrall 函式返回包含所有可用使用者組資料庫入口的列表.

如果需要執行很多使用者組查詢, 你可以使用 getgrall 來把當前所有的使用者組複製到一個字典裡, 這可以節省一些時間. Example 12-5 中的 groupinfo 函式返回一個使用者組 id ( int )或是 一個使用者組名稱( str )的資訊.

12.4.0.2. Example 12-5. 使用 grp 模組快取使用者組資訊

File: grp-example-2.py import grp import os # preload password dictionary _grp = {} for info in grp.getgrall(): _grp[info[0]] = _grp[info[2]] = info def groupinfo(gid): # name or gid integer return _grp[gid] print groupinfo(os.getgid()) print groupinfo("wheel") ('effbot', '', 4711, ['effbot']) ('wheel', '', 10, ['root', 'effbot', 'gorbot', 'timbot'])

12.5. nis 模組

(ֻ���� Unix , ��ѡ) nis ģ���ṩ�� NIS ( Network Information Services , ������Ϣ���� ,��ҳ) ����Ľӿ�, �� Example 12-6��ʾ. �����ڴӿ��õ� NIS ��ݿ��л�����.

12.5.0.1. Example 12-6. ʹ�� nis ģ��

File: nis-example-1.py import nis import string print nis.cat("ypservers") print string.split(nis.match("bacon", "hosts.byname")) {'bacon.spam.egg': 'bacon.spam.egg'} ['194.18.155.250', 'bacon.spam.egg', 'bacon', 'spam-010']

12.6. curses 模組

(ֻ���� Unix ��ѡ) curses ģ���ṩ�˶��ı��ַ��ն˴��ڵĿ���, ��ʹ����һ�ֶ�����ն˵ķ���. �� Example 12-7 ��ʾ.

12.6.0.1. Example 12-7. ʹ�� curses ģ��

File: curses-example-1.py import curses text = [ "a very simple curses demo", "", "(press any key to exit)" ] # connect to the screen # ��ӵ���Ļ screen = curses.initscr() # setup keyboard # ���ü��� curses.noecho() # no keyboard echo curses.cbreak() # don't wait for newline # screen size # ��Ļ�ߴ� rows, columns = screen.getmaxyx() # draw a border around the screen # ��һ��߿� screen.border() # display centered text # ��ʾ���� y = (rows - len(text)) / 2 for line in text: screen.addstr(y, (columns-len(line))/2, line) y = y + 1 screen.getch() curses.endwin()

12.7. termios 模組

(只用於 Unix , 可選) termios 為 Unix 的終端控制裝置提供了一個介面. 它可用於控制終端通訊埠的大多方面.

Example 12-8 中, 該模組臨時關閉了鍵盤迴顯(由第三個標誌域的 ECHO 標誌控制).

12.7.0.1. Example 12-8. 使用 termios 模組

File: termios-example-1.py import termios, TERMIOS import sys fileno = sys.stdin.fileno() attr = termios.tcgetattr(fileno) orig = attr[:] print "attr =>", attr[:4] # flags # disable echo flag attr[3] = attr[3] & ~TERMIOS.ECHO try: termios.tcsetattr(fileno, TERMIOS.TCSADRAIN, attr) message = raw_input("enter secret message: ") print finally: # restore terminal settings termios.tcsetattr(fileno, TERMIOS.TCSADRAIN, orig) print "secret =>", repr(message) attr => [1280, 5, 189, 35387] enter secret message: secret => 'and now for something completely different'

12.8. tty 模組

(只用於 Unix) tty 模組包含一些用於處理 tty 裝置的工具函式. Example 12-9 將終端視窗切換為 "raw" 模式.

12.8.0.1. Example 12-9. 使用 tty 模組

File: tty-example-1.py import tty import os, sys fileno = sys.stdin.fileno() tty.setraw(fileno) print raw_input("raw input: ") tty.setcbreak(fileno) print raw_input("cbreak input: ") os.system("stty sane") # ... raw input: this is raw input cbreak input: this is cbreak input

12.9. resource 模組

(只用於 Unix , 可選) resource 模組用於查詢或修改當前系統資源限制設定. Example 12-10 展示瞭如何執行查詢操作, Example 12-11 展示瞭如何執行修改操作.

12.9.0.1. Example 12-10. 使用 resource 模組查詢當前設定

File: resource-example-1.py import resource print "usage stats", "=>", resource.getrusage(resource.RUSAGE_SELF) print "max cpu", "=>", resource.getrlimit(resource.RLIMIT_CPU) print "max data", "=>", resource.getrlimit(resource.RLIMIT_DATA) print "max processes", "=>", resource.getrlimit(resource.RLIMIT_NPROC) print "page size", "=>", resource.getpagesize() usage stats => (0.03, 0.02, 0, 0, 0, 0, 75, 168, 0, 0, 0, 0, 0, 0, 0, 0) max cpu => (2147483647, 2147483647) max data => (2147483647, 2147483647) max processes => (256, 256) page size => 4096

12.9.0.2. Example 12-11. 使用 resource 模組限制資源

File: resource-example-2.py import resource resource.setrlimit(resource.RLIMIT_CPU, (0, 1)) # pretend we're busy for i in range(1000): for j in range(1000): for k in range(1000): pass CPU time limit exceeded

12.10. syslog 模組

(只用於 Unix 可選) syslog 模組用於向系統日誌裝置傳送資訊( syslogd ). 這些資訊如何處理依不同的系統而定, 通常會被記錄在一個 log 檔案中, 例如/var/log/messages , /var/adm/syslog , 或者其他類似處理. (如果你找不到這個檔案, 請聯絡你的系統管理員). Example 12-12 展示了該模組的使用.

12.10.0.1. Example 12-12. 使用 syslog 模組

File: syslog-example-1.py import syslog import sys syslog.openlog(sys.argv[0]) syslog.syslog(syslog.LOG_NOTICE, "a log notice") syslog.syslog(syslog.LOG_NOTICE, "another log notice: %s" % "watch out!") syslog.closelog()

12.11. msvcrt 模組

(只用於 Windows/DOS ) msvcrt 模組用於訪問 Microsoft Visual C/C++ Runtime Library (MSVCRT) 中函式的方法.

Example 12-13 展示了 getch 函式, 它用於從命令列讀取一次按鍵操作.

12.11.0.1. Example 12-13. 使用 msvcrt 模組獲得按鍵值

File: msvcrt-example-1.py import msvcrt print "press 'escape' to quit..." while 1: char = msvcrt.getch() if char == chr(27): break print char, if char == chr(13): print press 'escape' to quit... h e l l o

kbhit 函式在按鍵時返回(這樣的捕獲操作不會讓 getch 阻塞), 如 Example 12-14 所示.

12.11.0.2. Example 12-14. 使用 msvcrt 模組接受鍵盤輸入

File: msvcrt-example-2.py import msvcrt import time print "press SPACE to enter the serial number" while not msvcrt.kbhit() or msvcrt.getch() != " ": # do something else print ".", time.sleep(0.1) print # clear the keyboard buffer # 清除鍵盤緩衝區 while msvcrt.kbhit(): msvcrt.getch() serial = raw_input("enter your serial number: ") print "serial number is", serial press SPACE to enter the serial number . . . . . . . . . . . . . . . . . . . . . . . . enter your serial number: 10 serial number is 10
譯註: 某翻譯在這裡評註道: 我能在 cmd 下執行. 用別的 IDLE 要不然卡住, 要不然接受不了鍵盤輸入. 原因未知. 這是因為 IDLE 啟動兩個 python 執行緒, 使用 socket 傳送資料, 獲得程式返回的.

locking 函式實現了 Windows 下的跨程式檔案鎖定, 如 Example 12-15 所示.

12.11.0.3. Example 12-15. 使用 msvcrt 模組鎖定檔案

File: msvcrt-example-3.py import msvcrt import os LK_UNLCK = 0 # unlock the file region 解鎖區域 LK_LOCK = 1 # lock the file region 鎖定檔案區域 LK_NBLCK = 2 # non-blocking lock 非阻塞檔案鎖 LK_RLCK = 3 # lock for writing 為寫入檔案提供鎖定 LK_NBRLCK = 4 # non-blocking lock for writing 為寫入檔案提供的非阻塞鎖定 FILE = "counter.txt" if not os.path.exists(FILE): file = open(FILE, "w") file.write("0") file.close() for i in range(20): file = open(FILE, "r+") # look from current position (0) to end of file msvcrt.locking(file.fileno(), LK_LOCK, os.path.getsize(FILE)) counter = int(file.readline()) + 1 file.seek(0) file.write(str(counter)) file.close() # unlocks the file print os.getpid(), "=>", counter time.sleep(0.1) 208 => 21 208 => 22 208 => 23 208 => 24 208 => 25 208 => 26

12.12. nt 模組

(非直接使用模組, 只用於 Windows ) nt 模組是 os 模組在 Windows 平臺下呼叫的執行模組. 幾乎沒有任何原因直接使用這個模組, 請使用 os 模組替代.Example 12-16 展示了它的使用.

12.12.0.1. Example 12-16. 使用 nt 模組

File: nt-example-1.py import nt # in real life, use os.listdir and os.stat instead! for file in nt.listdir("."): print file, nt.stat(file)[6] aifc-example-1.py 314 anydbm-example-1.py 259 array-example-1.py 48

12.13. _winreg 模組

(只用於 Windows , 2.0 中新增) _winreg 模組提供了訪問 Windows 登錄檔資料庫的一個基本介面. Example 12-17 展示了它的使用.

12.13.0.1. Example 12-17. 使用 _winreg 模組

File: winreg-example-1.py import _winreg explorer = _winreg.OpenKey( _winreg.HKEY_CURRENT_USER, "Software//Microsoft//Windows/CurrentVersion//Explorer" ) # list values owned by this registry key  # 列出該登錄檔鍵下的所有值 try: i = 0 while 1: name, value, type= _winreg.EnumValue(explorer, i) print repr(name), i += 1 except WindowsError: print value, type = _winreg.QueryValueEx(explorer, "Logon User Name") print print "user is", repr(value) 'Logon User Name' 'CleanShutdown' 'ShellState' 'Shutdown Setting' 'Reason Setting' 'FaultCount' 'FaultTime' 'IconUnderline'... user is u'Effbot'

12.14. posix 模組

(非直接使用模組, 只用於 Unix/POSIX ) posix 模組是 os 模組在 Unix 及其他 POSIX 系統下使用的實現模組. 一般只需要通過 os 模組訪問它即可. 如Example 12-18 所示.

12.14.0.1. Example 12-18. 使用 posix 模組

File: posix-example-1.py import posix for file in posix.listdir("."): print file, posix.stat(file)[6] aifc-example-1.py 314 anydbm-example-1.py 259 array-example-1.py 48

13. 執行支援模組

就是其他模組中用到的模組.


13.1. dospath 模組

dospath 模組(參見 Example 13-1 )提供了 DOS 平臺下的 os.path 功能. 你可以使用它在其他平臺處理 DOS 路徑.

13.1.0.1. Example 13-1. 使用 dospath 模組

File: dospath-example-1.py import dospath  file = "/my/little/pony" print "isabs", "=>", dospath.isabs(file) print "dirname", "=>", dospath.dirname(file) print "basename", "=>", dospath.basename(file) print "normpath", "=>", dospath.normpath(file) print "split", "=>", dospath.split(file) print "join", "=>", dospath.join(file, "zorba") isabs => 1 dirname => /my/little basename => pony normpath => /my/little/pony split => ('/my/little', 'pony') join => /my/little/pony/zorba

注意 Python 的 DOS 支援可以使用斜槓和反斜槓作為目錄分隔符.


13.2. macpath 模組

macpath 模組( 參見 Example 13-2 )提供了 Macintosh 平臺下的 os.path 功能. 你也可以使用它在其他平臺處理 Macintosh 路徑.

13.2.0.1. Example 13-2. 使用 macpath 模組

File: macpath-example-1.py import macpath file = "my:little:pony" print "isabs", "=>", macpath.isabs(file) print "dirname", "=>", macpath.dirname(file) print "basename", "=>", macpath.basename(file) print "normpath", "=>", macpath.normpath(file) print "split", "=>", macpath.split(file) print "join", "=>", macpath.join(file, "zorba") isabs => 1 dirname => my:little basename => pony normpath => my:little:pony split => ('my:little', 'pony') join => my:little:pony:zorba

13.3. ntpath 模組

ntpath 模組( 參見 Example 13-3 )提供了 Windows 平臺下的 os.path 功能. 你也可以使用它在其他平臺處理 Windows 路徑.

13.3.0.1. Example 13-3. 使用 ntpath 模組

File: ntpath-example-1.py import ntpath file = "/my/little/pony" print "isabs", "=>", ntpath.isabs(file) print "dirname", "=>", ntpath.dirname(file) print "basename", "=>", ntpath.basename(file) print "normpath", "=>", ntpath.normpath(file) print "split", "=>", ntpath.split(file) print "join", "=>", ntpath.join(file, "zorba") isabs => 1 dirname => /my/little basename => pony normpath => /my/little/pony split => ('/my/little', 'pony') join => /my/little/pony/zorba

注意該模組可以同時使用斜槓和反斜槓作為目錄分隔符.


13.4. posixpath 模組

posixpath 模組( 參見 Example 13-4 )提供了 Unix 和其他 POSIX 相容平臺下的 os.path 功能. 你也可以使用它在其他平臺處理 POSIX 路徑. 另外, 它也可以處理 URL .

13.4.0.1. Example 13-4. 使用 posixpath 模組

File: posixpath-example-1.py import posixpath file = "/my/little/pony" print "isabs", "=>", posixpath.isabs(file) print "dirname", "=>", posixpath.dirname(file) print "basename", "=>", posixpath.basename(file) print "normpath", "=>", posixpath.normpath(file) print "split", "=>", posixpath.split(file) print "join", "=>", posixpath.join(file, "zorba") isabs => 1 dirname => /my/little basename => pony normpath => /my/little/pony split => ('/my/little', 'pony') join => /my/little/pony/zorba

13.5. strop 模組

(已廢棄) strop 為 string 模組中的大多函式提供了底層 C 語言實現. string 模組會自動呼叫它, 所以一般你不需要直接使用它.

不過在匯入 Python 模組之前處理路徑的時候你可能會用到它. 如 Example 13-5 所示.

13.5.0.1. Example 13-5. 使用 strop 模組

File: strop-example-1.py import strop import sys # assuming we have an executable named ".../executable", add a # directory named ".../executable-extra" to the path if strop.lower(sys.executable)[-4:] == ".exe": extra = sys.executable[:-4] # windows else: extra = sys.executable sys.path.insert(0, extra + "-extra") import mymodule

在 Python 2.0 及以後版本中, 你應該使用字串方法代替 strop , 例如在上邊的程式碼中. 使用 "sys.executable.lower()" 替換 "strop.lower(sys.executable)" .


13.6. imp 模組

imp 模組包含的函式可以用於實現自定義的 import 行為. Example 13-6 過載了 import 語句, 實現了對模組來源的記錄功能.

13.6.0.1. Example 13-6. 使用 imp 模組

File: imp-example-1.py import imp import sys def my_import(name, globals=None, locals=None, fromlist=None): try: module = sys.modules[name] # already imported? except KeyError: file, pathname, description = imp.find_module(name) print "import", name, "from", pathname, description module = imp.load_module(name, file, pathname, description) return module import _ _builtin_ _ _ _builtin_ _._ _import_ _ = my_import import xmllib import xmllib from /python/lib/xmllib.py ('.py', 'r', 1) import re from /python/lib/re.py ('.py', 'r', 1) import sre from /python/lib/sre.py ('.py', 'r', 1) import sre_compile from /python/lib/sre_compile.py ('.py', 'r', 1) import _sre from /python/_sre.pyd ('.pyd', 'rb', 3)

注意這裡的匯入功能不支援包. 具體實現請參閱 knee 模組的原始碼.


13.7. new 模組

new 模組是一個底層的模組, 你可以使用它來建立不同的內建物件, 例如類物件, 函式物件, 以及其他由 Python 執行時系統建立的型別. Example 13-7 展示了該模組的使用.

如果你使用的是 1.5.2 版本 , 那麼你有可能需要重新編譯 Python 來使用這個模組, 在預設情況下並不是所有平臺都有這個模組. 在 2.0 及以後版本中, 不需要這麼做.

13.7.0.1. Example 13-7. 使用 new 模組

File: new-example-1.py import new class Sample: a = "default" def _ _init_ _(self): self.a = "initialised" def _ _repr_ _(self): return self.a # # create instances a = Sample() print "normal", "=>", a b = new.instance(Sample, {}) print "new.instance", "=>", b b._ _init_ _() print "after _ _init_ _", "=>", b c = new.instance(Sample, {"a": "assigned"}) print "new.instance w. dictionary", "=>", c normal => initialised new.instance => default after _ _init_ _ => initialised new.instance w. dictionary => assigned

13.8. pre 模組

(已廢棄) pre 模組是 1.5.2 中 re 模組呼叫的實現功能模組. 在當前版本中已廢棄. Example 13-8 展示了它的使用.

13.8.0.1. Example 13-8. 使用 pre 模組

File: pre-example-1.py import pre p = pre.compile("[Python]+") print p.findall("Python is not that bad") ['Python', 'not', 'th', 't']

13.9. sre 模組

(功能實現模組, 已宣告不支援) sre 模組是 re 模組的底層實現. 一般沒必要直接使用它, 而且以後版本將不會支援它. Example 13-9 展示了它的使用.

13.9.0.1. Example 13-9. 使用 sre 模組

File: sre-example-1.py import sre text = "The Bookshop Sketch" # a single character m = sre.match(".", text) if m: print repr("."), "=>", repr(m.group(0)) # and so on, for all 're' examples... '.' => 'T'

13.10. py_compile 模組

py_compile 模組用於將 Python 模組編譯為位元組程式碼. 它和 Python 的 import 語句行為類似, 不過它接受檔名而不是模組名作為引數. 使用方法如Example 13-10 所示.

13.10.0.1. Example 13-10. 使用 py_compile 模組

File: py-compile-example-1.py import py_compile # explicitly compile this module py_compile.compile("py-compile-example-1.py")

compileall 模組可以把一個目錄樹下的所有 Python 檔案編譯為位元組程式碼.


13.11. compileall 模組

compileall 模組用於將給定目錄下(以及 Python path )的所有 Python 指令碼編譯為位元組程式碼. 它也可以作為可執行指令碼使用(在 Unix 系統下, Python 安裝時會自動呼叫執行它). 用法參見 Example 13-11 .

13.11.0.1. Example 13-11. 使用 compileall 模組編譯目錄中的所有指令碼

File: compileall-example-1.py import compileall print "This may take a while!" compileall.compile_dir(".", force=1) This may take a while! Listing . ... Compiling ./SimpleAsyncHTTP.py ... Compiling ./aifc-example-1.py ... Compiling ./anydbm-example-1.py ... ...

13.12. ihooks 模組

ihooks 模組為替換匯入提供了一個框架. 這允許多個匯入機制共存. 使用方法參見 Example 13-12 .

13.12.0.1. Example 13-12. 使用 ihooks 模組

File: ihooks-example-1.py import ihooks, imp, os def import_from(filename): "Import module from a named file" loader = ihooks.BasicModuleLoader() path, file = os.path.split(filename) name, ext = os.path.splitext(file) m = loader.find_module_in_dir(name, path) if not m: raise ImportError, name m = loader.load_module(name, m) return m colorsys = import_from("/python/lib/colorsys.py") print colorsys <module 'colorsys' from '/python/lib/colorsys.py'>

13.13. linecache 模組

linecache 模組用於從模組原始檔中讀取程式碼. 它會快取最近訪問的模組 (整個原始檔). 如 Example 13-13 .

13.13.0.1. Example 13-13. 使用 linecache 模組

File: linecache-example-1.py import linecache print linecache.getline("linecache-example-1.py", 5) print linecache.getline("linecache-example-1.py", 5)

traceback 模組使用這個模組實現了對匯入操作的跟蹤.


13.14. macurl2path 模組

(功能實現模組) macurl2path 模組用於 URL 和 Macintosh 檔名 的相互對映. 一般沒有必要直接使用它, 請使用 urllib 中的機制. 它的用法參見Example 13-14 .

13.14.0.1. Example 13-14. 使用 macurl2path 模組

File: macurl2path-example-1.py import macurl2path file = ":my:little:pony" print macurl2path.pathname2url(file) print macurl2path.url2pathname(macurl2path.pathname2url(file)) my/little/pony :my:little:pony

13.15. nturl2path 模組

(功能實現模組) nturl2path 模組用於 URL 和 Windows 檔名的 相互對映. 用法參見 Example 13-15 .

13.15.0.1. Example 13-15. 使用 nturl2path 模組

File: nturl2path-example-1.py import nturl2path file = r"c:/my/little/pony" print nturl2path.pathname2url(file) print nturl2path.url2pathname(nturl2path.pathname2url(file)) ///C|/my/little/pony C:/my/little/pony

同樣地, 請通過 urllib 模組來訪問這些函式, 如 Example 13-16 所示.

13.15.0.2. Example 13-16. 通過 urllib 呼叫 nturl2path 模組

File: nturl2path-example-2.py import urllib file = r"c:/my/little/pony" print urllib.pathname2url(file) print urllib.url2pathname(urllib.pathname2url(file)) ///C|/my/little/pony C:/my/little/pony

13.16. tokenize 模組

tokenize 模組將一段 Python 原始檔分割成不同的 token . 你可以在程式碼高亮工具中使用它.

在 Example 13-17 中, 我們分別列印出這些 token .

13.16.0.1. Example 13-17. 使用 tokenize 模組

File: tokenize-example-1.py import tokenize file = open("tokenize-example-1.py") def handle_token(type, token, (srow, scol), (erow, ecol), line): print "%d,%d-%d,%d:/t%s/t%s" % / (srow, scol, erow, ecol, tokenize.tok_name[type], repr(token)) tokenize.tokenize( file.readline, handle_token ) 1,0-1,6: NAME 'import' 1,7-1,15: NAME 'tokenize' 1,15-1,16: NEWLINE '/012' 2,0-2,1: NL '/012' 3,0-3,4: NAME 'file' 3,5-3,6: OP '=' 3,7-3,11: NAME 'open' 3,11-3,12: OP '(' 3,12-3,35: STRING '"tokenize-example-1.py"' 3,35-3,36: OP ')' 3,36-3,37: NEWLINE '/012' ...

注意這裡的 tokenize 函式接受兩個可呼叫物件作為引數: 前一個用於獲取新的程式碼行, 第二個用於在獲得每個 token 時呼叫.


13.17. keyword 模組

keyword 模組(參見 Example 13-18 )有一個包含當前 Python 版本所使用的關鍵字的列表. 它還提供了一個字典, 以關鍵字作為 key , 以一個描述性函式作為 value , 它可用於檢查 給定單詞是否是 Python 關鍵字.

13.17.0.1. Example 13-18. 使用 keyword 模組

File: keyword-example-1.py import keyword name = raw_input("Enter module name: ") if keyword.iskeyword(name): print name, "is a reserved word." print "here's a complete list of reserved words:" print keyword.kwlist Enter module name: assert assert is a reserved word. here's a complete list of reserved words: ['and', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while']

13.18. parser 模組

(可選) parser 模組提供了一個到 Python 內建語法分析器和編譯器的介面.

Example 13-19 將一個簡單的表示式編譯為一個抽象語法樹( abstract syntax tree , AST ), 然後將 AST 轉換為一個巢狀列表, 轉儲樹 ( 其中每個節點包含一個語法符號或者是一個 token )中的內容, 將所有數字加上 1 , 最後將列表轉回一個程式碼物件. 至少我認為它是這麼做的.

13.18.0.1. Example 13-19. 使用 parser 模組

File: parser-example-1.py import parser import symbol, token def dump_and_modify(node): name = symbol.sym_name.get(node[0]) if name is None: name = token.tok_name.get(node[0]) print name, for i in range(1, len(node)): item = node[i] if type(item) is type([]): dump_and_modify(item) else: print repr(item) if name == "NUMBER": # increment all numbers! node[i] = repr(int(item)+1) ast = parser.expr("1 + 3") list = ast.tolist() dump_and_modify(list) ast = parser.sequence2ast(list) print eval(parser.compileast(ast)) eval_input testlist test and_test not_test comparison expr xor_expr and_expr shift_expr arith_expr term factor power atom NUMBER '1' PLUS '+' term factor power atom NUMBER '3' NEWLINE '' ENDMARKER '' 6

13.19. symbol 模組

symbol 模組包含 Python 語法中的非終止符號. 可能只有你涉及 parser 模組的時候用到它. 用法參見 Example 13-20 .

13.19.0.1. Example 13-20. 使用 symbol 模組

File: symbol-example-1.py import symbol print "print", symbol.print_stmt print "return", symbol.return_stmt print 268 return 274

13.20. token 模組

token 模組包含標準 Python tokenizer 所使用的 token 標記. 如 Example 13-21 所示.

13.20.0.1. Example 13-21. 使用 token 模組

File: token-example-1.py import token print "NUMBER", token.NUMBER print "PLUS", token.STAR print "STRING", token.STRING NUMBER 2 PLUS 16 STRING 3

14. 其他模組


14.1. 概覽

本章描述的是一些並不怎麼常見的模組. 一些是很實用的, 另些是已經廢棄的模組.


14.2. pyclbr 模組

pyclbr 模組包含一個基本的 Python 類解析器, 如 Example 14-1 所示.

版本 1.5.2 中, 改模組只包含一個 readmodule 函式, 解析給定模組, 返回一個模組所有頂層類組成的列表.

14.2.0.1. Example 14-1. 使用 pyclbr 模組

File: pyclbr-example-1.py import pyclbr mod = pyclbr.readmodule("cgi") for k, v in mod.items(): print k, v MiniFieldStorage <pyclbr.Class instance at 7873b0> InterpFormContentDict <pyclbr.Class instance at 79bd00> FieldStorage <pyclbr.Class instance at 790e20> SvFormContentDict <pyclbr.Class instance at 79b5e0> StringIO <pyclbr.Class instance at 77dd90> FormContent <pyclbr.Class instance at 79bd60> FormContentDict <pyclbr.Class instance at 79a9c0>

2.0 及以後版本中, 新增了另個介面 readmodule_ex , 它還會讀取全域性函式. 如 Example 14-2 所示.

14.2.0.2. Example 14-2. 使用 pyclbr 模組讀取類和函式

File: pyclbr-example-3.py import pyclbr # 2.0 and later mod = pyclbr.readmodule_ex("cgi") for k, v in mod.items(): print k, vMiniFieldStorage <pyclbr.Class instance at 00905D2C> parse_header <pyclbr.Function instance at 00905BD4> test <pyclbr.Function instance at 00906FBC> print_environ_usage <pyclbr.Function instance at 00907C94> parse_multipart <pyclbr.Function instance at 00905294> FormContentDict <pyclbr.Class instance at 008D3494> initlog <pyclbr.Function instance at 00904AAC> parse <pyclbr.Function instance at 00904EFC> StringIO <pyclbr.Class instance at 00903EAC> SvFormContentDict <pyclbr.Class instance at 00906824> ...

訪問類例項的屬性可以獲得關於類的更多資訊, 如 Example 14-3 所示.

14.2.0.3. Example 14-3. 使用 pyclbr 模組

File: pyclbr-example-2.py import pyclbr import string mod = pyclbr.readmodule("cgi") def dump(c): # print class header s = "class " + c.name if c.super: s = s + "(" + string.join(map(lambda v: v.name, c.super), ", ") + ")" print s + ":" # print method names, sorted by line number methods = c.methods.items() methods.sort(lambda a, b: cmp(a[1], b[1])) for method, lineno in methods: print " def " + method print for k, v in mod.items(): dump(v) class MiniFieldStorage: def _ _init_ _ def _ _repr_ _ class InterpFormContentDict(SvFormContentDict): def _ _getitem_ _ def values def items ...

14.3. filecmp 模組

( 2.0 新增) filecmp 模組用於比較檔案和目錄, 如 Example 14-4 所示.

14.3.0.1. Example 14-4. 使用 filecmp 模組

File: filecmp-example-1.py import filecmp if filecmp.cmp("samples/sample.au", "samples/sample.wav"): print "files are identical" else: print "files differ!" # files differ!

1.5.2 以及先前版本中, 你可以使用 cmp 和 dircmp 模組代替.


14.4. cmd 模組

cmd 模組為命令列介面( command-line interfaces , CLI )提供了一個簡單的框架. 它被用在 pdb 模組中, 當然你也可以在自己的程式中使用它, 如 Example 14-5 所示.

你只需要繼承 Cmd 類, 定義 do 和 help 方法. 基類會自動地將這些方法轉換為對應命令.

14.4.0.1. Example 14-5. 使用 cmd 模組

File: cmd-example-1.py import cmd import string, sys class CLI(cmd.Cmd): def _ _init_ _(self): cmd.Cmd._ _init_ _(self) self.prompt = '> ' def do_hello(self, arg): print "hello again", arg, "!" def help_hello(self): print "syntax: hello [message]", print "-- prints a hello message" def do_quit(self, arg): sys.exit(1) def help_quit(self): print "syntax: quit", print "-- terminates the application" # shortcuts do_q = do_quit # # try it out cli = CLI() cli.cmdloop() > help Documented commands (type help <topic>): ======================================== hello quit Undocumented commands: ====================== help q > hello world hello again world ! > q

14.5. rexec 模組

Feather 注: 版本 2.3 時取消了改模組的支援, 具體原因請參閱 : http://www.amk.ca/python/howto/rexec/ 和 http://mail.python.org/pipermail/python-dev/2002-December/031160.html

解決方法請參閱: http://mail.python.org/pipermail/python-list/2003-November/234581.html

rexec 模組提供了在限制環境下的 exec , eval , 以及 import 語句, 如 Example 14-6 所示. 在這個環境下, 所有可能對機器造成威脅的函式都不可用.

14.5.0.1. Example 14-6. 使用 rexec 模組

File: rexec-example-1.py import rexec r = rexec.RExec() print r.r_eval("1+2+3") print r.r_eval("_ _import_ _('os').remove('file')") 6 Traceback (innermost last): File "rexec-example-1.py", line 5, in ? print r.r_eval("_ _import_ _('os').remove('file')") File "/usr/local/lib/python1.5/rexec.py", line 257, in r_eval return eval(code, m._ _dict_ _) File "<string>", line 0, in ? AttributeError: remove

14.6. Bastion 模組

Feather 注: 版本 2.3 時取消了改模組的支援, 具體原因請參閱 : http://www.amk.ca/python/howto/rexec/ 和 http://mail.python.org/pipermail/python-dev/2003-January/031848.html

Bastion 模組, 允許你控制給定物件如何使用, 如 Example 14-7 所示. 你可以通過它把物件從未限制部分傳遞到限制部分.

預設情況下, 所有的例項變數都是隱藏的, 所有的方法以下劃線開頭.

14.6.0.1. Example 14-7. 使用 Bastion 模組

File: bastion-example-1.py import Bastion class Sample: value = 0 def _set(self, value): self.value = value def setvalue(self, value): if 10 < value <= 20: self._set(value) else: raise ValueError, "illegal value" def getvalue(self): return self.value # # try it s = Sample() s._set(100) # cheat print s.getvalue() s = Bastion.Bastion(Sample()) s._set(100) # attempt to cheat print s.getvalue() 100 Traceback (innermost last): ... AttributeError: _set

你可以控制釋出哪個函式. 在 Example 14- 中, 內部方法可以從外部呼叫, 但 getvalue 不再起作用.

14.6.0.2. Example 14-8. 使用 Bastion 模組處理非標準過濾器

File: bastion-example-2.py import Bastion class Sample: value = 0 def _set(self, value): self.value = value def setvalue(self, value): if 10 < value <= 20: self._set(value) else: raise ValueError, "illegal value" def getvalue(self): return self.value # # try it def is_public(name): return name[:3] != "get" s = Bastion.Bastion(Sample(), is_public) s._set(100) # this works print s.getvalue() # but not this 100 Traceback (innermost last): ... AttributeError: getvalue

14.7. readline 模組

(可選) readline 模組使用 GNU readline 庫(或相容庫)實現了 Unix 下增強的輸入編輯支援. 如 Example 14-9 所示.

該模組提供了增強的命令列編輯功能, 例如命令列歷史等. 它還增強了 input 和 raw_input 函式.

14.7.0.1. Example 14-9. 使用 readline 模組

File: readline-example-1.py import readline # activate readline editing

14.8. rlcompleter 模組

(可選, 只用於 Unix ) rlcompleter 模組為 readline 模組提供了單詞自動完成功能.

匯入該模組就可以啟動自動完成功能. 預設情況下完成函式被繫結在了 Esc 鍵上. 按兩次 Esc 鍵就可以自動完成當前單詞. 你可以使用下面的程式碼修改所繫結的鍵:

import readline readline.parse_and_bind("tab: complete")

Example 14-10 展示瞭如何在程式中使用自動完成函式.

14.8.0.1. Example 14-10. 使用 rlcompleter 模組展開名字

File: rlcompleter-example-1.py import rlcompleter import sys completer = rlcompleter.Completer() for phrase in "co", "sys.p", "is": print phrase, "=>", # emulate readline completion handler try: for index in xrange(sys.maxint): term = completer.complete(phrase, index) if term is None: break print term, except: pass print co => continue compile complex coerce completer sys.p => sys.path sys.platform sys.prefix is => is isinstance issubclass

14.9. statvfs 模組

statvfs 模組包含一些與 os.statvfs (可選)函式配合使用的常量和函式, 該函式會返回檔案系統的相關資訊. 如 Example 14-11 所示.

14.9.0.1. Example 14-11. 使用 statvfs 模組

File: statvfs-example-1.py import statvfs import os st = os.statvfs(".") print "preferred block size", "=>", st[statvfs.F_BSIZE] print "fundamental block size", "=>", st[statvfs.F_FRSIZE] print "total blocks", "=>", st[statvfs.F_BLOCKS] print "total free blocks", "=>", st[statvfs.F_BFREE] print "available blocks", "=>", st[statvfs.F_BAVAIL] print "total file nodes", "=>", st[statvfs.F_FILES] print "total free nodes", "=>", st[statvfs.F_FFREE] print "available nodes", "=>", st[statvfs.F_FAVAIL] print "max file name length", "=>", st[statvfs.F_NAMEMAX] preferred block size => 8192 fundamental block size => 1024 total blocks => 749443 total free blocks => 110442 available blocks => 35497 total file nodes => 92158 total free nodes => 68164 available nodes => 68164 max file name length => 255

14.10. calendar 模組

calendar 模組是 Unix cal 命令的 Python 實現. 它可以將給定年份/月份的日曆輸出到標準輸出裝置上.

prmonth(year, month) 列印給定月份的日曆, 如 Example 14-12 所示.

14.10.0.1. Example 14-12. 使用 calendar 模組

File: calendar-example-1.py import calendar calendar.prmonth(1999, 12) December 1999 Mo Tu We Th Fr Sa Su 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

prcal(year) 列印給定年份的日曆, 如 Example 14-13 所示.

14.10.0.2. Example 14-13. 使用 calendar 模組

File: calendar-example-2.py import calendar calendar.prcal(2000) 2000 January February March Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su 1 2 1 2 3 4 5 6 1 2 3 4 5 3 4 5 6 7 8 9 7 8 9 10 11 12 13 6 7 8 9 10 11 12 10 11 12 13 14 15 16 14 15 16 17 18 19 20 13 14 15 16 17 18 19 17 18 19 20 21 22 23 21 22 23 24 25 26 27 20 21 22 23 24 25 26 24 25 26 27 28 29 30 28 29 27 28 29 30 31 31 April May June Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su 1 2 1 2 3 4 5 6 7 1 2 3 4 3 4 5 6 7 8 9 8 9 10 11 12 13 14 5 6 7 8 9 10 11 10 11 12 13 14 15 16 15 16 17 18 19 20 21 12 13 14 15 16 17 18 17 18 19 20 21 22 23 22 23 24 25 26 27 28 19 20 21 22 23 24 25 24 25 26 27 28 29 30 29 30 31 26 27 28 29 30 July August September Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su 1 2 1 2 3 4 5 6 1 2 3 3 4 5 6 7 8 9 7 8 9 10 11 12 13 4 5 6 7 8 9 10 10 11 12 13 14 15 16 14 15 16 17 18 19 20 11 12 13 14 15 16 17 17 18 19 20 21 22 23 21 22 23 24 25 26 27 18 19 20 21 22 23 24 24 25 26 27 28 29 30 28 29 30 31 25 26 27 28 29 30 31 October November December Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su 1 1 2 3 4 5 1 2 3 2 3 4 5 6 7 8 6 7 8 9 10 11 12 4 5 6 7 8 9 10 9 10 11 12 13 14 15 13 14 15 16 17 18 19 11 12 13 14 15 16 17 16 17 18 19 20 21 22 20 21 22 23 24 25 26 18 19 20 21 22 23 24 23 24 25 26 27 28 29 27 28 29 30 25 26 27 28 29 30 31 30 31

注意這裡的日曆是按照歐洲習慣列印的, 也就是說星期一是一個星期的第一天, 其他情況需要請參考模組中的幾個類. (和我們們一樣, 不用管了)

該模組中的其他類或函式可以幫助你輸出自己需要的格式.


14.11. sched 模組

sched 模組為非執行緒環境提供了一個簡單的計劃任務模式. 如 Example 14-14 所示.

14.11.0.1. Example 14-14. 使用 sched 模組

File: sched-example-1.py import sched import time, sys scheduler = sched.scheduler(time.time, time.sleep) # add a few operations to the queue scheduler.enter(0.5, 100, sys.stdout.write, ("one/n",)) scheduler.enter(1.0, 300, sys.stdout.write, ("three/n",)) scheduler.enter(1.0, 200, sys.stdout.write, ("two/n",)) scheduler.run() one two three

14.12. statcache 模組

statcache 模組提供了訪問檔案相關資訊的相關函式. 它是 os.stat 的擴充套件模組, 而且它會快取收集到的資訊. 如 Example 14-15 所示.

2.2 後該模組被廢棄, 請使用 os.stat() 函式代替, 原因很簡單, 它導致了更復雜的快取管理, 反而降低了效能.

14.12.0.1. Example 14-15. 使用 statcache 模組

File: statcache-example-1.py import statcache import os, stat, time now = time.time() for i in range(1000): st = os.stat("samples/sample.txt") print "os.stat", "=>", time.time() - now now = time.time() for i in range(1000): st = statcache.stat("samples/sample.txt") print "statcache.stat", "=>", time.time() - now print "mode", "=>", oct(stat.S_IMODE(st[stat.ST_MODE])) print "size", "=>", st[stat.ST_SIZE] print "last modified", "=>", time.ctime(st[stat.ST_MTIME]) os.stat => 0.371000051498 statcache.stat => 0.0199999809265 mode => 0666 size => 305 last modified => Sun Oct 10 18:39:37 1999

14.13. grep 模組

grep 模組提供了在文字檔案中搜尋字串的另種方法, 如 Example 14-16 所示.

版本 2.1 時被宣告不支援, 及就是說, 當前版本已經無法使用該模組.

14.13.0.1. Example 14-16. 使用 grep 模組

File: grep-example-1.py import grep import glob grep.grep("/<rather/>", glob.glob("samples/*.txt")) # 4: indentation, rather than delimiters, might become

14.14. dircache 模組

(已經廢棄) 與 statcache 類似, 該模組是 os.listdir 函式的一個擴充套件, 提供了快取支援, 可能因為同樣的原因被廢棄吧~ MUHAHAHAHAHA~~~~ . 請使用 os.listdir 代替. 如 Example 14-17 所示.

14.14.0.1. Example 14-17. 使用 dircache 模組

File: dircache-example-1.py import dircache import os, time #  # test cached version t0 = time.clock() for i in range(100): dircache.listdir(os.sep) print "cached", time.clock() - t0 #  # test standard version t0 = time.clock() for i in range(100): os.listdir(os.sep) print "standard", time.clock() - t0 cached 0.0664509964968 standard 0.5560845807

14.15. dircmp 模組

(已廢棄, 只用於 1.5.2) dircmp 模組用於比較兩個目錄的內容, 如 Example 14-18 所示.

14.15.0.1. Example 14-18. 使用 dircmp 模組

File: dircmp-example-1.py import dircmp d = dircmp.dircmp() d.new("samples", "oldsamples") d.run() d.report() diff samples oldsamples Only in samples : ['sample.aiff', 'sample.au', 'sample.wav'] Identical files : ['sample.gif', 'sample.gz', 'sample.jpg', ...]

Python 2.0 後, 該模組被 filecmp 替換.


14.16. cmp 模組

(已廢棄, 只用於 1.5.2) cmp 模組用於比較兩個檔案, 如 Example 14-19 所示.

14.16.0.1. Example 14-19. 使用 cmp 模組

File: cmp-example-1.py import cmp if cmp.cmp("samples/sample.au", "samples/sample.wav"): print "files are identical" else: print "files differ!" files differ!

Python 2.0 後, 該模組被 filecmp 替換.


14.17. cmpcache 模組

(已廢棄, 只用於 1.5.2) cmpcache 模組用於比較兩個檔案. 它是 cmp 模組的擴充套件, 提供了快取支援. 如 Example 14-20 所示.

14.17.0.1. Example 14-20. 使用 cmpcache 模組

File: cmpcache-example-1.py import cmpcache if cmpcache.cmp("samples/sample.au", "samples/sample.wav"): print "files are identical" else: print "files differ!" files differ!

Python 2.0 後, 該模組被 filecmp 替換.

但 filecmp 已經不提供快取支援.


14.18. util 模組

(已廢棄, 只用於 1.5.2) util 模組提供了常見操作的封裝函式. 新程式碼可以使用如 Examples 14-21 到 14-23 的實現方法.

Example 14-21 展示了 remove(sequence, item) 函式.

14.18.0.1. Example 14-21. 實現 util 模組的 remove 函式

File: util-example-1.py def remove(sequence, item): if item in sequence: sequence.remove(item)

Example 14-22 展示了 readfile(filename) => string 函式.

14.18.0.2. Example 14-22. 實現 util 模組的 readfile 函式

File: util-example-2.py def readfile(filename): file = open(filename, "r") return file.read()

Example 14-23 展示了 `readopenfile(file) => string 函式.

14.18.0.3. Example 14-23. 實現 util 模組的 readopenfile 函式

File: util-example-3.py def readopenfile(file): return file.read()

14.19. soundex 模組

(已廢棄, 只用於 1.5.2) soundex 實現了一個簡單的 hash 演算法, 基於英文發音將單詞轉換為 6 個字元的字串.

版本 2.0 後, 該模組已從標準庫中刪除.

get_soundex(word) 返回給定單詞的 soundex 字串. sound_similar(word1, word2) 判斷兩個單詞的 soundex 是否相同. 一般說來發音相似的單詞有相同的 soundex . 如 Example 14-24 所示.

14.19.0.1. Example 14-24. 使用 soundex 模組

File: soundex-example-1.py import soundex a = "fredrik" b = "friedrich" print soundex.get_soundex(a), soundex.get_soundex(b) print soundex.sound_similar(a, b) F63620 F63620 1

14.20. timing 模組

(已廢棄, 只用於 Unix ) timing 用於監控 Python 程式的執行時間. 如 Example 14-25 所示.

14.20.0.1. Example 14-25. 使用 timing 模組

File: timing-example-1.py import timing import time def procedure(): time.sleep(1.234) timing.start() procedure() timing.finish() print "seconds:", timing.seconds() print "milliseconds:", timing.milli() print "microseconds:", timing.micro()seconds: 1 milliseconds: 1239 microseconds: 1239999

你可以按照 Example 14-26 中的方法用 time 模組實現 timing 模組的功能.

14.20.0.2. Example 14-26. 模擬 timing 模組

File: timing-example-2.py import time t0 = t1 = 0 def start(): global t0 t0 = time.time() def finish(): global t1 t1 = time.time() def seconds(): return int(t1 - t0) def milli(): return int((t1 - t0) * 1000) def micro(): return int((t1 - t0) * 1000000)

time.clock() 可以替換 time.time() 獲得 CPU 時間.


14.21. posixfile 模組

(已廢棄, 只用於 Unix ) posixfile 提供了一個類檔案的物件( file-like object ), 實現了檔案鎖定的支援. 如 Example 14-27 所示. 新程式請使用 fcntl 模組代替.

14.21.0.1. Example 14-27. 使用 posixfile 模組

File: posixfile-example-1.py import posixfile import string filename = "counter.txt" try: # open for update file = posixfile.open(filename, "r+") counter = int(file.read(6)) + 1 except IOError: # create it file = posixfile.open(filename, "w") counter = 0 file.lock("w|", 6) file.seek(0) # rewind file.write("%06d" % counter) file.close() # releases lock

14.22. bisect 模組

bisect 模組用於向排序後的序列插入物件.

insort(sequence, item) 將條目插入到序列中, 並且保證序列的排序. 序列可以是任意實現了 _ _getitem_ _ 和 insert 方法的序列物件. 如Example 14-28 所示.

14.22.0.1. Example 14-28. 使用 bisect 模組向列表插入條目

File: bisect-example-1.py import bisect list = [10, 20, 30] bisect.insort(list, 25) bisect.insort(list, 15) print list [10, 15, 20, 25, 30]

bisect(sequence, item) => index 返回條目插入後的索引值, 不對序列做任何修改. 如 Example 14-29 所示.

14.22.0.2. Example 14-29. 使用 bisect 模組獲得插入點位置

File: bisect-example-2.py import bisect list = [10, 20, 30] print list print bisect.bisect(list, 25) print bisect.bisect(list, 15) [10, 20, 30] 2 1

14.23. knee 模組

knee 模組用於 Python 1.5 中匯入包( package import )的實現. 當然 Python 直譯器已經支援了這個, 所以這個模組幾乎沒有什麼作用, 不過你可以看看它的程式碼, 明白這一切是怎麼完成的.

程式碼請參見 Python-X.tgz/Python-2.4.4/Demo/imputil/knee.py

當然, 你可以匯入該模組,如 Example 14-30 所示.

14.23.0.1. Example 14-30. 使用 knee 模組

File: knee-example-1.py import knee # that's all, folks!

14.24. tzparse 模組

(已廢棄) tzparse 模組用於解析時區標誌( time zone specification ). 匯入時它會自動分析 TZ 環境變數. 如 Example 14-31 所示.

14.24.0.1. Example 14-31. 使用 tzparse 模組

File: tzparse-example-1.py import os if not os.environ.has_key("TZ"): # set it to something... os.environ["TZ"] = "EST+5EDT;100/2,300/2" # importing this module will parse the TZ variable import tzparse print "tzparams", "=>", tzparse.tzparams print "timezone", "=>", tzparse.timezone print "altzone", "=>", tzparse.altzone print "daylight", "=>", tzparse.daylight print "tzname", "=>", tzparse.tzname tzparams => ('EST', 5, 'EDT', 100, 2, 300, 2) timezone => 18000 altzone => 14400 daylight => 1 tzname => ('EST', 'EDT')

除了這些變數之外, 該模組還提供了一些用於時間計算的函式.


14.25. regex 模組

(已廢棄) regex 模組是舊版本的(1.5 前)正規表示式模組, 用法如 Example 14-32 所示. 新程式碼請使用 re 模組實現.

注意在 Python 1.5.2 中 regex 比 re 模組要快. 但在新版本中 re 模組更快.

14.25.0.1. Example 14-32. 使用 regex 模組

File: regex-example-1.py import regex text = "Man's crisis of identity in the latter half of the 20th century" p = regex.compile("latter") # literal print p.match(text) print p.search(text), repr(p.group(0)) p = regex.compile("[0-9]+") # number print p.search(text), repr(p.group(0)) p = regex.compile("/</w/w/>") # two-letter word print p.search(text), repr(p.group(0)) p = regex.compile("/w+$") # word at the end print p.search(text), repr(p.group(0)) -1 32 'latter' 51 '20' 13 'of' 56 'century'

14.26. regsub 模組

(已廢棄) regsub 模組提供了基於正規表示式的字串替換操作. 用法如 Example 14-33 所示. 新程式碼請使用 re 模組中的 replace 函式代替.

14.26.0.1. Example 14-33. 使用 regsub 模組

File: regsub-example-1.py import regsub text = "Well, there's spam, egg, sausage, and spam." print regsub.sub("spam", "ham", text) # just the first print regsub.gsub("spam", "bacon", text) # all of them Well, there's ham, egg, sausage, and spam. Well, there's bacon, egg, sausage, and bacon.

14.27. reconvert 模組

(已廢棄) reconvert 提供了舊樣式正規表示式( regex 模組中使用)到新樣式( re 模組)的轉換工具. 如 Example 14-34 所示. 它也可以作為一個命令列工具.

14.27.0.1. Example 14-34. 使用 reconvert 模組

File: reconvert-example-1.py import reconvert for pattern in "abcd", "a/(b*c/)d", "/</w+/>": print pattern, "=>", reconvert.convert(pattern) abcd => abcd a/(b*c/)d => a(b*c)d /</w+/> => /b/w+/b

14.28. regex_syntax 模組

(已廢棄) regex_syntax 模組用於改變正規表示式的模式, 如 Example 14-35 所示.

14.28.0.1. Example 14-35. 使用 regex_syntax 模組

File: regex-syntax-example-1.py import regex_syntax import regex def compile(pattern, syntax): syntax = regex.set_syntax(syntax) try: pattern = regex.compile(pattern) finally: # restore original syntax regex.set_syntax(syntax) return pattern def compile_awk(pattern): return compile(pattern, regex_syntax.RE_SYNTAX_AWK) def compile_grep(pattern): return compile(pattern, regex_syntax.RE_SYNTAX_GREP) def compile_emacs(pattern): return compile(pattern, regex_syntax.RE_SYNTAX_EMACS)

14.29. find 模組

(已廢棄, 只用於 1.5.2) find 模組用於在給定目錄及其子目錄中查詢符合給定匹配模式的檔案, 如 Example 14-36 所示.

匹配模式的語法與 fnmatch 中相同.

14.29.0.1. Example 14-36. 使用 find 模組

File: find-example-1.py import find # find all JPEG files in or beneath the current directory for file in find.find("*.jpg", "."): print file ./samples/sample.jpg

15. Py 2.0 後新增模組


本章將在以後的時間裡慢慢完成, 更新.


16. 後記

相關文章