Python標準庫(非常經典的各種模塊介紹)


  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 報告, 請將它們發送到 [email protected]. 我將閱讀盡我所能閱讀所有的郵件, 但可能回覆不是那麼及時.

本書的相關更新內容以及其他信息請訪問 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: [email protected]
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 ([email protected])

250 pages

book
title = The Python Standard Library
email = [email protected]
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: <[email protected]>
Date: Tue, 14 Nov 2000 14:55:07 -0500
To: "Fredrik Lundh" <[email protected]>
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 = <[email protected]>
to = "Fredrik Lundh" <[email protected]>
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', '[email protected]')]

地址字段被解析爲 (實際名稱, 郵件地址) 這樣的元組. 數據字段被解析爲 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 = [email protected]
date = Fri, 15 Oct 1999 03:21:15 -0400
content-transfer-encoding = 7bit
from = "Fredrik Lundh" <[email protected]>
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 = <[email protected]>
received = (from [email protected])
by spam.egg (8.8.7/8.8.5) id CAA03202
for effbot; Fri, 15 Oct 1999 02:27:36 +0200
from = Fredrik Lundh <[email protected]>
date = Fri, 15 Oct 1999 12:35:36 +0200
to = [email protected]
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: [email protected] )
非商業用途, 轉載請保留作者信息. 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 = <[email protected]>
received = (from [email protected])
by spam.egg (8.8.7/8.8.5) id KAA09206
for mulder; Tue, 12 Oct 1999 10:08:47 +0200
from = Fredrik Lundh <[email protected]>
date = Tue, 12 Oct 1999 10:08:47 +0200
to = [email protected]

...


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 = <[email protected]>
to = [email protected]
date = Tue, 12 Oct 1999 10:16:19 +0200 (MET DST)
from = <[email protected]>
received = ([email protected]) by imap.algonet.se (8.8.8+Sun/8.6.12)
id KAA12177 for [email protected]; 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 = "[email protected]"
TO = "[email protected]"

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: [email protected]
To: [email protected]
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 = "[email protected]" # 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" <[email protected]>
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 = <[email protected]>
lines = 22
...
from = "Fredrik Lundh" <[email protected]>
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, v

MiniFieldStorage <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. 後記

http://blog.csdn.net/xiao_qiang_/article/details/3006801

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章