Python Standard Library

Python Standard Library


"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個例子.html

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 新聞組裏回答問題. 也許某我的發現一個模塊正是他想要的, 可是殊不知道如何使用它. 也許某我的爲他的任務挑選的不合適的模塊. 也許某我的已經厭 倦了發明新輪子. 大多時候, 一個簡短的例子要比一份手冊文檔更有幫助.node

本書是超過3,000個新聞組討論的精華部分, 固然也有不少的新腳本, 爲了涵蓋標準庫的每一個角落.python

我盡力使得每一個腳本都易於理解, 易於重用代碼. 我有意縮短註釋的長度, 若是你想更深刻地 瞭解背景, 那麼你能夠參閱每一個 Python 發佈中的參考手冊. 本書的重要之處在於範例代碼.react

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

本書的相關更新內容以及其餘信息請訪問 http://www.pythonware.com/people/fredrik/librarybook.htmios

爲何沒有Tkinter?git

本書涵蓋了整個標準庫, 除了(可選的)Tkinter ui(user-interface : 用戶界面) 庫. 有不少緣由, 更可能是由於時間, 本書的空間, 以及我正在寫另外一本關於 Tkinter 的書.程序員

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

產品細節正則表達式

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

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

感謝 Lenny Muellner, 他幫助我把SGML文件轉變爲大家如今所看到的這本書, 以及Christien Shangraw, 他將那些代碼文件集合起來作成了隨書CD (能夠在http://examples.oreilly.com/pythonsl 找到, 居然沒有404, 奇蹟).

0.2. 代碼約定

本書使用如下習慣用法:

斜體

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

等寬字體 e.g. Python

用於代碼以及方法,模塊,操做符,函數,語句,屬性等的名稱.

等寬粗體

用於代碼執行結果.

0.3. 關於例子

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

除了一些平臺相關模塊的腳本, 全部例子均可以在 Windows, Solaris, 以及 Linux 下正常執行.

全部代碼都是有版權的. 固然,你能夠自由地使用這些這些模塊,別忘記你是從哪獲得(?學會)這些的.

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

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

0.4. 如何聯繫咱們

Python 江湖 QQ 羣: 43680167

Feather (校對) QQ: 85660100


1. 核心模塊

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

1.1. 介紹

Python 的標準庫包括了不少的模塊, 從 Python 語言自身特定的類型和聲明, 到一些只用於少數程序的不著名的模塊.

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

1.1.1. 內建函數和異常

下面的這兩個模塊比其餘模塊加在一塊兒還要重要: 定義內建函數(例如 len, int, range ...)的 _ _builtin_ _ 模塊, 以及定義全部內建異常的 exceptions 模塊.

Python 在啓動時導入這兩個模塊, 使任何程序都可以使用它們.

1.1.2. 操做系統接口模塊

Python 有許多使用了 POSIX 標準 API 和標準 C 語言庫的模塊. 它們爲底層操做系統提供了平臺獨立的接口.

這類的模塊包括: 提供文件和進程處理功能的 os 模塊; 提供平臺獨立的文件名處理 (分拆目錄名, 文件名, 後綴等)的 os.path 模塊; 以及時間日期處理相關的time/datetime 模塊.

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

延伸一點說, 網絡和線程模塊一樣也能夠歸爲這一個類型. 不過 Python 並無在全部的平臺/版本實現這些.

1.1.3. 類型支持模塊

標準庫裏有許多用於支持內建類型操做的庫. string 模塊實現了經常使用的字符串處理. math 模塊提供了數學計算操做和常量(pi, e都屬於這類常量), cmath 模塊爲複數提供了和 math 同樣的功能.

1.1.4. 正則表達式

re 模塊爲 Python 提供了正則表達式支持. 正則表達式是用於匹配字符串或特定子字符串的 有特定語法的字符串模式.

1.1.5. 語言支持模塊

sys 模塊可讓你訪問解釋器相關參數,好比模塊搜索路徑,解釋器版本號等. operator 模塊提供了和內建操做符做用相同的函數. copy 模塊容許 你複製對象, Python 2.0 新加入的 gc 模塊提供了對垃圾收集的相關控制功能.


1.2. _ _builtin_ _ 模塊

這個模塊包含 Python 中使用的內建函數. 通常不用手動導入這個模塊; Python會幫你作好一切.

1.2.1. 使用元組或字典中的參數調用函數

Python容許你實時地建立函數參數列表. 只要把全部的參數放入一個元組中, 而後經過內建的 apply 函數調用函數. 如 Example 1-1.

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

File: builtin-apply-example-1.py

def function(a, b):
    print a, b

apply(function, ("whither", "canada?"))
apply(function, (1, 2 + 3))

whither canada?
1 5

要想把關鍵字參數傳遞給一個函數, 你能夠將一個字典做爲 apply 函數的第 3 個參數, 參考 Example 1-2.

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

File: builtin-apply-example-2.py

def function(a, b):
    print a, b

apply(function, ("crunchy", "frog"))
apply(function, ("crunchy",), {"b": "frog"})
apply(function, (), {"a": "crunchy", "b": "frog"})

crunchy frog
crunchy frog
crunchy frog

apply 函數的一個常見用法是把構造函數參數從子類傳遞到基類, 尤爲是構造函數須要接受不少參數的時候. 如 Example 1-3 所示.

1.2.1.3. Example 1-3. 使用 apply 函數調用基類的構造函數

File: builtin-apply-example-3.py

class Rectangle:
    def _ _init_ _(self, color="white", width=10, height=10):
        print "create a", color, self, "sized", width, "x", height

class RoundedRectangle(Rectangle):
    def _ _init_ _(self, **kw):
        apply(Rectangle._ _init_ _, (self,), kw)

rect = Rectangle(color="green", height=100, width=100)
rect = RoundedRectangle(color="blue", height=20)

create a green <Rectangle instance at 8c8260> sized 100 x 100
create a blue <RoundedRectangle instance at 8c84c0> sized 10 x 20

Python 2.0 提供了另個方法來作相同的事. 你只須要使用一個傳統的函數調用 , 使用 * 來標記元組, ** 來標記字典.

下面兩個語句是等價的:

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

1.2.2. 加載和重載模塊

若是你寫過較龐大的 Python 程序, 那麼你就應該知道 import 語句是用來導入外部模塊的 (固然也可使用 from-import 版本). 不過你可能不知道 import 實際上是靠調用內建 函數 _ _import_ _ 來工做的.

經過這個戲法你能夠動態地調用函數. 當你只知道模塊名稱(字符串)的時候, 這將很方便. Example 1-4 展現了這種用法, 動態地導入全部以 "-plugin" 結尾的模塊.

1.2.2.1. Example 1-4. 使用 _ _import_ _ 函數加載模塊

File: builtin-import-example-1.py

import glob, os

modules = []

for module_file in glob.glob("*-plugin.py"):
    try:
        module_name, ext = os.path.splitext(os.path.basename(module_file))
        module = _ _import_ _(module_name)
        modules.append(module)
    except ImportError:
        pass # ignore broken modules

# say hello to all modules
for module in modules:
    module.hello()

example-plugin says hello

注意這個 plug-in 模塊文件名中有個 "-" (hyphens). 這意味着你不能使用普通的 import 命令, 由於 Python 的辨識符不容許有 "-" .

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

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

File: example-plugin.py

def hello():
    print "example-plugin says hello"

Example 1-6 展現瞭如何根據給定模塊名和函數名得到想要的函數對象.

1.2.2.3. Example 1-6. 使用 _ _import_ _ 函數得到特定函數

File: builtin-import-example-2.py

def getfunctionbyname(module_name, function_name):
    module = _ _import_ _(module_name)
    return getattr(module, function_name)

print repr(getfunctionbyname("dumbdbm", "open"))

<function open at 794fa0>

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

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

File: builtin-import-example-3.py

class LazyImport:
    def _ _init_ _(self, module_name):
        self.module_name = module_name
        self.module = None
    def _ _getattr_ _(self, name):
        if self.module is None:
            self.module = _ _import_ _(self.module_name)
        return getattr(self.module, name)

string = LazyImport("string")

print string.lowercase

abcdefghijklmnopqrstuvwxyz

Python 也提供了從新加載已加載模塊的基本支持. [Example 1-8 #eg-1-8 會加載 3 次 hello.py 文件.

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

File: builtin-reload-example-1.py

import hello
reload(hello)
reload(hello)

hello again, and welcome to the show
hello again, and welcome to the show
hello again, and welcome to the show

reload 直接接受模塊做爲參數.

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

注意,當你重加載模塊時, 它會被從新編譯, 新的模塊會代替模塊字典裏的老模塊. 可是, 已經用原模塊裏的類創建的實例仍然使用的是老模塊(不會被更新).

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

1.2.3. 關於名稱空間

dir 返回由給定模塊, 類, 實例, 或其餘類型的全部成員組成的列表. 這可能在交互式 Python 解釋器下頗有用, 也能夠用在其餘地方. Example 1-9展現了 dir 函數的用法.

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

File: builtin-dir-example-1.py

def dump(value):
    print value, "=>", dir(value)

import sys

dump(0)
dump(1.0)
dump(0.0j) # complex number
dump([]) # list
dump({}) # dictionary
dump("string")
dump(len) # function
dump(sys) # module

0 => []
1.0 => []
0j => ['conjugate', 'imag', 'real']
[] => ['append', 'count', 'extend', 'index', 'insert',
    'pop', 'remove', 'reverse', 'sort']
{} => ['clear', 'copy', 'get', 'has_key', 'items',
    'keys', 'update', 'values']
string => []
<built-in function len> => ['_ _doc_ _', '_ _name_ _', '_ _self_ _']
<module 'sys' (built-in)> => ['_ _doc_ _', '_ _name_ _',
    '_ _stderr_ _', '_ _stdin_ _', '_ _stdout_ _', 'argv',
    'builtin_module_names', 'copyright', 'dllhandle',
    'exc_info', 'exc_type', 'exec_prefix', 'executable',
...

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

1.2.3.2. Example 1-10. 使用 dir 函數查找類的全部成員

File: builtin-dir-example-2.py

class A:
    def a(self):
        pass
    def b(self):
        pass

class B(A):
    def c(self):
        pass
    def d(self):
        pass

def getmembers(klass, members=None):
    # get a list of all class members, ordered by class
    if members is None:
        members = []
    for k in klass._ _bases_ _:
        getmembers(k, members)
    for m in dir(klass):
        if m not in members:
            members.append(m)
    return members

print getmembers(A)
print getmembers(B)
print getmembers(IOError)

['_ _doc_ _', '_ _module_ _', 'a', 'b']
['_ _doc_ _', '_ _module_ _', 'a', 'b', 'c', 'd']
['_ _doc_ _', '_ _getitem_ _', '_ _init_ _', '_ _module_ _', '_ _str_ _']

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

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

vars 函數與此類似, 它返回的是包含每一個成員當前值的字典. 若是你使用不帶參數的 vars , 它將返回當前局部名稱空間的可見元素(同 locals() 函數 ). 如Example 1-11所表示.

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

File: builtin-vars-example-1.py

book = "library2"
pages = 250
scripts = 350


print "the %(book)s book contains more than %(scripts)s scripts" % vars()

the library book contains more than 350 scripts

1.2.4. 檢查對象類型

Python 是一種動態類型語言, 這意味着給一個定變量名能夠在不一樣的場合綁定到不一樣的類型上. 在接下面例子中, 一樣的函數分別被整數, 浮點數, 以及一個字符串調用:

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

type 函數 (如 Example 1-12 所示) 容許你檢查一個變量的類型. 這個函數會返回一個 type descriptor (類型描述符), 它對於 Python 解釋器提供的每一個類型都是不一樣的.

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

File: builtin-type-example-1.py

def dump(value):
    print type(value), value

dump(1)
dump(1.0)
dump("one")

<type 'int'> 1
<type 'float'> 1.0
<type 'string'> one

每一個類型都有一個對應的類型對象, 因此你可使用 is 操做符 (對象身份?) 來 檢查類型. (如 Example 1-13所示).

1.2.4.2. Example 1-13. 對文件名和文件對象使用 type 函數

File: builtin-type-example-2.py

def load(file):
    if isinstance(file, type("")):
        file = open(file, "rb")
    return file.read()

print len(load("samples/sample.jpg")), "bytes"
print len(load(open("samples/sample.jpg", "rb"))), "bytes"


4672 bytes
4672 bytes

callable 函數, 如 Example 1-14 所示, 能夠檢查一個對象是不是可調用的 (不管是直接調用或是經過 apply). 對於函數, 方法, lambda 函式, 類, 以及實現了 _ _call_ _ 方法的類實例, 它都返回 True.

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

File: builtin-callable-example-1.py

def dump(function):
    if callable(function):
        print function, "is callable"
    else:
        print function, "is *not* callable"

class A:
    def method(self, value):
        return value

class B(A):
    def _ _call_ _(self, value):
        return value

a = A()
b = B()

dump(0) # simple objects
dump("string")
dump(callable)
dump(dump) # function

dump(A) # classes
dump(B)
dump(B.method)

dump(a) # instances
dump(b)
dump(b.method)

0 is *not* callable
string is *not* callable
<built-in function callable> is callable
<function dump at 8ca320> is callable
A is callable
B is callable
<unbound method A.method> is callable
<A instance at 8caa10> is *not* callable
<B instance at 8cab00> is callable
<method A.method of B instance at 8cab00> is callable

注意類對象 (A 和 B) 都是可調用的; 若是調用它們, 就產生新的對象(類實例). 可是 A 類的實例不可調用, 由於它的類沒有實現 _ _call_ _ 方法.

你能夠在 operator 模塊中找到檢查對象是否爲某一內建類型(數字, 序列, 或者字典等) 的函數. 可是, 由於建立一個類很簡單(好比實現基本序列方法的類), 因此對這些 類型使用顯式的類型判斷並非好主意.

在處理類和實例的時候會複雜些. Python 不會把類做爲本質上的類型對待; 相反地, 全部的類都屬於一個特殊的類類型(special class type), 全部的類實例屬於一個特殊的實例類型(special instance type).

這意味着你不能使用 type 函數來測試一個實例是否屬於一個給定的類; 全部的實例都是一樣 的類型! 爲了解決這個問題, 你可使用 isinstance 函數,它會檢查一個對象是 不是給定類(或其子類)的實例. Example 1-15 展現了 isinstance 函數的使用.

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

File: builtin-isinstance-example-1.py

class A:
    pass

class B:
    pass

class C(A):
    pass

class D(A, B):
    pass

def dump(object):
    print object, "=>",
    if isinstance(object, A):
        print "A",
    if isinstance(object, B):
        print "B",
    if isinstance(object, C):
        print "C",
    if isinstance(object, D):
        print "D",
    print

a = A()
b = B()
c = C()
d = D()

dump(a)
dump(b)
dump(c)
dump(d)
dump(0)
dump("string")

<A instance at 8ca6d0> => A
<B instance at 8ca750> => B
<C instance at 8ca780> => A C
<D instance at 8ca7b0> => A B D
0 =>
string =>

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

注意, isinstance 能夠接受任何對象做爲參數, 而 issubclass 函數在接受非類對象參 數時會引起 TypeError 異常.

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

File: builtin-issubclass-example-1.py

class A:
    pass

class B:
    pass

class C(A):
    pass

class D(A, B):
    pass

def dump(object):
    print object, "=>",
    if issubclass(object, A):
        print "A",
    if issubclass(object, B):
        print "B",
    if issubclass(object, C):
        print "C",
    if issubclass(object, D):
        print "D",
    print

dump(A)
dump(B)
dump(C)
dump(D)
dump(0)
dump("string")

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

1.2.5. 計算 Python 表達式

Python 提供了在程序中與解釋器交互的多種方法. 例如 eval 函數將一個字符串 做爲 Python 表達式求值. 你能夠傳遞一串文本, 簡單的表達式, 或者使用 內建 Python 函數. 如 Example 1-17 所示.

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

File: builtin-eval-example-1.py

def dump(expression):
    result = eval(expression)
    print expression, "=>", result, type(result)

dump("1")
dump("1.0")
dump("'string'")
dump("1.0 + 2.0")
dump("'*' * 10")
dump("len('world')")

1 => 1 <type 'int'>
1.0 => 1.0 <type 'float'>
'string' => string <type 'string'>
1.0 + 2.0 => 3.0 <type 'float'>
'*' * 10 => ********** <type 'string'>
len('world') => 5 <type 'int'>

若是你不肯定字符串來源的安全性, 那麼你在使用 eval 的時候會遇到些麻煩. 例如, 某個用戶可能會使用 _ _import_ _ 函數加載 os 模塊, 而後從硬盤刪除文件 (如 Example 1-18 所示).

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

File: builtin-eval-example-2.py

print eval("_ _import_ _('os').getcwd()")
print eval("_ _import_ _('os').remove('file')")

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

這裏咱們獲得了一個 os.error 異常, 這說明 Python 事實上在嘗試刪除文件!

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

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

呃.... 咱們仍是獲得了個 os.error 異常.

這是由於 Python 在求值前會檢查這個字典, 若是沒有發現名稱爲 _ _builtins_ _ 的變量(複數形式), 它就會添加一個:

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

若是你打印這個 namespace 的內容, 你會發現裏邊有全部的內建函數.

[!Feather 注: 若是我RP不錯的話, 添加的這個_ _builtins_ _就是當前的_ _builtins_ _]

咱們注意到了若是這個變量存在, Python 就不會去添加默認的, 那麼咱們的解決方法也來了, 爲傳遞的字典參數加入一個 _ _builtins_ _ 項便可. 如 Example 1-19 所示.

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

File: builtin-eval-example-3.py

print eval("_ _import_ _('os').getcwd()", {})
print eval("_ _import_ _('os').remove('file')", {"_ _builtins_ _": {}})

/home/fredrik/librarybook
Traceback (innermost last):
  File "builtin-eval-example-3.py", line 2, in ?
  File "<string>", line 0, in ?
NameError: _ _import_ _

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

1.2.6. 編譯和執行代碼

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

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

File: builtin-compile-example-1.py

NAME = "script.py"

BODY = """
prnt 'owl-stretching time'
"""

try:
    compile(BODY, NAME, "exec")
except SyntaxError, v:
    print "syntax error:", v, "in", NAME

# syntax error: invalid syntax in script.py

成功執行後, compile 函數會返回一個代碼對象, 你可使用 exec 語句執行它, 參見 Example 1-21 .

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

File: builtin-compile-example-2.py

BODY = """
print 'the ant, an introduction'
"""

code = compile(BODY, "<script>", "exec")

print code

exec code

<code object ? at 8c6be0, file "<script>", line 0>
the ant, an introduction

使用 Example 1-22 中的類能夠在程序執行時實時地生成代碼. write 方法用於添加代碼, indent 和 dedent 方法用於控制縮進結構. 其餘部分交給類來處理.

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

File: builtin-compile-example-3.py

import sys, string

class CodeGeneratorBackend:
    "Simple code generator for Python"

    def begin(self, tab="/t"):
        self.code = []
        self.tab = tab
        self.level = 0

    def end(self):
        self.code.append("") # make sure there's a newline at the end 
        return compile(string.join(self.code, "/n"), "<code>", "exec")

    def write(self, string):
        self.code.append(self.tab * self.level + string)

    def indent(self):
        self.level = self.level + 1
        # in 2.0 and later, this can be written as: self.level += 1

    def dedent(self):
        if self.level == 0:
            raise SyntaxError, "internal error in code generator"
        self.level = self.level - 1
        # or: self.level -= 1

#
# try it out!

c = CodeGeneratorBackend()
c.begin()
c.write("for i in range(5):")
c.indent()
c.write("print 'code generation made easy!'")
c.dedent()
exec c.end()

code generation made easy!
code generation made easy!
code generation made easy!
code generation made easy!
code generation made easy!

Python 還提供了 execfile 函數, 一個從文件加載代碼, 編譯代碼, 執行代碼的快捷方式. Example 1-23 簡單地展現瞭如何使用這個函數.

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

File: builtin-execfile-example-1.py

execfile("hello.py")

def EXECFILE(filename, locals=None, globals=None):
    exec compile(open(filename).read(), filename, "exec") in locals, globals

EXECFILE("hello.py")

hello again, and welcome to the show
hello again, and welcome to the show

Example 1-24 中的代碼是 Example 1-23 中使用的 hello.py 文件.

1.2.6.5. Example 1-24. hello.py 腳本

File: hello.py

print "hello again, and welcome to the show"

1.2.7. 從 _ _builtin_ _ 模塊重載函數

由於 Python 在檢查局部名稱空間和模塊名稱空間前不會檢查內建函數, 因此有時候你可能要顯式地引用 _ _builtin_ _ 模塊. 例如 Example 1-25 重載了內建的 open 函數. 這時候要想使用原來的 open 函數, 就須要腳本顯式地指明模塊名稱.

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

File: builtin-open-example-1.py

def open(filename, mode="rb"):
    import _ _builtin_ _
    file = _ _builtin_ _.open(filename, mode)
    if file.read(5) not in("GIF87", "GIF89"):
        raise IOError, "not a GIF file"
    file.seek(0)
    return file

fp = open("samples/sample.gif")
print len(fp.read()), "bytes"

fp = open("samples/sample.jpg")
print len(fp.read()), "bytes"

3565 bytes
Traceback (innermost last):
  File "builtin-open-example-1.py", line 12, in ?
  File "builtin-open-example-1.py", line 5, in open
IOError: not a GIF file
[!Feather 注: 明白這個open()函數是幹什麼的麼? 檢查一個文件是不是 GIF 文件, 
通常如這類的圖片格式都在文件開頭有默認的格式. 
另外打開文件推薦使用file()而不是open() , 雖然暫時沒有區別]

1.3. exceptions 模塊

exceptions 模塊提供了標準異常的層次結構. Python 啓動的時候會自動導入這個模塊, 而且將它加入到 _ _builtin_ _ 模塊中. 也就是說, 通常不須要手動導入這個模塊.

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

該模塊定義瞭如下標準異常:

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

你能夠建立本身的異常類. 只須要繼承內建的 Exception 類(或者它的任意一個合適的子類)便可, 有須要時能夠再重載它的 _ _str_ _ 方法. Example 1-26 展現瞭如何使用 exceptions 模塊.

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

File: exceptions-example-1.py

# python imports this module by itself, so the following
# line isn't really needed
# python 會自動導入該模塊, 因此如下這行是沒必要要的
# import exceptions

class HTTPError(Exception):
    # indicates an HTTP protocol error
    def _ _init_ _(self, url, errcode, errmsg):
        self.url = url
        self.errcode = errcode
        self.errmsg = errmsg
    def _ _str_ _(self):
        return (
            "<HTTPError for %s: %s %s>" %
            (self.url, self.errcode, self.errmsg)
            )

try:
    raise HTTPError("http://www.python.org/foo", 200, "Not Found")
except HTTPError, error:
    print "url", "=>", error.url
    print "errcode", "=>", error.errcode
    print "errmsg", "=>", error.errmsg
    raise # reraise exception

url => http://www.python.org/foo
errcode => 200
errmsg => Not Found
Traceback (innermost last):
  File "exceptions-example-1", line 16, in ?
HTTPError: <HTTPError for http://www.python.org/foo: 200 Not Found>

1.4. os 模塊

這個模塊中的大部分函數經過對應平臺相關模塊實現, 好比 posix 和 nt. os 模塊會在第一次導入的時候自動加載合適的執行模塊.

1.4.1. 處理文件

內建的 open / file 函數用於建立, 打開和編輯文件, 如 Example 1-27 所示. 而 os 模塊提供了重命名和刪除文件所需的函數.

1.4.1.1. Example 1-27. 使用 os 模塊重命名和刪除文件

File: os-example-3.py

import os
import string

def replace(file, search_for, replace_with):
    # replace strings in a text file

    back = os.path.splitext(file)[0] + ".bak"
    temp = os.path.splitext(file)[0] + ".tmp"

    try:
        # remove old temp file, if any
        os.remove(temp)
    except os.error:
        pass

    fi = open(file)
    fo = open(temp, "w")

    for s in fi.readlines():
        fo.write(string.replace(s, search_for, replace_with))

    fi.close()
    fo.close()

    try:
        # remove old backup file, if any
        os.remove(back)
    except os.error:
        pass

    # rename original to backup...
    os.rename(file, back)

    # ...and temporary to original
    os.rename(temp, file)

#
# try it out!

file = "samples/sample.txt"

replace(file, "hello", "tjena")
replace(file, "tjena", "hello")

1.4.2. 處理目錄

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

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

1.4.2.1. Example 1-28. 使用 os 列出目錄下的文件

File: os-example-5.py

import os

for file in os.listdir("samples"):
    print file

sample.au
sample.jpg
sample.wav
...

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

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

File: os-example-4.py

import os

# where are we?
cwd = os.getcwd()
print "1", cwd

# go down
os.chdir("samples")
print "2", os.getcwd()

# go back up
os.chdir(os.pardir)
print "3", os.getcwd()

1 /ematter/librarybook
2 /ematter/librarybook/samples
3 /ematter/librarybook

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

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

File: os-example-6.py

import os

os.makedirs("test/multiple/levels")

fp = open("test/multiple/levels/file", "w")
fp.write("inspector praline")
fp.close()

# remove the file
os.remove("test/multiple/levels/file")

# and all empty directories above it
os.removedirs("test/multiple/levels")

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

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

File: os-example-7.py

import os

os.mkdir("test")
os.rmdir("test")

os.rmdir("samples") # this will fail

Traceback (innermost last):
  File "os-example-7", line 6, in ?
OSError: [Errno 41] Directory not empty: 'samples'

若是須要刪除非空目錄, 你可使用 shutil 模塊中的 rmtree 函數.

1.4.3. 處理文件屬性

stat 函數能夠用來獲取一個存在文件的信息, 如 Example 1-32 所示. 它返回一個類元組對象(stat_result對象, 包含 10 個元素), 依次是st_mode (權限模式), st_ino (inode number), st_dev (device), st_nlink (number of hard links), st_uid (全部者用戶 ID), st_gid (全部者所在組 ID ), st_size (文件大小, 字節), st_atime (最近一次訪問時間), st_mtime (最近修改時間), st_ctime (平臺相關; Unix下的最近一次元數據/metadata修改時間, 或者 Windows 下的建立時間) - 以上項目也可做爲屬性訪問.

[!Feather 注: 原文爲 9 元元組. 另,返回對象並不是元組類型,爲 struct.]

1.4.3.1. Example 1-32. 使用 os 模塊獲取文件屬性

File: os-example-1.py

import os
import time

file = "samples/sample.jpg"

def dump(st):
    mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime = st
    print "- size:", size, "bytes"
    print "- owner:", uid, gid
    print "- created:", time.ctime(ctime)
    print "- last accessed:", time.ctime(atime)
    print "- last modified:", time.ctime(mtime)
    print "- mode:", oct(mode)
    print "- inode/dev:", ino, dev

#
# get stats for a filename

st = os.stat(file)

print "stat", file
dump(st)
print

#
# get stats for an open file

fp = open(file)

st = os.fstat(fp.fileno())

print "fstat", file
dump(st)

stat samples/sample.jpg
- size: 4762 bytes
- owner: 0 0
- created: Tue Sep 07 22:45:58 1999
- last accessed: Sun Sep 19 00:00:00 1999
- last modified: Sun May 19 01:42:16 1996
- mode: 0100666
- inode/dev: 0 2

fstat samples/sample.jpg
- size: 4762 bytes
- owner: 0 0
- created: Tue Sep 07 22:45:58 1999
- last accessed: Sun Sep 19 00:00:00 1999
- last modified: Sun May 19 01:42:16 1996
- mode: 0100666
- inode/dev: 0 0

返回對象中有些屬性在非 Unix 平臺下是無心義的, 好比 (st_inode , st_dev)爲 Unix 下的爲每一個文件提供了惟一標識, 但在其餘平臺可能爲任意無心義數據 .

stat 模塊包含了不少能夠處理該返回對象的常量及函數. 下面的代碼展現了其中的一些.

可使用 chmod 和 utime 函數修改文件的權限模式和時間屬性,如 Example 1-33 所示.

1.4.3.2. Example 1-33. 使用 os 模塊修改文件的權限和時間戳

File: os-example-2.py

import os
import stat, time

infile = "samples/sample.jpg"
outfile = "out.jpg"

# copy contents
fi = open(infile, "rb")
fo = open(outfile, "wb")

while 1:
    s = fi.read(10000)
    if not s:
        break
    fo.write(s)

fi.close()
fo.close()

# copy mode and timestamp
st = os.stat(infile)
os.chmod(outfile, stat.S_IMODE(st[stat.ST_MODE]))
os.utime(outfile, (st[stat.ST_ATIME], st[stat.ST_MTIME]))

print "original", "=>"
print "mode", oct(stat.S_IMODE(st[stat.ST_MODE]))
print "atime", time.ctime(st[stat.ST_ATIME])
print "mtime", time.ctime(st[stat.ST_MTIME])

print "copy", "=>"
st = os.stat(outfile)
print "mode", oct(stat.S_IMODE(st[stat.ST_MODE]))
print "atime", time.ctime(st[stat.ST_ATIME])
print "mtime", time.ctime(st[stat.ST_MTIME])

original =>
mode 0666
atime Thu Oct 14 15:15:50 1999
mtime Mon Nov 13 15:42:36 1995
copy =>
mode 0666
atime Thu Oct 14 15:15:50 1999
mtime Mon Nov 13 15:42:36 1995

1.4.4. 處理進程

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

1.4.4.1. Example 1-34. 使用 os 執行操做系統命令

File: os-example-8.py

import os

if os.name == "nt":
    command = "dir"
else:
    command = "ls -l"

os.system(command)

-rwxrw-r--   1 effbot  effbot        76 Oct  9 14:17 README
-rwxrw-r--   1 effbot  effbot      1727 Oct  7 19:00 SimpleAsyncHTTP.py
-rwxrw-r--   1 effbot  effbot       314 Oct  7 20:29 aifc-example-1.py
-rwxrw-r--   1 effbot  effbot       259 Oct  7 20:38 anydbm-example-1.py
...

命令經過操做系統的標準 shell 執行, 並返回 shell 的退出狀態. 須要注意的是在 Windows 95/98 下, shell 一般是 command.com , 它的推出狀態老是 0.

因爲 11os.system11 直接將命令傳遞給 shell , 因此若是你不檢查傳入參數的時候會很危險 (好比命令  os.system("viewer %s" % file), 將 file 變量設置爲 " sample.jpg; rm -rf $HOME" ....). 若是不肯定參數的安全性, 那麼最好使用  exec 或  spawn 代替(稍後介紹).

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

1.4.4.2. Example 1-35. 使用 os 模塊啓動新進程

File: os-exec-example-1.py

import os
import sys

program = "python"
arguments = ["hello.py"]

print os.execvp(program, (program,) +  tuple(arguments))
print "goodbye"

hello again, and welcome to the show

Python 提供了不少表現不一樣的 exec 函數. Example 1-35 使用的是 execvp 函數, 它會從標準路徑搜索執行程序, 把第二個參數(元組)做爲單獨的參數傳遞給程序, 並使用當前的環境變量來運行程序. 其餘七個同類型函數請參閱 Python Library Reference .

在 Unix 環境下, 你能夠經過組合使用 exec , fork 以及 wait 函數來從當前程序調用另外一個程序, 如 Example 1-36 所示. fork 函數複製當前進程, wait 函數會等待一個子進程執行結束.

1.4.4.3. Example 1-36. 使用 os 模塊調用其餘程序 (Unix)

File: os-exec-example-2.py

import os
import sys

def run(program, *args):
    pid = os.fork()
    if not pid:
        os.execvp(program, (program,) +  args)
    return os.wait()[0]

run("python", "hello.py")

print "goodbye"

hello again, and welcome to the show
goodbye

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

fork 和 wait 函數在 Windows 上是不可用的, 可是你可使用 spawn 函數, 如 Example 1-37 所示. 不過, spawn 不會沿着路徑搜索可執行文件, 你必須本身處理好這些.

1.4.4.4. Example 1-37. 使用 os 模塊調用其餘程序 (Windows)

File: os-spawn-example-1.py

import os
import string

def run(program, *args):
    # find executable
    for path in string.split(os.environ["PATH"], os.pathsep):
        file = os.path.join(path, program) + ".exe"
        try:
            return os.spawnv(os.P_WAIT, file, (file,) + args)
        except os.error:
            pass
    raise os.error, "cannot find executable"

run("python", "hello.py")

print "goodbye"

hello again, and welcome to the show
goodbye

spawn 函數還可用於在後臺運行一個程序. Example 1-38 給 run 函數添加了一個可選的 mode 參數; 當設置爲 os.P_NOWAIT 時, 這個腳本不會等待子程序結束, 默認值 os.P_WAIT 時 spawn 會等待子進程結束.

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

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

File: os-spawn-example-2.py

import os
import string

def run(program, *args, **kw):
    # find executable
    mode = kw.get("mode", os.P_WAIT)
    for path in string.split(os.environ["PATH"], os.pathsep):
        file = os.path.join(path, program) + ".exe"
        try:
            return os.spawnv(mode, file, (file,) + args)
        except os.error:
            pass
    raise os.error, "cannot find executable"

run("python", "hello.py", mode=os.P_NOWAIT)
print "goodbye"

goodbye
hello again, and welcome to the show

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

1.4.4.6. Example 1-39. 使用 spawn 或 fork/exec 調用其餘程序

File: os-spawn-example-3.py

import os
import string

if os.name in ("nt", "dos"):
    exefile = ".exe"
else:
    exefile = ""

def spawn(program, *args):
    try:
        # possible 2.0 shortcut!
        return os.spawnvp(program, (program,) + args)
    except AttributeError:
        pass
    try:
        spawnv = os.spawnv
    except AttributeError:

        # assume it's unix
        pid = os.fork()
        if not pid:
            os.execvp(program, (program,) + args)
        return os.wait()[0]
    else:
        # got spawnv but no spawnp: go look for an executable
        for path in string.split(os.environ["PATH"], os.pathsep):
            file = os.path.join(path, program) + exefile
            try:
                return spawnv(os.P_WAIT, file, (file,) + args)
            except os.error:
                pass
        raise IOError, "cannot find executable"

#
# try it out!

spawn("python", "hello.py")

print "goodbye"

hello again, and welcome to the show
goodbye

Example 1-39 首先嚐試調用 spawnvp 函數. 若是該函數不存在 (一些版本/平臺沒有這個函數), 它將繼續查找一個名爲 spawnv 的函數而且 開始查找程序路徑. 做爲最後的選擇, 它會調用 exec 和 fork 函數完成工做.

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

Unix 系統中, 你可使用 fork 函數把當前進程轉入後臺(一個"守護者/daemon"). 通常來講, 你須要派生(fork off)一個當前進程的副本, 而後終止原進程, 如Example 1-40 所示.

1.4.5.1. Example 1-40. 使用 os 模塊使腳本做爲守護執行 (Unix)

File: os-example-14.py

import os
import time

pid = os.fork()
if pid:
    os._exit(0) # kill original

print "daemon started"
time.sleep(10)
print "daemon terminated"

須要建立一個真正的後臺程序稍微有點複雜, 首先調用 setpgrp 函數建立一個 "進程組首領/process group leader". 不然, 向無關進程組發送的信號(同時)會引發守護進程的問題:

os.setpgrp()

爲了確保守護進程建立的文件可以得到程序指定的 mode flags(權限模式標記?), 最好刪除 user mode mask:

os.umask(0)

而後, 你應該重定向 stdout/stderr 文件, 而不能只是簡單地關閉它們(若是你的程序須要 stdout 或 stderr 寫入內容的時候, 可能會出現意想不到的問題).

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

換言之, 因爲 Python 的 print 和 C 中的 printf/fprintf 在設備(device) 沒有鏈接後不會關閉你的程序, 此時守護進程中的 sys.stdout.write() 會拋出一個IOError 異常, 而你的程序依然在後臺運行的很好....

另外, 先前例子中的 _exit 函數會終止當前進程. 而 sys.exit 不一樣, 若是調用者(caller) 捕獲了 SystemExit 異常, 程序仍然會繼續執行. 如 Example 1-41 所示.

1.4.5.2. Example 1-41. 使用 os 模塊終止當前進程

File: os-example-9.py

import os
import sys

try:
    sys.exit(1)
except SystemExit, value:
    print "caught exit(%s)" % value

try:
    os._exit(2)
except SystemExit, value:
    print "caught exit(%s)" % value

print "bye!"

caught exit(1)

1.5. os.path 模塊

os.path 模塊包含了各類處理長文件名(路徑名)的函數. 先導入 (import) os 模塊, 而後就能夠以 os.path 訪問該模塊.

1.5.1. 處理文件名

os.path 模塊包含了許多與平臺無關的處理長文件名的函數. 也就是說, 你不須要處理先後斜槓, 冒號等. 咱們能夠看看 Example 1-42 中的樣例代碼.

1.5.1.1. Example 1-42. 使用 os.path 模塊處理文件名

File: os-path-example-1.py

import os

filename = "my/little/pony"

print "using", os.name, "..."
print "split", "=>", os.path.split(filename)
print "splitext", "=>", os.path.splitext(filename)
print "dirname", "=>", os.path.dirname(filename)
print "basename", "=>", os.path.basename(filename)
print "join", "=>", os.path.join(os.path.dirname(filename),
                                 os.path.basename(filename))

using nt ...
split => ('my/little', 'pony')
splitext => ('my/little/pony', '')
dirname => my/little
basename => pony
join => my/little/pony

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

os.path 模塊中還有許多函數容許你簡單快速地獲知文件名的一些特徵,如 Example 1-43 所示。

1.5.1.2. Example 1-43. 使用 os.path 模塊檢查文件名的特徵

File: os-path-example-2.py

import os

FILES = (
    os.curdir,
    "/",
    "file",
    "/file",
    "samples",
    "samples/sample.jpg",
    "directory/file",
    "../directory/file",
    "/directory/file"
    )

for file in FILES:
    print file, "=>",
    if os.path.exists(file):
        print "EXISTS",
    if os.path.isabs(file):
        print "ISABS",
    if os.path.isdir(file):
        print "ISDIR",
    if os.path.isfile(file):
        print "ISFILE",
    if os.path.islink(file):
        print "ISLINK",
    if os.path.ismount(file):
        print "ISMOUNT",
    print

. => EXISTS ISDIR
/ => EXISTS ISABS ISDIR ISMOUNT
file =>
/file => ISABS
samples => EXISTS ISDIR
samples/sample.jpg => EXISTS ISFILE
directory/file =>
../directory/file =>
/directory/file => ISABS

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

1.5.1.3. Example 1-44. 使用 os.path 模塊將用戶名插入到文件名

File: os-path-expanduser-example-1.py

import os

print os.path.expanduser("~/.pythonrc")

# /home/effbot/.pythonrc

expandvars 函數將文件名中的環境變量替換爲對應值, 如 Example 1-45 所示.

1.5.1.4. Example 1-45. 使用 os.path 替換文件名中的環境變量

File: os-path-expandvars-example-1.py

import os

os.environ["USER"] = "user"

print os.path.expandvars("/home/$USER/config")
print os.path.expandvars("$USER/folders")

/home/user/config
user/folders

1.5.2. 搜索文件系統

walk 函數會幫你找出一個目錄樹下的全部文件 (如 Example 1-46 所示). 它的參數依次是目錄名, 回調函數, 以及傳遞給回調函數的數據對象.

1.5.2.1. Example 1-46. 使用 os.path 搜索文件系統

File: os-path-walk-example-1.py

import os

def callback(arg, directory, files):
    for file in files:
        print os.path.join(directory, file), repr(arg)

os.path.walk(".", callback, "secret message")

./aifc-example-1.py 'secret message'
./anydbm-example-1.py 'secret message'
./array-example-1.py 'secret message'
...
./samples 'secret message'
./samples/sample.jpg 'secret message'
./samples/sample.txt 'secret message'
./samples/sample.zip 'secret message'
./samples/articles 'secret message'
./samples/articles/article-1.txt 'secret message'
./samples/articles/article-2.txt 'secret message'
...

walk 函數的接口多少有點晦澀 (也許只是對我我的而言, 我老是記不住參數的順序). Example 1-47 中展現的 index 函數會返回一個文件名列表, 你能夠直接使用 for-in 循環處理文件.

1.5.2.2. Example 1-47. 使用 os.listdir 搜索文件系統

File: os-path-walk-example-2.py

import os

def index(directory):
    # like os.listdir, but traverses directory trees
    stack = [directory]
    files = []
    while stack:
        directory = stack.pop()
        for file in os.listdir(directory):
            fullname = os.path.join(directory, file)
            files.append(fullname)
            if os.path.isdir(fullname) and not os.path.islink(fullname):
                stack.append(fullname)
    return files

for file in index("."):
    print file

./aifc-example-1.py
./anydbm-example-1.py
./array-example-1.py
...

若是你不想列出全部的文件 (基於性能或者是內存的考慮) , Example 1-48 展現了另外一種方法. 這裏 DirectoryWalker 類的行爲與序列對象類似, 一次返回一個文件. (generator?)

1.5.2.3. Example 1-48. 使用 DirectoryWalker 搜索文件系統

File: os-path-walk-example-3.py

import os

class DirectoryWalker:
    # a forward iterator that traverses a directory tree

    def _ _init_ _(self, directory):
        self.stack = [directory]
        self.files = []
        self.index = 0

    def _ _getitem_ _(self, index):
        while 1:
            try:
                file = self.files[self.index]
                self.index = self.index + 1
            except IndexError:
                # pop next directory from stack
                self.directory = self.stack.pop()
                self.files = os.listdir(self.directory)
                self.index = 0
            else:
                # got a filename
                fullname = os.path.join(self.directory, file)
                if os.path.isdir(fullname) and not os.path.islink(fullname):
                    self.stack.append(fullname)
                return fullname

for file in DirectoryWalker("."):
    print file

./aifc-example-1.py
./anydbm-example-1.py
./array-example-1.py
...

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

最後, 若是你須要處理文件大小和時間戳, Example 1-49 給出了一個類, 它返回文件名和它的 os.stat 屬性(一個元組). 這個版本在每一個文件上都能節省一次或兩次 stat 調用( os.path.isdir 和 os.path.islink 內部都使用了 stat ), 而且在一些平臺上運行很快.

1.5.2.4. Example 1-49. 使用 DirectoryStatWalker 搜索文件系統

File: os-path-walk-example-4.py

import os, stat

class DirectoryStatWalker:
    # a forward iterator that traverses a directory tree, and
    # returns the filename and additional file information

    def _ _init_ _(self, directory):
        self.stack = [directory]
        self.files = []
        self.index = 0

    def _ _getitem_ _(self, index):
        while 1:
            try:
                file = self.files[self.index]
                self.index = self.index + 1
            except IndexError:
                # pop next directory from stack
                self.directory = self.stack.pop()
                self.files = os.listdir(self.directory)
                self.index = 0
            else:
                # got a filename
                fullname = os.path.join(self.directory, file)
                st = os.stat(fullname)
                mode = st[stat.ST_MODE]
                if stat.S_ISDIR(mode) and not stat.S_ISLNK(mode):
                    self.stack.append(fullname)
                return fullname, st

for file, st in DirectoryStatWalker("."):
    print file, st[stat.ST_SIZE]

./aifc-example-1.py 336
./anydbm-example-1.py 244
./array-example-1.py 526

1.6. stat 模塊

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

1.6.0.1. Example 1-50. Using the stat Module

File: stat-example-1.py

import stat
import os, time

st = os.stat("samples/sample.txt")

print "mode", "=>", oct(stat.S_IMODE(st[stat.ST_MODE]))

print "type", "=>",
if stat.S_ISDIR(st[stat.ST_MODE]):
    print "DIRECTORY",
if stat.S_ISREG(st[stat.ST_MODE]):
    print "REGULAR",
if stat.S_ISLNK(st[stat.ST_MODE]):
    print "LINK",
print

print "size", "=>", st[stat.ST_SIZE]

print "last accessed", "=>", time.ctime(st[stat.ST_ATIME])
print "last modified", "=>", time.ctime(st[stat.ST_MTIME])
print "inode changed", "=>", time.ctime(st[stat.ST_CTIME])

mode => 0664
type => REGULAR
size => 305
last accessed => Sun Oct 10 22:12:30 1999
last modified => Sun Oct 10 18:39:37 1999
inode changed => Sun Oct 10 15:26:38 1999

1.7. string 模塊

string 模塊提供了一些用於處理字符串類型的函數, 如 Example 1-51 所示.

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

File: string-example-1.py

import string

text = "Monty Python's Flying Circus"

print "upper", "=>", string.upper(text)
print "lower", "=>", string.lower(text)
print "split", "=>", string.split(text)
print "join", "=>", string.join(string.split(text), "+")
print "replace", "=>", string.replace(text, "Python", "Java")
print "find", "=>", string.find(text, "Python"), string.find(text, "Java")
print "count", "=>", string.count(text, "n")

upper => MONTY PYTHON'S FLYING CIRCUS
lower => monty python's flying circus
split => ['Monty', "Python's", 'Flying', 'Circus']
join => Monty+Python's+Flying+Circus
replace => Monty Java's Flying Circus
find => 6 -1
count => 3

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

在 Python1.6 和後繼版本,更多的字符串操做均可以做爲字符串方法來訪問, 如 Example 1-52 所示, string 模塊中的許多函數只是對相對應字符串方法的封裝.

1.7.0.2. Example 1-52. 使用字符串方法替代 string 模塊函數

File: string-example-2.py

text = "Monty Python's Flying Circus"

print "upper", "=>", text.upper()
print "lower", "=>", text.lower()
print "split", "=>", text.split()
print "join", "=>", "+".join(text.split())
print "replace", "=>", text.replace("Python", "Perl")
print "find", "=>", text.find("Python"), text.find("Perl")
print "count", "=>", text.count("n")

upper => MONTY PYTHON'S FLYING CIRCUS
lower => monty python's flying circus
split => ['Monty', "Python's", 'Flying', 'Circus']
join => Monty+Python's+Flying+Circus
replace => Monty Perl's Flying Circus
find => 6 -1
count => 3

爲了加強模塊對字符的處理能力, 除了字符串方法, string 模塊還包含了類型轉換函數用於把字符串轉換爲其餘類型, (如 Example 1-53 所示).

1.7.0.3. Example 1-53. 使用 string 模塊將字符串轉爲數字

File: string-example-3.py

import string

print int("4711"),
print string.atoi("4711"),
print string.atoi("11147", 8), # octal 八進制
print string.atoi("1267", 16), # hexadecimal 十六進制
print string.atoi("3mv", 36) # whatever...

print string.atoi("4711", 0),
print string.atoi("04711", 0),
print string.atoi("0x4711", 0)

print float("4711"),
print string.atof("1"),
print string.atof("1.23e5")

4711 4711 4711 4711 4711
4711 2505 18193
4711.0 1.0 123000.0

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

atoi 函數能夠接受可選的第二個參數, 指定數基(number base). 若是數基爲 0, 那麼函數將檢查字符串的前幾個字符來決定使用的數基: 若是爲 "0x," 數基將爲 16 (十六進制), 若是爲 "0," 則數基爲 8 (八進制). 默認數基值爲 10 (十進制), 當你未傳遞參數時就使用這個值.

在 1.6 及之後版本中, int 函數和 atoi 同樣能夠接受第二個參數. 與字符串版本函數不同的是 , int 和 float 能夠接受 Unicode 字符串對象.


1.8. re 模塊

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

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

match 嘗試從字符串的起始匹配一個模式, 如 Example 1-54 所示. 若是模式匹配了某些內容 (包括空字符串, 若是模式容許的話) , 它將返回一個匹配對象. 使用它的 group 方法能夠找出匹配的內容.

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

File: re-example-1.py

import re

text = "The Attila the Hun Show"

# a single character 單個字符
m = re.match(".", text)
if m: print repr("."), "=>", repr(m.group(0))

# any string of characters 任何字符串
m = re.match(".*", text)
if m: print repr(".*"), "=>", repr(m.group(0))

# a string of letters (at least one) 只包含字母的字符串(至少一個)
m = re.match("/w+", text)
if m: print repr("/w+"), "=>", repr(m.group(0))

# a string of digits 只包含數字的字符串
m = re.match("/d+", text)
if m: print repr("/d+"), "=>", repr(m.group(0))

 '.' => 'T'
'.*' => 'The Attila the Hun Show'
'//w+' => 'The'

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

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

File: re-example-2.py

import re

text ="10/15/99"

m = re.match("(/d{2})/(/d{2})/(/d{2,4})", text)
if m:
    print m.group(1, 2, 3)

('10', '15', '99')

search 函數會在字符串內查找模式匹配, 如 Example 1-56 所示. 它在全部可能的字符位置嘗試匹配模式, 從最左邊開始, 一旦找到匹配就返回一個匹配對象. 若是沒有找到相應的匹配, 就返回 None .

1.8.0.3. Example 1-56. 使用 re 模塊搜索子字符串

File: re-example-3.py

import re

text = "Example 3: There is 1 date 10/25/95 in here!"

m = re.search("(/d{1,2})/(/d{1,2})/(/d{2,4})", text)

print m.group(1), m.group(2), m.group(3)

month, day, year = m.group(1, 2, 3)
print month, day, year

date = m.group(0)
print date

10 25 95
10 25 95
10/25/95

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

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

File: re-example-4.py

import re

text = "you're no fun anymore..."

# literal replace (string.replace is faster)
# 文字替換 (string.replace 速度更快)
print re.sub("fun", "entertaining", text)

# collapse all non-letter sequences to a single dash 
# 將全部非字母序列轉換爲一個"-"(dansh,破折號)
print re.sub("[^/w]+", "-", text)

# convert all words to beeps 
# 將全部單詞替換爲 BEEP
print re.sub("/S+", "-BEEP-", text)

you're no entertaining anymore...
you-re-no-fun-anymore-
-BEEP- -BEEP- -BEEP- -BEEP-

你也能夠經過回調 (callback) 函數使用 sub 來替換指定模式. Example 1-58 展現瞭如何預編譯模式.

1.8.0.5. Example 1-58. 使用 re 模塊替換字符串(經過回調函數)

File: re-example-5.py

import re
import string

text = "a line of text//012another line of text//012etc..."

def octal(match):
    # replace octal code with corresponding ASCII character
    # 使用對應 ASCII 字符替換八進制代碼
    return chr(string.atoi(match.group(1), 8))

octal_pattern = re.compile(r"//(/d/d/d)")

print text
print octal_pattern.sub(octal, text)

a line of text/012another line of text/012etc...
a line of text
another line of text
etc...

若是你不編譯, re 模塊會爲你緩存一個編譯後版本, 全部的小腳本中, 一般不須要編譯正則表達式. Python1.5.2 中, 緩存中能夠容納 20 個匹配模式, 而在 2.0 中, 緩存則能夠容納 100 個匹配模式.

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

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

File: re-example-6.py

import re, string

def combined_pattern(patterns):
    p = re.compile(
        string.join(map(lambda x: "("+x+")", patterns), "|")
        )
    def fixup(v, m=p.match, r=range(0,len(patterns))):
        try:
            regs = m(v).regs
        except AttributeError:
            return None # no match, so m.regs will fail
        else:
            for i in r:
                if regs[i+1] != (-1, -1):
                    return i
    return fixup

#
# try it out!

patterns = [
    r"/d+",
    r"abc/d{2,4}",
    r"p/w+"
]

p = combined_pattern(patterns)

print p("129391")
print p("abc800")
print p("abc1600")
print p("python")
print p("perl")
print p("tcl")

0
1
1
2
2
None

1.9. math 模塊

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

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

File: math-example-1.py

import math

print "e", "=>", math.e
print "pi", "=>", math.pi
print "hypot", "=>", math.hypot(3.0, 4.0)

# and many others...

e => 2.71828182846
pi => 3.14159265359
hypot => 5.0

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


1.10. cmath 模塊

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

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

File: cmath-example-1.py

import cmath

print "pi", "=>", cmath.pi
print "sqrt(-1)", "=>", cmath.sqrt(-1)

pi => 3.14159265359
sqrt(-1) => 1j

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


1.11. operator 模塊

operator 模塊爲 Python 提供了一個 "功能性" 的標準操做符接口. 當使用 map 以及 filter 一類的函數的時候, operator 模塊中的函數能夠替換一些 lambda函式. 並且這些函數在一些喜歡寫晦澀代碼的程序員中很流行. Example 1-62 展現了 operator 模塊的通常用法.

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

File: operator-example-1.py

import operator

sequence = 1, 2, 4

print "add", "=>", reduce(operator.add, sequence)
print "sub", "=>", reduce(operator.sub, sequence)
print "mul", "=>", reduce(operator.mul, sequence)
print "concat", "=>", operator.concat("spam", "egg")
print "repeat", "=>", operator.repeat("spam", 5)
print "getitem", "=>", operator.getitem(sequence, 2)
print "indexOf", "=>", operator.indexOf(sequence, 2)
print "sequenceIncludes", "=>", operator.sequenceIncludes(sequence, 3)

add => 7
sub => -5
mul => 8
concat => spamegg
repeat => spamspamspamspamspam

getitem => 4
indexOf => 1
sequenceIncludes => 0

Example 1-63 展現了一些能夠用於檢查對象類型的 operator 函數.

1.11.0.2. Example 1-63. 使用 operator 模塊檢查類型

File: operator-example-2.py

import operator
import UserList

def dump(data):
    print type(data), "=>",
    if operator.isCallable(data):
        print "CALLABLE",
    if operator.isMappingType(data):
        print "MAPPING",
    if operator.isNumberType(data):
        print "NUMBER",
    if operator.isSequenceType(data):
        print "SEQUENCE",
    print
        
dump(0)
dump("string")
dump("string"[0])
dump([1, 2, 3])
dump((1, 2, 3))
dump({"a": 1})
dump(len) # function 函數
dump(UserList) # module 模塊
dump(UserList.UserList) # class 類
dump(UserList.UserList()) # instance 實例

<type 'int'> => NUMBER
<type 'string'> => SEQUENCE
<type 'string'> => SEQUENCE
<type 'list'> => SEQUENCE
<type 'tuple'> => SEQUENCE
<type 'dictionary'> => MAPPING
<type 'builtin_function_or_method'> => CALLABLE
<type 'module'> =>
<type 'class'> => CALLABLE
<type 'instance'> => MAPPING NUMBER SEQUENCE

這裏須要注意 operator 模塊使用很是規的方法處理對象實例. 因此使用 isNumberType , isMappingType , 以及 isSequenceType 函數的時候要當心, 這很容易下降代碼的擴展性.

一樣須要注意的是一個字符串序列成員 (單個字符) 也是序列. 因此當在遞歸函數使用 isSequenceType 來截斷對象樹的時候, 別把普通字符串做爲參數(或者是任何包含字符串的序列對象).


1.12. copy 模塊

copy 模塊包含兩個函數, 用來拷貝對象, 如 Example 1-64 所示.

copy(object) => object 建立給定對象的 "淺/淺層(shallow)" 拷貝(copy). 這裏 "淺/淺層(shallow)" 的意思是複製對象自己, 但當對象是一個容器 (container) 時, 它的成員仍然指向原來的成員對象.

1.12.0.1. Example 1-64. 使用 copy 模塊複製對象

File: copy-example-1.py

import copy

a = [[1],[2],[3]]
b = copy.copy(a)

print "before", "=>"
print a
print b

# modify original
a[0][0] = 0
a[1] = None

print "after", "=>"
print a
print b

before =>
[[1], [2], [3]]
[[1], [2], [3]]
after =>
[[0], None, [3]]
[[0], [2], [3]]

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

相反地, deepcopy(object) => object 建立一個對象的深層拷貝(deepcopy), 如 Example 1-65 所示, 當對象爲一個容器時, 全部的成員都被遞歸地複製了。

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

File: copy-example-2.py

import copy

a = [[1],[2],[3]]
b = copy.deepcopy(a)

print "before", "=>"
print a
print b

# modify original
a[0][0] = 0
a[1] = None

print "after", "=>"
print a
print b

before =>
[[1], [2], [3]]
[[1], [2], [3]]
after =>
[[0], None, [3]]
[[1], [2], [3]]

1.13. sys 模塊

sys 模塊提供了許多函數和變量來處理 Python 運行時環境的不一樣部分.

1.13.1. 處理命令行參數

在解釋器啓動後, argv 列表包含了傳遞給腳本的全部參數, 如 Example 1-66 所示. 列表的第一個元素爲腳本自身的名稱.

1.13.1.1. Example 1-66. 使用sys模塊得到腳本的參數

File: sys-argv-example-1.py

import sys

print "script name is", sys.argv[0]

if len(sys.argv) > 1:
    print "there are", len(sys.argv)-1, "arguments:"
    for arg in sys.argv[1:]:
        print arg
else:
    print "there are no arguments!"

script name is sys-argv-example-1.py
there are no arguments!

若是是從標準輸入讀入腳本 (好比 "python < sys-argv-example-1.py"), 腳本的名稱將被設置爲空串. 若是把腳本做爲字符串傳遞給python (使用 -c 選項), 腳本名會被設置爲 "-c".

1.13.2. 處理模塊

path 列表是一個由目錄名構成的列表, Python 從中查找擴展模塊( Python 源模塊, 編譯模塊,或者二進制擴展). 啓動 Python 時,這個列表從根據內建規則, PYTHONPATH 環境變量的內容, 以及註冊表( Windows 系統)等進行初始化. 因爲它只是一個普通的列表, 你能夠在程序中對它進行操做, 如 Example 1-67 所示.

1.13.2.1. Example 1-67. 使用sys模塊操做模塊搜索路徑

File: sys-path-example-1.py

import sys

print "path has", len(sys.path), "members"

# add the sample directory to the path
sys.path.insert(0, "samples")
import sample

# nuke the path
sys.path = []
import random # oops!

path has 7 members
this is the sample module!
Traceback (innermost last):
  File "sys-path-example-1.py", line 11, in ?
    import random # oops!
ImportError: No module named random

builtin_module_names 列表包含 Python 解釋器中全部內建模塊的名稱, Example 1-68 給出了它的樣例代碼.

1.13.2.2. Example 1-68. 使用sys模塊查找內建模塊

File: sys-builtin-module-names-example-1.py

import sys

def dump(module):
    print module, "=>",
    if module in sys.builtin_module_names:
        print "<BUILTIN>"
    else:
        module = _ _import_ _(module)
        print module._ _file_ _

dump("os")
dump("sys")
dump("string")
dump("strop")
dump("zlib")

os => C:/python/lib/os.pyc
sys => <BUILTIN>
string => C:/python/lib/string.pyc
strop => <BUILTIN>
zlib => C:/python/zlib.pyd

modules 字典包含全部加載的模塊. import 語句在從磁盤導入內容以前會先檢查這個字典.

正如你在 Example 1-69 中所見到的, Python 在處理你的腳本以前就已經導入了不少模塊.

1.13.2.3. Example 1-69. 使用sys模塊查找已導入的模塊

File: sys-modules-example-1.py

import sys

print sys.modules.keys()

['os.path', 'os', 'exceptions', '_ _main_ _', 'ntpath', 'strop', 'nt',
'sys', '_ _builtin_ _', 'site', 'signal', 'UserDict', 'string', 'stat']

1.13.3. 處理引用記數

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

1.13.3.1. Example 1-70. 使用sys模塊得到引用記數

File: sys-getrefcount-example-1.py

import sys

variable = 1234

print sys.getrefcount(0)
print sys.getrefcount(variable)
print sys.getrefcount(None)

50
3
192

注意這個值老是比實際的數量大, 由於該函數自己在肯定這個值的時候依賴這個對象.

== 檢查主機平臺===

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

1.13.3.2. Example 1-71. 使用sys模塊得到當前平臺

File: sys-platform-example-1.py

import sys

#
# emulate "import os.path" (sort of)...

if sys.platform == "win32":
    import ntpath
    pathmodule = ntpath
elif sys.platform == "mac":
    import macpath
    pathmodule = macpath
else:
    # assume it's a posix platform
    import posixpath
    pathmodule = posixpath

print pathmodule

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

1.13.4. 跟蹤程序

setprofiler 函數容許你配置一個分析函數(profiling function). 這個函數會在每次調用某個函數或方法時被調用(明確或隱含的), 或是遇到異常的時候被調用. 讓咱們看看 Example 1-72 的代碼.

1.13.4.1. Example 1-72. 使用sys模塊配置分析函數

File: sys-setprofiler-example-1.py

import sys

def test(n):
    j = 0
    for i in range(n):
        j = j + i
    return n

def profiler(frame, event, arg):
    print event, frame.f_code.co_name, frame.f_lineno, "->", arg

# profiler is activated on the next call, return, or exception
# 分析函數將在下次函數調用, 返回, 或異常時激活
sys.setprofile(profiler)

# profile this function call
# 分析此次函數調用
test(1)

# disable profiler
# 禁用分析函數
sys.setprofile(None)

# don't profile this call
# 不會分析此次函數調用
test(2)

call test 3 -> None
return test 7 -> 1

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

Example 1-73 中的 settrace 函數與此相似, 可是 trace 函數會在解釋器每執行到新的一行時被調用.

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

File: sys-settrace-example-1.py

import sys

def test(n):
    j = 0
    for i in range(n):
        j = j + i
    return n

def tracer(frame, event, arg):
    print event, frame.f_code.co_name, frame.f_lineno, "->", arg
    return tracer

# tracer is activated on the next call, return, or exception
# 跟蹤器將在下次函數調用, 返回, 或異常時激活
sys.settrace(tracer)

# trace this function call
# 跟蹤此次函數調用
test(1)

# disable tracing
# 禁用跟蹤器
sys.settrace(None)

# don't trace this call
# 不會跟蹤此次函數調用
test(2)

call test 3 -> None
line test 3 -> None
line test 4 -> None
line test 5 -> None
line test 5 -> None
line test 6 -> None
line test 5 -> None
line test 7 -> None
return test 7 -> 1

基於該函數提供的跟蹤功能, pdb 模塊提供了完整的調試( debug )框架.

1.13.5. 處理標準輸出/輸入

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

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

File: sys-stdout-example-1.py

import sys
import string

class Redirect:

    def _ _init_ _(self, stdout):
        self.stdout = stdout

    def write(self, s):
        self.stdout.write(string.lower(s))

# redirect standard output (including the print statement)
# 重定向標準輸出(包括print語句)
old_stdout = sys.stdout
sys.stdout = Redirect(sys.stdout)

print "HEJA SVERIGE",
print "FRISKT HUM/303/226R"

# restore standard output
# 恢復標準輸出
sys.stdout = old_stdout

print "M/303/205/303/205/303/205/303/205L!"

heja sverige friskt hum/303/266r
M/303/205/303/205/303/205/303/205L!

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

(除非 C 類型的實例外:Python 使用一個叫作 softspace 的整數屬性來控制輸出中的空白. 若是沒有這個屬性, Python 將把這個屬性附加到這個對象上. 你不須要在使用 Python 對象時擔憂, 可是在重定向到一個 C 類型時, 你應該確保該類型支持 softspace 屬性.)

1.13.6. 退出程序

執行至主程序的末尾時,解釋器會自動退出. 可是若是須要中途退出程序, 你能夠調用 sys.exit 函數, 它帶有一個可選的整數參數返回給調用它的程序.Example 1-75 給出了範例.

1.13.6.1. Example 1-75. 使用sys模塊退出程序

File: sys-exit-example-1.py

import sys

print "hello"

sys.exit(1)

print "there"

hello

注意 sys.exit 並非當即退出. 而是引起一個 SystemExit 異常. 這意味着你能夠在主程序中捕獲對 sys.exit 的調用, 如 Example 1-76 所示.

1.13.6.2. Example 1-76. 捕獲sys.exit調用

File: sys-exit-example-2.py

import sys

print "hello"

try:
    sys.exit(1)
except SystemExit:
    pass

print "there"

hello
there

若是準備在退出前本身清理一些東西(好比刪除臨時文件), 你能夠配置一個 "退出處理函數"(exit handler), 它將在程序退出的時候自動被調用. 如 Example 1-77 所示.

1.13.6.3. Example 1-77. 另外一種捕獲sys.exit調用的方法

File: sys-exitfunc-example-1.py

import sys

def exitfunc():
    print "world"

sys.exitfunc = exitfunc

print "hello"
sys.exit(1)
print "there" # never printed # 不會被 print

hello
world

在 Python 2.0 之後, 你可使用 atexit 模塊來註冊多個退出處理函數.


1.14. atexit 模塊

(用於2.0版本及以上) atexit 模塊容許你註冊一個或多個終止函數(暫且這麼叫), 這些函數將在解釋器終止前被自動調用.

調用 register 函數, 即可以將函數註冊爲終止函數, 如 Example 1-78 所示. 你也能夠添加更多的參數, 這些將做爲 exit 函數的參數傳遞.

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

File: atexit-example-1.py

import atexit

def exit(*args):
    print "exit", args

# register two exit handler
atexit.register(exit)
atexit.register(exit, 1)
atexit.register(exit, "hello", "world")

exit ('hello', 'world')
exit (1,)
exit ()

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


1.15. time 模塊

time 模塊提供了一些處理日期和一天內時間的函數. 它是創建在 C 運行時庫的簡單封裝.

給定的日期和時間能夠被表示爲浮點型(從參考時間, 一般是 1970.1.1 到如今通過的秒數. 即 Unix 格式), 或者一個表示時間的 struct (類元組).

1.15.1. 得到當前時間

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

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

File: time-example-1.py

import time

now = time.time()

print now, "seconds since", time.gmtime(0)[:6]
print
print "or in other words:"
print "- local time:", time.localtime(now)
print "- utc:", time.gmtime(now)

937758359.77 seconds since (1970, 1, 1, 0, 0, 0)

or in other words:
- local time: (1999, 9, 19, 18, 25, 59, 6, 262, 1)
- utc: (1999, 9, 19, 16, 25, 59, 6, 262, 0)

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

1.15.2. 將時間值轉換爲字符串

你可使用標準的格式化字符串把時間對象轉換爲字符串, 不過 time 模塊已經提供了許多標準轉換函數, 如 Example 1-80 所示.

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

File: time-example-2.py

import time

now = time.localtime(time.time())

print time.asctime(now)
print time.strftime("%y/%m/%d %H:%M", now)
print time.strftime("%a %b %d", now)
print time.strftime("%c", now)
print time.strftime("%I %p", now)
print time.strftime("%Y-%m-%d %H:%M:%S %Z", now)

# do it by hand...
year, month, day, hour, minute, second, weekday, yearday, daylight = now
print "%04d-%02d-%02d" % (year, month, day)
print "%02d:%02d:%02d" % (hour, minute, second)
print ("MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN")[weekday], yearday

Sun Oct 10 21:39:24 1999
99/10/10 21:39
Sun Oct 10
Sun Oct 10 21:39:24 1999
09 PM
1999-10-10 21:39:24 CEST
1999-10-10
21:39:24
SUN 283

1.15.3. 將字符串轉換爲時間對象

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

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

File: time-example-6.py

import time

# make sure we have a strptime function!
# 確認有函數 strptime
try:
    strptime = time.strptime
except AttributeError:
    from strptime import strptime

print strptime("31 Nov 00", "%d %b %y")
print strptime("1 Jan 70 1:30pm", "%d %b %y %I:%M%p")

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

1.15.3.2. Example 1-82. strptime 實現

File: strptime.py

import re
import string

MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
          "Sep", "Oct", "Nov", "Dec"]

SPEC = {
    # map formatting code to a regular expression fragment
    "%a": "(?P<weekday>[a-z]+)",
    "%A": "(?P<weekday>[a-z]+)",
    "%b": "(?P<month>[a-z]+)",
    "%B": "(?P<month>[a-z]+)",
    "%C": "(?P<century>/d/d?)",
    "%d": "(?P<day>/d/d?)",
    "%D": "(?P<month>/d/d?)/(?P<day>/d/d?)/(?P<year>/d/d)",
    "%e": "(?P<day>/d/d?)",
    "%h": "(?P<month>[a-z]+)",
    "%H": "(?P<hour>/d/d?)",
    "%I": "(?P<hour12>/d/d?)",
    "%j": "(?P<yearday>/d/d?/d?)",
    "%m": "(?P<month>/d/d?)",
    "%M": "(?P<minute>/d/d?)",
    "%p": "(?P<ampm12>am|pm)",
    "%R": "(?P<hour>/d/d?):(?P<minute>/d/d?)",
    "%S": "(?P<second>/d/d?)",
    "%T": "(?P<hour>/d/d?):(?P<minute>/d/d?):(?P<second>/d/d?)",
    "%U": "(?P<week>/d/d)",
    "%w": "(?P<weekday>/d)",
    "%W": "(?P<weekday>/d/d)",
    "%y": "(?P<year>/d/d)",
    "%Y": "(?P<year>/d/d/d/d)",
    "%%": "%"
}

class TimeParser:
    def _ _init_ _(self, format):
        # convert strptime format string to regular expression
        format = string.join(re.split("(?:/s|%t|%n)+", format))
        pattern = []
        try:
            for spec in re.findall("%/w|%%|.", format):
                if spec[0] == "%":
                    spec = SPEC[spec]
                pattern.append(spec)
        except KeyError:
            raise ValueError, "unknown specificer: %s" % spec
        self.pattern = re.compile("(?i)" + string.join(pattern, ""))
    def match(self, daytime):
        # match time string
        match = self.pattern.match(daytime)
        if not match:
            raise ValueError, "format mismatch"
        get = match.groupdict().get
        tm = [0] * 9
        # extract date elements
        y = get("year")
        if y:
            y = int(y)
            if y < 68:
                y = 2000 + y
            elif y < 100:
                y = 1900 + y
            tm[0] = y
        m = get("month")
        if m:
            if m in MONTHS:
                m = MONTHS.index(m) + 1
            tm[1] = int(m)
        d = get("day")
        if d: tm[2] = int(d)
        # extract time elements
        h = get("hour")
        if h:
            tm[3] = int(h)
        else:
            h = get("hour12")
            if h:
                h = int(h)
                if string.lower(get("ampm12", "")) == "pm":
                    h = h + 12
                tm[3] = h
        m = get("minute")
        if m: tm[4] = int(m)
        s = get("second")
        if s: tm[5] = int(s)
        # ignore weekday/yearday for now
        return tuple(tm)

def strptime(string, format="%a %b %d %H:%M:%S %Y"):
    return TimeParser(format).match(string)

if _ _name_ _ == "_ _main_ _":
    # try it out
    import time
    print strptime("2000-12-20 01:02:03", "%Y-%m-%d %H:%M:%S")
    print strptime(time.ctime(time.time()))

(2000, 12, 20, 1, 2, 3, 0, 0, 0)
(2000, 11, 15, 12, 30, 45, 0, 0, 0)

1.15.4. 轉換時間值

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

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

File: time-example-3.py

import time

t0 = time.time()
tm = time.localtime(t0)

print tm

print t0
print time.mktime(tm)

(1999, 9, 9, 0, 11, 8, 3, 252, 1)
936828668.16
936828668.0

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

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

File: time-example-4.py

import time

def _d(y, m, d, days=(0,31,59,90,120,151,181,212,243,273,304,334,365)):
    # map a date to the number of days from a reference point
    return (((y - 1901)*1461)/4 + days[m-1] + d +
        ((m > 2 and not y % 4 and (y % 100 or not y % 400)) and 1))

def timegm(tm, epoch=_d(1970,1,1)):
    year, month, day, h, m, s = tm[:6]
    assert year >= 1970
    assert 1 <= month <= 12
    return (_d(year, month, day) - epoch)*86400 + h*3600 + m*60 + s

t0 = time.time()
tm = time.gmtime(t0)

print tm

print t0
print timegm(tm)

(1999, 9, 8, 22, 12, 12, 2, 251, 0)
936828732.48
936828732

從 1.6 版本開始, calendar 模塊提供了一個相似的函數 calendar.timegm .

1.15.5. Timing 相關

time 模塊能夠計算 Python 程序的執行時間, 如 Example 1-85 所示. 你能夠測量 "wall time" (real world time), 或是"進程時間" (消耗的 CPU 時間).

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

File: time-example-5.py

import time

def procedure():
    time.sleep(2.5)

# measure process time
t0 = time.clock()
procedure()
print time.clock() - t0, "seconds process time"

# measure wall time
t0 = time.time()
procedure()
print time.time() - t0, "seconds wall time"

0.0 seconds process time
2.50903499126 seconds wall time

並非全部的系統都能測量真實的進程時間. 一些系統中(包括 Windows ), clock 函數一般測量從程序啓動到測量時的 wall time.

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

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


1.16. types 模塊

types 模塊包含了標準解釋器定義的全部類型的類型對象, 如 Example 1-86 所示. 同一類型的全部對象共享一個類型對象. 你可使用 is 來檢查一個對象是否是屬於某個給定類型.

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

File: types-example-1.py

import types

def check(object):
    print object,

    if type(object) is types.IntType:
        print "INTEGER",
    if type(object) is types.FloatType:
        print "FLOAT",
    if type(object) is types.StringType:
        print "STRING",
    if type(object) is types.ClassType:
        print "CLASS",
    if type(object) is types.InstanceType:
        print "INSTANCE",
    print

check(0)
check(0.0)
check("0")

class A:
    pass

class B:
    pass

check(A)
check(B)

a = A()
b = B()

check(a)
check(b)

0 INTEGER
0.0 FLOAT
0 STRING
A CLASS
B CLASS
<A instance at 796960> INSTANCE
<B instance at 796990> INSTANCE

注意全部的類都具備相同的類型, 全部的實例也是同樣. 要測試一個類或者實例所屬的類, 可使用內建的 issubclass 和 isinstance 函數.

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


1.17. gc 模塊

(可選, 2.0 及之後版本) gc 模塊提供了到內建循環垃圾收集器的接口.

Python 使用引用記數來跟蹤何時銷燬一個對象; 一個對象的最後一個引用一旦消失, 這個對象就會被銷燬.

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

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

1.17.0.1. Example 1-87. 使用 gc 模塊收集循環引用垃圾

File: gc-example-1.py

import gc

# create a simple object that links to itself
class Node:

    def _ _init_ _(self, name):
        self.name = name
        self.parent = None
        self.children = []

    def addchild(self, node):
        node.parent = self
        self.children.append(node)

    def _ _repr_ _(self):
        return "<Node %s at %x>" % (repr(self.name), id(self))

# set up a self-referencing structure
root = Node("monty")

root.addchild(Node("eric"))
root.addchild(Node("john"))
root.addchild(Node("michael"))

# remove our only reference
del root

print gc.collect(), "unreachable objects"
print gc.collect(), "unreachable objects"

12 unreachable objects
0 unreachable objects

若是你肯定你的程序不會建立自引用的數據結構, 你可使用 gc.disable 函數禁用垃圾收集, 調用這個函數之後, Python 的工做方式將與 1.5.2 或更早的版本相同.


2. 更多標準模塊

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

2.1. 概覽

本章敘述了許多在 Python 程序中普遍使用的模塊. 固然, 在大型的 Python 程序中不使用這些模塊也是能夠的, 但若是使用會節省你很多時間.

2.1.1. 文件與流

fileinput 模塊可讓你更簡單地向不一樣的文件寫入內容. 該模塊提供了一個簡單的封裝類, 一個簡單的 for-in 語句就能夠循環獲得一個或多個文本文件的內容.

StringIO 模塊 (以及 cStringIO 模塊, 做爲一個的變種) 實現了一個工做在內存的文件對象. 你能夠在不少地方用 StringIO 對象替換普通的文件對象.

2.1.2. 類型封裝

UserDict , UserList , 以及 UserString 是對應內建類型的頂層簡單封裝. 和內建類型不一樣的是, 這些封裝是能夠被繼承的. 這在你須要一個和內建類型行爲類似但由額外新方法的類的時候頗有用.

2.1.3. 隨機數字

random 模塊提供了一些不一樣的隨機數字生成器. whrandom 模塊與此類似, 但容許你建立多個生成器對象.

[!Feather 注: whrandom 在版本 2.1 時聲明不支持. 請使用 random 替代.]

2.1.4. 加密算法

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

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

rotor 模塊提供了簡單的雙向加密. 版本 2.4 之後的朋友能夠不用忙活了.

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

2.2. fileinput 模塊

fileinput 模塊容許你循環一個或多個文本文件的內容, 如 Example 2-1 所示.

2.2.0.1. Example 2-1. 使用 fileinput 模塊循環一個文本文件

File: fileinput-example-1.py

import fileinput
import sys

for line in fileinput.input("samples/sample.txt"):
    sys.stdout.write("-> ")
    sys.stdout.write(line)

-> We will perhaps eventually be writing only small
-> modules which are identified by name as they are
-> used to build larger ones, so that devices like
-> indentation, rather than delimiters, might become
-> feasible for expressing local structure in the
-> source language.
->      -- Donald E. Knuth, December 1974

你也可使用 fileinput 模塊得到當前行的元信息 (meta information). 其中包括 isfirstline , filename , lineno , 如 Example 2-2 所示.

2.2.0.2. Example 2-2. 使用 fileinput 模塊處理多個文本文件

File: fileinput-example-2.py

import fileinput
import glob
import string, sys

for line in fileinput.input(glob.glob("samples/*.txt")):
    if fileinput.isfirstline(): # first in a file?
        sys.stderr.write("-- reading %s --/n" % fileinput.filename())
    sys.stdout.write(str(fileinput.lineno()) + " " + string.upper(line))

-- reading samples/sample.txt --
1 WE WILL PERHAPS EVENTUALLY BE WRITING ONLY SMALL
2 MODULES WHICH ARE IDENTIFIED BY NAME AS THEY ARE
3 USED TO BUILD LARGER ONES, SO THAT DEVICES LIKE
4 INDENTATION, RATHER THAN DELIMITERS, MIGHT BECOME
5 FEASIBLE FOR EXPRESSING LOCAL STRUCTURE IN THE
6 SOURCE LANGUAGE.
7    -- DONALD E. KNUTH, DECEMBER 1974

文本文件的替換操做很簡單. 只須要把 inplace 關鍵字參數設置爲 1 , 傳遞給 input 函數, 該模塊會幫你作好一切. Example 2-3 展現了這些.

2.2.0.3. Example 2-3. 使用 fileinput 模塊將 CRLF 改成 LF

File: fileinput-example-3.py

import fileinput, sys

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

2.3. shutil 模塊

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

2.3.0.1. Example 2-4. 使用 shutil 複製文件

File: shutil-example-1.py

import shutil
import os

for file in os.listdir("."):
    if os.path.splitext(file)[1] == ".py":
        print file
        shutil.copy(file, os.path.join("backup", file))

aifc-example-1.py
anydbm-example-1.py
array-example-1.py
...

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

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

File: shutil-example-2.py

import shutil
import os

SOURCE = "samples"
BACKUP = "samples-bak"

# create a backup directory
shutil.copytree(SOURCE, BACKUP)

print os.listdir(BACKUP)

# remove it
shutil.rmtree(BACKUP)

print os.listdir(BACKUP)

['sample.wav', 'sample.jpg', 'sample.au', 'sample.msg', 'sample.tgz',
...
Traceback (most recent call last):
 File "shutil-example-2.py", line 17, in ?
   print os.listdir(BACKUP)
os.error: No such file or directory

2.4. tempfile 模塊

Example 2-6 中展現的 tempfile 模塊容許你快速地建立名稱惟一的臨時文件供使用.

2.4.0.1. Example 2-6. 使用 tempfile 模塊建立臨時文件

File: tempfile-example-1.py

import tempfile
import os

tempfile = tempfile.mktemp()

print "tempfile", "=>", tempfile

file = open(tempfile, "w+b")
file.write("*" * 1000)
file.seek(0)
print len(file.read()), "bytes"
file.close()

try:
    # must remove file when done
    os.remove(tempfile)
except OSError:
    pass

tempfile => C:/TEMP/~160-1
1000 bytes

TemporaryFile 函數會自動挑選合適的文件名, 並打開文件, 如 Example 2-7 所示. 並且它會確保該文件在關閉的時候會被刪除. (在 Unix 下, 你能夠刪除一個已打開的文件, 這 時文件關閉時它會被自動刪除. 在其餘平臺上, 這經過一個特殊的封裝類實現.)

2.4.0.2. Example 2-7. 使用 tempfile 模塊打開臨時文件

File: tempfile-example-2.py

import tempfile

file = tempfile.TemporaryFile()

for i in range(100):
    file.write("*" * 100)

file.close() # removes the file!

2.5. StringIO 模塊

Example 2-8 展現了 StringIO 模塊的使用. 它實現了一個工做在內存的文件對象 (內存文件). 在大多須要標準文件對象的地方均可以使用它來替換.

2.5.0.1. Example 2-8. 使用 StringIO 模塊從內存文件讀入內容

File: stringio-example-1.py

import StringIO

MESSAGE = "That man is depriving a village somewhere of a computer scientist."

file = StringIO.StringIO(MESSAGE)

print file.read()

That man is depriving a village somewhere of a computer scientist.

StringIO 類實現了內建文件對象的全部方法, 此外還有 getvalue 方法用來返回它內部的字符串值. Example 2-9 展現了這個方法.

2.5.0.2. Example 2-9. 使用 StringIO 模塊向內存文件寫入內容

File: stringio-example-2.py

import StringIO

file = StringIO.StringIO()
file.write("This man is no ordinary man. ")
file.write("This is Mr. F. G. Superman.")

print file.getvalue()

This man is no ordinary man. This is Mr. F. G. Superman.

StringIO 能夠用於從新定向 Python 解釋器的輸出, 如 Example 2-10 所示.

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

File: stringio-example-3.py

import StringIO
import string, sys

stdout = sys.stdout

sys.stdout = file = StringIO.StringIO()

print """
According to Gbaya folktales, trickery and guile
are the best ways to defeat the python, king of
snakes, which was hatched from a dragon at the
world's start. -- National Geographic, May 1997
"""

sys.stdout = stdout

print string.upper(file.getvalue())

ACCORDING TO GBAYA FOLKTALES, TRICKERY AND GUILE
ARE THE BEST WAYS TO DEFEAT THE PYTHON, KING OF
SNAKES, WHICH WAS HATCHED FROM A DRAGON AT THE
WORLD'S START. -- NATIONAL GEOGRAPHIC, MAY 1997

2.6. cStringIO 模塊

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

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

File: cstringio-example-1.py

import cStringIO

MESSAGE = "That man is depriving a village somewhere of a computer scientist."

file = cStringIO.StringIO(MESSAGE)

print file.read()

That man is depriving a village somewhere of a computer scientist.

爲了讓你的代碼儘量快, 但同時保證兼容低版本的 Python ,你可使用一個小技巧在 cStringIO 不可用時啓用 StringIO 模塊, 如 Example 2-12 所示.

2.6.0.2. Example 2-12. 後退至 StringIO

File: cstringio-example-2.py

try:
    import cStringIO
    StringIO = cStringIO
except ImportError:
    import StringIO

print StringIO

<module  'StringIO' (built-in)>

2.7. mmap 模塊

(2.0 新增) mmap 模塊提供了操做系統內存映射函數的接口, 如 Example 2-13 所示. 映射區域的行爲和字符串對象相似, 但數據是直接從文件讀取的.

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

File: mmap-example-1.py

import mmap
import os

filename = "samples/sample.txt"

file = open(filename, "r+")
size = os.path.getsize(filename)

data = mmap.mmap(file.fileno(), size)

# basics
print data
print len(data), size

# use slicing to read from the file
# 使用切片操做讀取文件
print repr(data[:10]), repr(data[:10])

# or use the standard file interface
# 或使用標準的文件接口
print repr(data.read(10)), repr(data.read(10))

<mmap object at 008A2A10>
302 302
'We will pe' 'We will pe'
'We will pe' 'rhaps even'

在 Windows 下, 這個文件必須以既可讀又可寫的模式打開( `r+` , `w+` , 或 `a+` ), 不然 mmap 調用會失敗.

[!Feather 注: 經本人測試, a+ 模式是徹底能夠的, 原文只有 r+ 和 w+]

Example 2-14 展現了內存映射區域的使用, 在不少地方它均可以替換普通字符串使用, 包括正則表達式和其餘字符串操做.

2.7.0.2. Example 2-14. 對映射區域使用字符串方法和正則表達式

File: mmap-example-2.py

import mmap
import os, string, re

def mapfile(filename):
    file = open(filename, "r+")
    size = os.path.getsize(filename)
    return mmap.mmap(file.fileno(), size)

data = mapfile("samples/sample.txt")

# search
index = data.find("small")
print index, repr(data[index-5:index+15])

# regular expressions work too!
m = re.search("small", data)
print m.start(), m.group()

43 'only small/015/012modules '
43 small

2.8. UserDict 模塊

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

Example 2-15 展現了一個加強的字典類, 容許對字典使用 "加/+" 操做並提供了接受關鍵字參數的構造函數.

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

File: userdict-example-1.py

import UserDict

class FancyDict(UserDict.UserDict):

    def _ _init_ _(self, data = {}, **kw):
        UserDict.UserDict._ _init_ _(self)
        self.update(data)
        self.update(kw)

    def _ _add_ _(self, other):
        dict = FancyDict(self.data)
        dict.update(b)
        return dict

a = FancyDict(a = 1)
b = FancyDict(b = 2)

print a + b

{'b': 2, 'a': 1}

2.9. UserList 模塊

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

在 Example 2-16 中, AutoList 實例相似一個普通的列表對象, 但它容許你經過賦值爲列表添加項目.

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

File: userlist-example-1.py

import UserList

class AutoList(UserList.UserList):

    def _ _setitem_ _(self, i, item):
        if i == len(self.data):
            self.data.append(item)
        else:
            self.data[i] = item

list = AutoList()

for i in range(10):
    list[i] = i

print list

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

2.10. UserString 模塊

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

注意 MutableString 並非效率很好, 許多操做是經過切片和字符串鏈接實現的. 若是性能很對你的腳原本說重要的話, 你最好使用字符串片段的列表或者array 模塊. Example 2-17 展現了 UserString 模塊.

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

File: userstring-example-1.py

import UserString

class MyString(UserString.MutableString):

    def append(self, s):
        self.data = self.data + s

    def insert(self, index, s):
        self.data = self.data[index:] + s + self.data[index:]

    def remove(self, s):
        self.data = self.data.replace(s, "")

file = open("samples/book.txt")
text = file.read()
file.close()

book = MyString(text)

for bird in ["gannet", "robin", "nuthatch"]:
    book.remove(bird)

print book

...
C: The one without the !
P: The one without the -!!! They've ALL got the !! It's a
Standard British Bird, the , it's in all the books!!!
...

2.11. traceback 模塊

Example 2-18 展現了 traceback 模塊容許你在程序裏打印異常的跟蹤返回 (Traceback)信息, 相似未捕獲異常時解釋器所作的. 如 Example 2-18 所示.

2.11.0.1. Example 2-18. 使用 traceback 模塊打印跟蹤返回信息

File: traceback-example-1.py

# note! importing the traceback module messes up the
# exception state, so you better do that here and not
# in the exception handler
# 注意! 導入 traceback 會清理掉異常狀態, 因此
# 最好別在異常處理代碼中導入該模塊
import traceback

try:
    raise SyntaxError, "example"
except:
    traceback.print_exc()

Traceback (innermost last):
  File "traceback-example-1.py", line 7, in ?
SyntaxError: example

Example 2-19 使用 StringIO 模塊將跟蹤返回信息放在字符串中.

2.11.0.2. Example 2-19. 使用 traceback 模塊將跟蹤返回信息複製到字符串

File: traceback-example-2.py

import traceback
import StringIO

try:
    raise IOError, "an i/o error occurred"
except:
    fp = StringIO.StringIO()
    traceback.print_exc(file=fp)
    message = fp.getvalue()

    print "failure! the error was:", repr(message)

failure! the error was: 'Traceback (innermost last):/012  File
"traceback-example-2.py", line 5, in ?/012IOError: an i/o error
occurred/012'

你可使用 extract_tb 函數格式化跟蹤返回信息, 獲得包含錯誤信息的列表, 如 Example 2-20 所示.

2.11.0.3. Example 2-20. 使用 traceback Module 模塊編碼 Traceback 對象

File: traceback-example-3.py

import traceback
import sys

def function():
    raise IOError, "an i/o error occurred"

try:
    function()
except:
    info = sys.exc_info()
    for file, lineno, function, text in traceback.extract_tb(info[2]):
        print file, "line", lineno, "in", function
        print "=>", repr(text)
    print "** %s: %s" % info[:2]

traceback-example-3.py line 8 in ?
=> 'function()'
traceback-example-3.py line 5 in function
=> 'raise IOError, "an i/o error occurred"'
** exceptions.IOError: an i/o error occurred

2.12. errno 模塊

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

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

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

File: errno-example-1.py

import errno

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

no such file

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

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

File: errno-example-2.py

import errno

try:
    fp = open("no.such.file")
except IOError, (error, message):
    print error, repr(message)
    print errno.errorcode[error]

# 2 'No such file or directory'
# ENOENT

2.13. getopt 模塊

getopt 模塊包含用於抽出命令行選項和參數的函數, 它能夠處理多種格式的選項. 如 Example 2-23 所示.

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

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

File: getopt-example-1.py

import getopt
import sys

# simulate command-line invocation
# 模仿命令行參數
sys.argv = ["myscript.py", "-l", "-d", "directory", "filename"]

# process options
# 處理選項
opts, args = getopt.getopt(sys.argv[1:], "ld:")

long = 0
directory = None

for o, v in opts:
    if o == "-l":
        long = 1
    elif o == "-d":
        directory = v

print "long", "=", long
print "directory", "=", directory
print "arguments", "=", args

long = 1
directory = directory
arguments = ['filename']

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

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

File: getopt-example-2.py

import getopt
import sys

# simulate command-line invocation
# 模仿命令行參數
sys.argv = ["myscript.py", "--echo", "--printer", "lp01", "message"]

opts, args = getopt.getopt(sys.argv[1:], "ep:", ["echo", "printer="])

# process options
# 處理選項
echo = 0
printer = None

for o, v in opts:
    if o in ("-e", "--echo"):
        echo = 1
    elif o in ("-p", "--printer"):
        printer = v

print "echo", "=", echo
print "printer", "=", printer
print "arguments", "=", args

echo = 1
printer = lp01
arguments = ['message']
[!Feather 注: 我不知道你們明白沒, 能夠本身試下:
myscript.py -e -p lp01 message
myscript.py --echo --printer=lp01 message
]

2.14. getpass 模塊

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

getpass(prompt) 會顯示提示字符串, 關閉鍵盤的屏幕反饋, 而後讀取密碼. 若是提示參數省略, 那麼它將打印出 "Password:".

getuser() 得到當前用戶名, 若是可能的話.

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

File: getpass-example-1.py

import getpass

usr = getpass.getuser()

pwd = getpass.getpass("enter password for user %s: " % usr)

print usr, pwd

enter password for user mulder:
mulder trustno1

2.15. glob 模塊

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

這裏的模式和正則表達式相似, 但更簡單. 星號(*) 匹配零個或更多個字符, 問號(?) 匹配單個字符. 你也可使用方括號來指定字符範圍, 例如 [0-9] 表明一個數字. 其餘全部字符都表明它們自己.

glob(pattern) 返回知足給定模式的全部文件的列表. Example 2-26 展現了它的用法.

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

File: glob-example-1.py

import glob

for file in glob.glob("samples/*.jpg"):
    print file

samples/sample.jpg

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


2.16. fnmatch 模塊

fnmatch 模塊使用模式來匹配文件名. 如 Example 2-27 所示.

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

2.16.0.1. Example 2-27. 使用 fnmatch 模塊匹配文件

File: fnmatch-example-1.py

import fnmatch
import os

for file in os.listdir("samples"):
    if fnmatch.fnmatch(file, "*.jpg"):
        print file

sample.jpg

Example 2-28 中的 translate 函數能夠將一個文件匹配模式轉換爲正則表達式.

2.16.0.2. Example 2-28. 使用 fnmatch 模塊將模式轉換爲正則表達式

File: fnmatch-example-2.py

import fnmatch
import os, re

pattern = fnmatch.translate("*.jpg")

for file in os.listdir("samples"):
    if re.match(pattern, file):
        print file

print "(pattern was %s)" % pattern

sample.jpg
(pattern was .*/.jpg$)

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


2.17. random 模塊

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

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

基本隨機數生成器(基於 Wichmann 和 Hill , 1982 的數學運算理論) 能夠經過不少方法訪問, 如 Example 2-29 所示.

2.17.0.1. Example 2-29. 使用 random 模塊得到隨機數字

File: random-example-1.py

import random

for i in range(5):

    # random float: 0.0 <= number < 1.0
    print random.random(),

    # random float: 10 <= number < 20
    print random.uniform(10, 20),

    # random integer: 100 <= number <= 1000
    print random.randint(100, 1000),

    # random integer: even numbers in 100 <= number < 1000
    print random.randrange(100, 1000, 2)

0.946842713956 19.5910069381 709 172
0.573613195398 16.2758417025 407 120
0.363241598013 16.8079747714 916 580
0.602115173978 18.386796935 531 774
0.526767588533 18.0783794596 223 344

注意這裏的 randint 函數能夠返回上界, 而其餘函數老是返回小於上界的值. 全部函數都有可能返回下界值.

Example 2-30 展現了 choice 函數, 它用來從一個序列裏分揀出一個隨機項目. 它能夠用於列表, 元組, 以及其餘序列(固然, 非空的).

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

File: random-example-2.py

import random

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

2
3
1
9
1

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

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

File: random-example-4.py

import random

try:
    # available in 2.0 and later
    shuffle = random.shuffle
except AttributeError:
    def shuffle(x):
        for i in xrange(len(x)-1, 0, -1):
            # pick an element in x[:i+1] with which to exchange x[i]
            j = int(random.random() * (i+1))
            x[i], x[j] = x[j], x[i]

cards = range(52)

shuffle(cards)

myhand = cards[:5]

print myhand

[4, 8, 40, 12, 30]

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

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

File: random-example-3.py

import random

histogram = [0] * 20

# calculate histogram for gaussian
# noise, using average=5, stddev=1
for i in range(1000):
    i = int(random.gauss(5, 1) * 2)
    histogram[i] = histogram[i] + 1

# print the histogram
m = max(histogram)
for v in histogram:
    print "*" * (v * 50 / m)


****
**********
*************************
***********************************
************************************************
**************************************************
*************************************
***************************
*************
***
*

你能夠在 Python Library Reference 找到更多關於非恆定分佈隨機生成器函數的信息.

標準庫中提供的隨機數生成器都是僞隨機數生成器. 不過這對於不少目的來講已經足夠了, 好比模擬, 數值分析, 以及遊戲. 能夠肯定的是它不適合密碼學用途.

2.18. whrandom 模塊

這個模塊早在 2.1 就被聲明不同意, 早廢了. 請使用 random 代替. 
- Feather

Example 2-33 展現了 whrandom , 它提供了一個僞隨機數生成器. (基於 Wichmann 和 Hill, 1982 的數學運算理論). 除非你須要不共享狀態的多個生成器(如多線程程序), 請使用 random 模塊代替.

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

File: whrandom-example-1.py

import whrandom

# same as random
print whrandom.random()
print whrandom.choice([1, 2, 3, 5, 9])
print whrandom.uniform(10, 20)
print whrandom.randint(100, 1000)

0.113412062346
1
16.8778954689
799

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

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

File: whrandom-example-2.py

import whrandom

# initialize all generators with the same seed
rand1 = whrandom.whrandom(4,7,11)
rand2 = whrandom.whrandom(4,7,11)
rand3 = whrandom.whrandom(4,7,11)

for i in range(5):
    print rand1.random(), rand2.random(), rand3.random()

0.123993532536 0.123993532536 0.123993532536
0.180951499518 0.180951499518 0.180951499518
0.291924111809 0.291924111809 0.291924111809
0.952048889363 0.952048889363 0.952048889363
0.969794283643 0.969794283643 0.969794283643

2.19. md5 模塊

md5 (Message-Digest Algorithm 5)模塊用於計算信息密文(信息摘要).

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

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

File: md5-example-1.py

import md5

hash = md5.new()
hash.update("spam, spam, and eggs")

print repr(hash.digest())

 'L/005J/243/266/355/243u`/305r/203/267/020F/303'

注意這裏的校驗和是一個二進制字符串. Example 2-36 展現瞭如何得到一個十六進制或 base64 編碼的字符串.

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

File: md5-example-2.py

import md5
import string
import base64

hash = md5.new()
hash.update("spam, spam, and eggs")

value = hash.digest()
print hash.hexdigest()
# before 2.0, the above can be written as
# 在 2.0 前, 以上應該寫作:
# print string.join(map(lambda v: "%02x" % ord(v), value), "")

print base64.encodestring(value)

4c054aa3b6eda37560c57283b71046c3
TAVKo7bto3VgxXKDtxBGww==

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

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

File: md5-example-3.py

import md5
import string, random

def getchallenge():
    # generate a 16-byte long random string.  (note that the built-
    # in pseudo-random generator uses a 24-bit seed, so this is not
    # as good as it may seem...)
    # 生成一個 16 字節長的隨機字符串. 注意內建的僞隨機生成器
    # 使用的是 24 位的種子(seed), 因此這裏這樣用並很差..
    challenge = map(lambda i: chr(random.randint(0, 255)), range(16))
    return string.join(challenge, "")

def getresponse(password, challenge):
    # calculate combined digest for password and challenge
    # 計算密碼和質詢(challenge)的聯合密文
    m = md5.new()
    m.update(password)
    m.update(challenge)
    return m.digest()

#
# server/client communication
# 服務器/客戶端通信

# 1. client connects.  server issues challenge.
# 1. 客戶端鏈接, 服務器發佈質詢(challenge)

print "client:", "connect"

challenge = getchallenge()

print "server:", repr(challenge)

# 2. client combines password and challenge, and calculates
# the response.
# 2. 客戶端計算密碼和質詢(challenge)的組合後的密文

client_response = getresponse("trustno1", challenge)

print "client:", repr(client_response)

# 3. server does the same, and compares the result with the
# client response.  the result is a safe login in which the
# password is never sent across the communication channel.
# 3. 服務器作一樣的事, 而後比較結果與客戶端的返回, 
# 判斷是否容許用戶登錄. 這樣作密碼沒有在通信中明文傳輸.

server_response = getresponse("trustno1", challenge)

if server_response == client_response:
    print "server:", "login ok"

client: connect
server: '/334/352/227Z#/272/273/212KG/330/265/032>/311o'
client: "l'/305/240-x/245/237/035/225A/254/233/337/225/001"
server: login ok

Example 2-38 提供了 md5 的一個變種, 你能夠經過標記信息來判斷它是否在網絡傳輸過程當中被修改(丟失).

2.19.0.4. Example 2-38. 使用 md5 模塊檢查數據完整性

File: md5-example-4.py

import md5
import array

class HMAC_MD5:
    # keyed md5 message authentication

    def _ _init_ _(self, key):
        if len(key) > 64:
            key = md5.new(key).digest()
        ipad = array.array("B", [0x36] * 64)
        opad = array.array("B", [0x5C] * 64)
        for i in range(len(key)):
            ipad[i] = ipad[i] ^ ord(key[i])
            opad[i] = opad[i] ^ ord(key[i])
        self.ipad = md5.md5(ipad.tostring())
        self.opad = md5.md5(opad.tostring())

    def digest(self, data):
        ipad = self.ipad.copy()
        opad = self.opad.copy()
        ipad.update(data)
        opad.update(ipad.digest())
        return opad.digest()

#
# simulate server end
# 模擬服務器端

key = "this should be a well-kept secret"
message = open("samples/sample.txt").read()

signature = HMAC_MD5(key).digest(message)

# (send message and signature across a public network)
# (通過由網絡發送信息和簽名)

#
# simulate client end
#模擬客戶端

key = "this should be a well-kept secret"

client_signature = HMAC_MD5(key).digest(message)

if client_signature == signature:
    print "this is the original message:"
    print
    print message
else:
    print "someone has modified the message!!!"

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

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

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

2.20. sha 模塊

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

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

File: sha-example-1.py

import sha

hash = sha.new()
hash.update("spam, spam, and eggs")

print repr(hash.digest())
print hash.hexdigest()

'/321/333/003/026I/331/272-j/303/247/240/345/343Tvq/364/346/311'
d1db031649d9ba2d6ac3a7a0e5e3547671f4e6c9

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


2.21. crypt 模塊

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

Example 2-40 展現瞭如何使用 crypt.crypt 來加密一個密碼, 將密碼和 salt 組合起來而後傳遞給函數, 這裏的 salt 包含兩位隨機字符. 如今你能夠扔掉原密碼而只保存加密後的字符串了.

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

File: crypt-example-1.py

import crypt

import random, string

def getsalt(chars = string.letters + string.digits):
    # generate a random 2-character 'salt'
    # 生成隨機的 2 字符 'salt'
    return random.choice(chars) + random.choice(chars)

print crypt.crypt("bananas", getsalt())

'py8UGrijma1j6'

確認密碼時, 只須要用新密碼調用加密函數, 並取加密後字符串的前兩位做爲 salt 便可. 如 果結果和加密後字符串匹配, 那麼密碼就是正確的. Example 2-41使用 pwd 模塊來獲取已知用戶的加密後密碼.

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

File: crypt-example-2.py

import pwd, crypt

def login(user, password):
    "Check if user would be able to log in using password"
    try:
        pw1 = pwd.getpwnam(user)[1]
        pw2 = crypt.crypt(password, pw1[:2])
        return pw1 == pw2
    except KeyError:
        return 0 # no such user

user = raw_input("username:")
password = raw_input("password:")

if login(user, password):
    print "welcome", user
else:
    print "login failed"

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


2.22. rotor 模塊

這個模塊在 2.3 時被聲明不同意, 2.4 時廢了. 由於它的加密算法不安全. 
- Feather

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

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

File: rotor-example-1.py

import rotor

SECRET_KEY = "spam"
MESSAGE = "the holy grail"

r = rotor.newrotor(SECRET_KEY)

encoded_message = r.encrypt(MESSAGE)
decoded_message = r.decrypt(encoded_message)

print "original:", repr(MESSAGE)
print "encoded message:", repr(encoded_message)
print "decoded message:", repr(decoded_message)

original: 'the holy grail'
encoded message: '/227/271/244/015/305sw/3340/337/252/237/340U'
decoded message: 'the holy grail'

2.23. zlib 模塊

(可選) zlib 模塊爲 "zlib" 壓縮提供支持. (這種壓縮方法是 "deflate".)

Example 2-43 展現瞭如何使用 compress 和 decompress 函數接受字符串參數.

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

File: zlib-example-1.py

import zlib

MESSAGE = "life of brian"

compressed_message = zlib.compress(MESSAGE)
decompressed_message = zlib.decompress(compressed_message)

print "original:", repr(MESSAGE)
print "compressed message:", repr(compressed_message)
print "decompressed message:", repr(decompressed_message)

original: 'life of brian'
compressed message: 'x/234/313/311LKU/310OSH*/312L/314/003/000!/010/004/302'
decompressed message: 'life of brian'

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

2.23.0.2. Example 2-44. 使用 zlib 模塊壓縮多個不一樣類型文件

File: zlib-example-2.py

import zlib
import glob

for file in glob.glob("samples/*"):

    indata = open(file, "rb").read()
    outdata = zlib.compress(indata, zlib.Z_BEST_COMPRESSION)

    print file, len(indata), "=>", len(outdata),
    print "%d%%" % (len(outdata) * 100 / len(indata))

samples/sample.au 1676 => 1109 66%
samples/sample.gz 42 => 51 121%
samples/sample.htm 186 => 135 72%
samples/sample.ini 246 => 190 77%
samples/sample.jpg 4762 => 4632 97%
samples/sample.msg 450 => 275 61%
samples/sample.sgm 430 => 321 74%
samples/sample.tar 10240 => 125 1%
samples/sample.tgz 155 => 159 102%
samples/sample.txt 302 => 220 72%
samples/sample.wav 13260 => 10992 82%

你也能夠實時地壓縮或解壓縮數據, 如 Example 2-45 所示.

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

File: zlib-example-3.py

import zlib

encoder = zlib.compressobj()

data = encoder.compress("life")
data = data + encoder.compress(" of ")
data = data + encoder.compress("brian")
data = data + encoder.flush()

print repr(data)
print repr(zlib.decompress(data))

'x/234/313/311LKU/310OSH*/312L/314/003/000!/010/004/302'
'life of brian'

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

2.23.0.4. Example 2-46. 壓縮流的仿文件訪問方式

File: zlib-example-4.py

import zlib
import string, StringIO

class ZipInputStream:

    def _ _init_ _(self, file):
        self.file = file
        self._ _rewind()

    def _ _rewind(self):
        self.zip = zlib.decompressobj()
        self.pos = 0 # position in zipped stream
        self.offset = 0 # position in unzipped stream
        self.data = ""

    def _ _fill(self, bytes):
        if self.zip:
            # read until we have enough bytes in the buffer
            while not bytes or len(self.data) < bytes:
                self.file.seek(self.pos)
                data = self.file.read(16384)
                if not data:
                    self.data = self.data + self.zip.flush()
                    self.zip = None # no more data
                    break
                self.pos = self.pos + len(data)
                self.data = self.data + self.zip.decompress(data)

    def seek(self, offset, whence=0):
        if whence == 0:
            position = offset
        elif whence == 1:
            position = self.offset + offset
        else:
            raise IOError, "Illegal argument"
        if position < self.offset:
            raise IOError, "Cannot seek backwards"

        # skip forward, in 16k blocks
        while position > self.offset:
            if not self.read(min(position - self.offset, 16384)):
                break

    def tell(self):
        return self.offset

    def read(self, bytes = 0):
        self._ _fill(bytes)
        if bytes:
            data = self.data[:bytes]
            self.data = self.data[bytes:]
        else:
            data = self.data
            self.data = ""
        self.offset = self.offset + len(data)
        return data

    def readline(self):
        # make sure we have an entire line
        while self.zip and "/n" not in self.data:
            self._ _fill(len(self.data) + 512)
        i = string.find(self.data, "/n") + 1
        if i <= 0:
            return self.read()
        return self.read(i)

    def readlines(self):
        lines = []
        while 1:
            s = self.readline()
            if not s:
                break
            lines.append(s)
        return lines

#
# try it out

data = open("samples/sample.txt").read()
data = zlib.compress(data)

file = ZipInputStream(StringIO.StringIO(data))
for line in file.readlines():
    print line[:-1]

We will perhaps eventually be writing only small
modules which are identified by name as they are
used to build larger ones, so that devices like
indentation, rather than delimiters, might become
feasible for expressing local structure in the
source language.
    -- Donald E. Knuth, December 1974

2.24. code 模塊

code 模塊提供了一些用於模擬標準交互解釋器行爲的函數.

compile_command 與內建 compile 函數行爲類似, 但它會經過測試來保證你傳遞的是一個完成的 Python 語句.

在 Example 2-47 中, 咱們一行一行地編譯一個程序, 編譯完成後會執行所獲得的代碼對象 (code object). 程序代碼以下:

a = (
  1,
  2,
  3
)
print a

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

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

File: code-example-1.py

import code
import string

# 
SCRIPT = [
    "a = (",
    "  1,",
    "  2,",
    "  3 ",
    ")",
    "print a"
]

script = ""

for line in SCRIPT:
    script = script + line + "/n"
    co = code.compile_command(script, "<stdin>", "exec")
    if co:
        # got a complete statement.  execute it!
        print "-"*40
        print script,
        print "-"*40
        exec co
        script = ""

----------------------------------------
a = (
  1,
  2,
  3 
)
----------------------------------------
----------------------------------------
print a
----------------------------------------
(1, 2, 3)

InteractiveConsole 類實現了一個交互控制檯, 相似你啓動的 Python 解釋器交互模式.

控制檯能夠是活動的(自動調用函數到達下一行) 或是被動的(當有新數據時調用 push 方法). 默認使用內建的 raw_input 函數. 若是你想使用另個輸入函數, 你可使用相同的名稱重載這個方法. Example 2-48 展現瞭如何使用 code 模塊來模擬交互解釋器.

2.24.0.2. Example 2-48. 使用 code 模塊模擬交互解釋器

File: code-example-2.py

import code

console = code.InteractiveConsole()
console.interact()

Python 1.5.2
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
(InteractiveConsole)
>>> a = (
...     1,
...     2,
...     3
... )
>>> print a
(1, 2, 3)

Example 2-49 中的腳本定義了一個 keyboard 函數. 它容許你在程序中手動控制交互解釋器.

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

File: code-example-3.py

def keyboard(banner=None):
    import code, sys

    # use exception trick to pick up the current frame
    try:
        raise None
    except:
        frame = sys.exc_info()[2].tb_frame.f_back

    # evaluate commands in current namespace
    namespace = frame.f_globals.copy()
    namespace.update(frame.f_locals)

    code.interact(banner=banner, local=namespace)

def func():
    print "START"
    a = 10
    keyboard()
    print "END"

func()

START
Python 1.5.2
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
(InteractiveConsole)
>>> print a
10
>>> print keyboard
<function keyboard at 9032c8>
^Z
END

3. 線程和進程

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

3.1. 概覽

本章將介紹標準 Python 解釋器中所提供的線程支持模塊. 注意線程支持模塊是可選的, 有可能在一些 Python 解釋器中不可用.

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

3.1.1. 線程

執行 Python 程序的時候, 是按照從主模塊頂端向下執行的. 循環用於重複執行部分代碼, 函數和方法會將控制臨時移交到程序的另外一部分.

經過線程, 你的程序能夠在同時處理多個任務. 每一個線程都有它本身的控制流. 因此你能夠在一個線程裏從文件讀取數據, 另個向屏幕輸出內容.

爲了保證兩個線程能夠同時訪問相同的內部數據, Python 使用了 global interpreter lock (全局解釋器鎖). 在同一時間只可能有一個線程執行 Python 代碼; Python 其實是自動地在一段很短的時間後切換到下個線程執行, 或者等待 一個線程執行一項須要時間的操做(例如等待經過 socket 傳輸的數據, 或是從文件中讀取數據).

全局鎖事實上並不能避免你程序中的問題. 多個線程嘗試訪問相同的數據會致使異常 狀態. 例如如下的代碼:

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

若是不一樣的線程前後使用相同的 key 調用這裏的 getitem 方法, 那麼它們極可能會致使相同的參數調用兩次 create_new_item . 大多時候這樣作沒有問題, 但在某些時候會致使嚴重錯誤.

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

3.1.2. 進程

在大多現代操做系統中, 每一個程序在它自身的進程( process )內執行. 咱們經過在 shell 中鍵入命令或直接在菜單中選擇來執行一個程序/進程. Python 容許你在一個腳本內執行一個新的程序.

大多進程相關函數經過 os 模塊定義. 相關內容請參閱 第 1.4.4 小節 .


3.2. threading 模塊

(可選) threading 模塊爲線程提供了一個高級接口, 如 Example 3-1 所示. 它源自 Java 的線程實現. 和低級的 thread 模塊相同, 只有你在編譯解釋器時打開了線程支持纔可使用它 .

你只須要繼承 Thread 類, 定義好 run 方法, 就能夠建立一 個新的線程. 使用時首先建立該類的一個或多個實例, 而後調用 start 方法. 這樣每一個實例的 run 方法都會運行在它本身的線程裏.

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

File: threading-example-1.py

import threading
import time, random

class Counter:
    def _ _init_ _(self):
        self.lock = threading.Lock()
        self.value = 0

    def increment(self):
        self.lock.acquire() # critical section
        self.value = value = self.value + 1
        self.lock.release()
        return value

counter = Counter()

class Worker(threading.Thread):

    def run(self):
        for i in range(10):
            # pretend we're doing something that takes 10�00 ms
            value = counter.increment() # increment global counter
            time.sleep(random.randint(10, 100) / 1000.0)
            print self.getName(), "-- task", i, "finished", value

#
# try it

for i in range(10):
    Worker().start() # start a worker

Thread-1 -- task 0 finished 1
Thread-3 -- task 0 finished 3
Thread-7 -- task 0 finished 8
Thread-1 -- task 1 finished 7
Thread-4 -- task 0 Thread-5 -- task 0 finished 4
finished 5
Thread-8 -- task 0 Thread-6 -- task 0 finished 9
finished 6
...
Thread-6 -- task 9 finished 98
Thread-4 -- task 9 finished 99
Thread-9 -- task 9 finished 100

Example 3-1 使用了 Lock 對象來在全局 Counter 對象裏建立臨界區 (critical section). 若是刪除了 acquire 和 release 語句, 那麼 Counter 極可能不會到達 100.


3.3. Queue 模塊

Queue 模塊提供了一個線程安全的隊列 (queue) 實現, 如 Example 3-2 所示. 你能夠經過它在多個線程裏安全訪問同個對象.

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

File: queue-example-1.py

import threading
import Queue
import time, random

WORKERS = 2

class Worker(threading.Thread):

    def _ _init_ _(self, queue):
        self._ _queue = queue
        threading.Thread._ _init_ _(self)

    def run(self):
        while 1:
            item = self._ _queue.get()
            if item is None:
                break # reached end of queue

            # pretend we're doing something that takes 10�00 ms
            time.sleep(random.randint(10, 100) / 1000.0)

            print "task", item, "finished"

#
# try it

queue = Queue.Queue(0)

for i in range(WORKERS):
    Worker(queue).start() # start a worker

for i in range(10):
    queue.put(i)

for i in range(WORKERS):
    queue.put(None) # add end-of-queue markers

task 1 finished
task 0 finished
task 3 finished
task 2 finished
task 4 finished
task 5 finished
task 7 finished
task 6 finished
task 9 finished
task 8 finished

Example 3-3 展現瞭如何限制隊列的大小. 若是隊列滿了, 那麼控制主線程 (producer threads) 被阻塞, 等待項目被彈出 (pop off).

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

File: queue-example-2.py

import threading
import Queue

import time, random

WORKERS = 2

class Worker(threading.Thread):

    def _ _init_ _(self, queue):
        self._ _queue = queue
        threading.Thread._ _init_ _(self)

    def run(self):
        while 1:
            item = self._ _queue.get()
            if item is None:
                break # reached end of queue

            # pretend we're doing something that takes 10�00 ms
            time.sleep(random.randint(10, 100) / 1000.0)

            print "task", item, "finished"

#
# run with limited queue

queue = Queue.Queue(3)

for i in range(WORKERS):
    Worker(queue).start() # start a worker

for item in range(10):
    print "push", item
    queue.put(item)

for i in range(WORKERS):
    queue.put(None) # add end-of-queue markers

push 0
push 1
push 2
push 3
push 4
push 5
task 0 finished
push 6
task 1 finished
push 7
task 2 finished
push 8
task 3 finished
push 9
task 4 finished
task 6 finished
task 5 finished
task 7 finished
task 9 finished
task 8 finished

你能夠經過繼承 Queue 類來修改它的行爲. Example 3-4 爲咱們展現了一個簡單的具備優先級的隊列. 它接受一個元組做爲參數, 元組的第一個成員表示優先級(數值越小優先級越高).

3.3.0.3. Example 3-4. 使用 Queue 模塊實現優先級隊列

File: queue-example-3.py

import Queue
import bisect

Empty = Queue.Empty

class PriorityQueue(Queue.Queue):
    "Thread-safe priority queue"

    def _put(self, item):
        # insert in order
        bisect.insort(self.queue, item)

#
# try it

queue = PriorityQueue(0)

# add items out of order
queue.put((20, "second"))
queue.put((10, "first"))
queue.put((30, "third"))

# print queue contents
try:
    while 1:
        print queue.get_nowait()
except Empty:
    pass

third
second
first

Example 3-5 展現了一個簡單的堆棧 (stack) 實現 (末尾添加, 頭部彈出, 而非頭部添加, 頭部彈出).

3.3.0.4. Example 3-5. 使用 Queue 模塊實現一個堆棧

File: queue-example-4.py

import Queue

Empty = Queue.Empty

class Stack(Queue.Queue):
    "Thread-safe stack"
    
    def _put(self, item):
        # insert at the beginning of queue, not at the end
        self.queue.insert(0, item)

    # method aliases
    push = Queue.Queue.put
    pop = Queue.Queue.get
    pop_nowait = Queue.Queue.get_nowait

#
# try it

stack = Stack(0)

# push items on stack
stack.push("first")
stack.push("second")
stack.push("third")

# print stack contents
try:
    while 1:
        print stack.pop_nowait()
except Empty:
    pass

third
second
first

3.4. thread 模塊

(可選) thread 模塊提爲線程提供了一個低級 (low_level) 的接口, 如 Example 3-6 所示. 只有你在編譯解釋器時打開了線程支持纔可使用它. 若是沒有特殊須要, 最好使用高級接口 threading 模塊替代.

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

File: thread-example-1.py

import thread
import time, random

def worker():
    for i in range(50):
        # pretend we're doing something that takes 10�00 ms
        time.sleep(random.randint(10, 100) / 1000.0)
        print thread.get_ident(), "-- task", i, "finished"

#
# try it out!

for i in range(2):
    thread.start_new_thread(worker, ())

time.sleep(1)

print "goodbye!"

311 -- task 0 finished
265 -- task 0 finished
265 -- task 1 finished
311 -- task 1 finished
...
265 -- task 17 finished
311 -- task 13 finished
265 -- task 18 finished
goodbye!

注意當主程序退出的時候, 全部的線程也隨着退出. 而 threading 模塊不存在這個問題 . (該行爲可改變)


3.5. commands 模塊

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

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

File: commands-example-1.py

import commands

stat, output = commands.getstatusoutput("ls -lR")

print "status", "=>", stat
print "output", "=>", len(output), "bytes"

status => 0
output => 171046 bytes

3.6. pipes 模塊

(只用於 Unix) pipes 模塊提供了 "轉換管道 (conversion pipelines)" 的支持. 你能夠建立包含許多外部工具調用的管道來處理多個文件. 如 Example 3-8 所示.

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

File: pipes-example-1.py

import pipes

t = pipes.Template()

# create a pipeline
# 這裏 " - " 表明從標準輸入讀入內容
t.append("sort", "--")
t.append("uniq", "--")

# filter some text
# 這裏空字符串表明標準輸出
t.copy("samples/sample.txt", "")

Alan Jones (sensible party)
Kevin Phillips-Bong (slightly silly)
Tarquin Fin-tim-lin-bin-whin-bim-lin-bus-stop-F'tang-F'tang-Olé-Biscuitbarrel

3.7. popen2 模塊

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

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

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

File: popen2-example-1.py

import popen2, string

fin, fout = popen2.popen2("sort")

fout.write("foo/n")
fout.write("bar/n")
fout.close()

print fin.readline(),
print fin.readline(),
fin.close()

bar
foo

Example 3-10 展現瞭如何使用該模塊控制應用程序 .

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

File: popen2-example-2.py

import popen2
import string

class Chess:
    "Interface class for chesstool-compatible programs"

    def _ _init_ _(self, engine = "gnuchessc"):
        self.fin, self.fout = popen2.popen2(engine)
        s = self.fin.readline()
        if s != "Chess/n":
            raise IOError, "incompatible chess program"

    def move(self, move):
        self.fout.write(move + "/n")
        self.fout.flush()
        my = self.fin.readline()
        if my == "Illegal move":
            raise ValueError, "illegal move"
        his = self.fin.readline()
        return string.split(his)[2]

    def quit(self):
        self.fout.write("quit/n")
        self.fout.flush()

#
# play a few moves

g = Chess()

print g.move("a2a4")
print g.move("b2b3")

g.quit()

b8c6
e7e5

3.8. signal 模塊

你可使用 signal 模塊配置你本身的信號處理器 (signal handler), 如 Example 3-11 所示. 當解釋器收到某個信號時, 信號處理器會當即執行.

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

File: signal-example-1.py

import signal
import time

def handler(signo, frame):
    print "got signal", signo

signal.signal(signal.SIGALRM, handler)

# wake me up in two seconds
signal.alarm(2)

now = time.time()

time.sleep(200)

print "slept for", time.time() - now, "seconds"

got signal 14
slept for 1.99262607098 seconds

4. 數據表示

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

4.1. 概覽

本章描述了一些用於在 Python 對象和其餘數據表示類型間相互轉換的模塊. 這些模塊一般用於讀寫特定的文件格式或是儲存/取出 Python 變量.

4.1.1. 二進制數據

Python 提供了一些用於二進制數據解碼/編碼的模塊. struct 模塊用於在 二進制數據結構(例如 C 中的 struct )和 Python 元組間轉換. array 模塊將二進制數據陣列 ( C arrays )封裝爲 Python 序列對象.

4.1.2. 自描述格式

marshal 和 pickle 模塊用於在不一樣的 Python 程序間共享/傳遞數據.

marshal 模塊使用了簡單的自描述格式( Self-Describing Formats ), 它支持大多的內建數據類型, 包括 code 對象. Python 自身也使用了這個格式來儲存編譯後代碼( .pyc 文件).

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

4.1.3. 輸出格式

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

pprint 模塊幾乎能夠將任何 Python 數據結構很好地打印出來(提升可讀性).

repr 模塊能夠用來替換內建同名函數. 該模塊與內建函數不一樣的是它限制了不少輸出形式: 他只會 輸出字符串的前 30 個字符, 它只打印嵌套數據結構的幾個等級, 等等.

4.1.4. 編碼二進制數據

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


4.2. array 模塊

array 模塊實現了一個有效的陣列儲存類型. 陣列和列表相似, 但其中全部的項目必須爲相同的 類型. 該類型在陣列建立時指定.

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

4.2.0.1. Example 4-1. 使用 array 模塊將數列轉換爲字符串

File: array-example-1.py

import array

a = array.array("B", range(16)) # unsigned char
b = array.array("h", range(16)) # signed short

print a
print repr(a.tostring())

print b
print repr(b.tostring())

array('B', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
'/000/001/002/003/004/005/006/007/010/011/012/013/014/015/016/017'

array('h', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
'/000/000/001/000/002/000/003/000/004/000/005/000/006/000/007/000
/010/000/011/000/012/000/013/000/014/000/015/000/016/000/017/000'

array 對象能夠做爲一個普通列表對待, 如 Example 4-2 所示. 不過, 你不能鏈接兩個不一樣類型的陣列.

4.2.0.2. Example 4-2. 做爲普通序列操做陣列

File: array-example-2.py

import array

a = array.array("B", [1, 2, 3])

a.append(4)

a = a + a

a = a[2:-2]

print a
print repr(a.tostring())
for i in a:
    print i,

array('B', [3, 4, 1, 2])
'/003/004/001/002'
3 4 1 2

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

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

File: array-example-3.py

import array

a = array.array("i", "fish license") # signed integer

print a
print repr(a.tostring())
print a.tolist()

array('i', [1752394086, 1667853344, 1702063717])
'fish license'
[1752394086, 1667853344, 1702063717]

最後, Example 4-4 展現瞭如何使用該模塊判斷當前平臺的字節序( endianess ) .

4.2.0.4. Example 4-4. 使用 array 模塊判斷平臺字節序

File: array-example-4.py

import array

def little_endian():
    return ord(array.array("i",[1]).tostring()[0])

if little_endian():
    print "little-endian platform (intel, alpha)"
else:
    print "big-endian platform (motorola, sparc)"

big-endian platform (motorola, sparc)

Python 2.0 以及之後版本提供了 sys.byteorder 屬性, 能夠更簡單地判斷字節序 (屬性值爲 "little" 或 "big" ), 如 Example 4-5 所示.

4.2.0.5. Example 4-5. 使用 sys.byteorder 屬性判斷平臺字節序( Python 2.0 及之後)

File: sys-byteorder-example-1.py

import sys

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

big-endian platform (motorola, sparc)

4.3. struct 模塊

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

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

File: struct-example-1.py

import struct

# native byteorder
buffer = struct.pack("ihb", 1, 2, 3)
print repr(buffer)
print struct.unpack("ihb", buffer)

# data from a sequence, network byteorder
data = [1, 2, 3]
buffer = apply(struct.pack, ("!ihb",) + tuple(data))
print repr(buffer)
print struct.unpack("!ihb", buffer)

# in 2.0, the apply statement can also be written as:
# buffer = struct.pack("!ihb", *data)

'/001/000/000/000/002/000/003'
(1, 2, 3)
'/000/000/000/001/000/002/003'
(1, 2, 3)

4.4. xdrlib 模塊

xdrlib 模塊用於在 Python 數據類型和 Sun 的 external data representation (XDR) 間相互轉化, 如 Example 4-7 所示.

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

File: xdrlib-example-1.py

import xdrlib

#
# create a packer and add some data to it

p = xdrlib.Packer()
p.pack_uint(1)
p.pack_string("spam")

data = p.get_buffer()

print "packed:", repr(data)

#
# create an unpacker and use it to decode the data

u = xdrlib.Unpacker(data)

print "unpacked:", u.unpack_uint(), repr(u.unpack_string())

u.done()

packed: '/000/000/000/001/000/000/000/004spam'
unpacked: 1 'spam'

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

4.4.0.2. Example 4-8. 使用 xdrlib 模塊發送 RPC 調用包

File: xdrlib-example-2.py

import xdrlib

# some constants (see the RPC specs for details)
RPC_CALL = 1
RPC_VERSION = 2

MY_PROGRAM_ID = 1234 # assigned by Sun
MY_VERSION_ID = 1000
MY_TIME_PROCEDURE_ID = 9999

AUTH_NULL = 0

transaction = 1

p = xdrlib.Packer()

# send a Sun RPC call package
p.pack_uint(transaction)
p.pack_enum(RPC_CALL)
p.pack_uint(RPC_VERSION)
p.pack_uint(MY_PROGRAM_ID)
p.pack_uint(MY_VERSION_ID)
p.pack_uint(MY_TIME_PROCEDURE_ID)
p.pack_enum(AUTH_NULL)
p.pack_uint(0)
p.pack_enum(AUTH_NULL)
p.pack_uint(0)

print repr(p.get_buffer())

'/000/000/000/001/000/000/000/001/000/000/000/002/000/000/004/322
/000/000/003/350/000/000/'/017/000/000/000/000/000/000/000/000/000
/000/000/000/000/000/000/000'

4.5. marshal 模塊

marshal 模塊能夠把不連續的數據組合起來 - 與字符串相互轉化, 這樣它們就能夠寫入文件或是在網絡中傳輸. 如 Example 4-9 所示.

marshal 模塊使用了簡單的自描述格式. 對於每一個數據項目, 格式化後的字符串都包含一個類型代碼, 而後是一個或多個類型標識區域. 整數使用小字節序( little-endian order )儲存, 字符串儲存時和它自身內容長度相同(可能包含空字節), 元組由組成它的對象組合表示.

4.5.0.1. Example 4-9. 使用 marshal 模塊組合不連續數據

File: marshal-example-1.py

import marshal

value = (
    "this is a string",
    [1, 2, 3, 4],
    ("more tuples", 1.0, 2.3, 4.5),
    "this is yet another string"
    )

data = marshal.dumps(value)

# intermediate format
print type(data), len(data)

print "-"*50
print repr(data)
print "-"*50

print marshal.loads(data)

<type 'string'> 118
--------------------------------------------------
'(/004/000/000/000s/020/000/000/000this is a string
[/004/000/000/000i/001/000/000/000i/002/000/000/000
i/003/000/000/000i/004/000/000/000(/004/000/000/000
s/013/000/000/000more tuplesf/0031.0f/0032.3f/0034.
5s/032/000/000/000this is yet another string'
--------------------------------------------------
('this is a string', [1, 2, 3, 4], ('more tuples',
1.0, 2.3, 4.5), 'this is yet another string')

marshal 模塊還能夠處理 code 對象(它用於儲存預編譯的 Python 模塊). 如 Example 4-10 所示.

4.5.0.2. Example 4-10. 使用 marshal 模塊處理代碼

File: marshal-example-2.py

import marshal

script = """
print 'hello'
"""

code = compile(script, "<script>", "exec")

data = marshal.dumps(code)

# intermediate format
print type(data), len(data)

print "-"*50
print repr(data)
print "-"*50

exec marshal.loads(data)

<type 'string'> 81
--------------------------------------------------
'c/000/000/000/000/001/000/000/000s/017/000/000/00
0/177/000/000/177/002/000d/000/000GHd/001/000S(/00
2/000/000/000s/005/000/000/000helloN(/000/000/000/
000(/000/000/000/000s/010/000/000/000<script>s/001
/000/000/000?/002/000s/000/000/000/000'
--------------------------------------------------
hello

4.6. pickle 模塊

pickle 模塊同 marshal 模塊相同, 將數據連續化, 便於保存傳輸. 它比 marshal 要慢一些, 但它能夠處理類實例, 共享的元素, 以及遞歸數據結構等.

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

File: pickle-example-1.py

import pickle

value = (
    "this is a string",
    [1, 2, 3, 4],
    ("more tuples", 1.0, 2.3, 4.5),
    "this is yet another string"
    )

data = pickle.dumps(value)

# intermediate format
print type(data), len(data)

print "-"*50
print data
print "-"*50

print pickle.loads(data)

<type 'string'> 121
--------------------------------------------------
(S'this is a string'
p0
(lp1
I1
aI2
aI3
aI4
a(S'more tuples'
p2
F1.0
F2.3
F4.5
tp3
S'this is yet another string'
p4
tp5
.
--------------------------------------------------
('this is a string', [1, 2, 3, 4], ('more tuples',
1.0, 2.3, 4.5), 'this is yet another string')

不過另外一方面, pickle 不能處理 code 對象(能夠參閱 copy_reg 模塊來完成這個).

默認狀況下, pickle 使用急於文本的格式. 你也可使用二進制格式, 這樣數字和二進制 字符串就會以緊密的格式儲存, 這樣文件就會更小點. 如 Example 4-12所示.

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

File: pickle-example-2.py

import pickle
import math

value = (
    "this is a long string" * 100,
    [1.2345678, 2.3456789, 3.4567890] * 100
    )

# text mode
data = pickle.dumps(value)
print type(data), len(data), pickle.loads(data) == value

# binary mode
data = pickle.dumps(value, 1)
print type(data), len(data), pickle.loads(data) == value

4.7. cPickle 模塊

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

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

File: cpickle-example-1.py

try:
    import cPickle
    pickle = cPickle
except ImportError:
    import pickle

4.8. copy_reg 模塊

你可使用 copy_reg 模塊註冊你本身的擴展類型. 這樣 pickle 和 copy 模塊就會知道 如何處理非標準類型.

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

File: copy-reg-example-1.py

import pickle

CODE = """
print 'good evening'
"""

code = compile(CODE, "<string>", "exec")

exec code
exec pickle.loads(pickle.dumps(code))

good evening
Traceback (innermost last):
...
pickle.PicklingError: can't pickle 'code' objects

咱們能夠註冊一個 code 對象處理器來完成目標. 處理器應包含兩個部分: 一個 pickler , 接受 code 對象 並返回一個只包含簡單數據類型的元組, 以及一個unpickler , 做用相反, 接受這樣的元組做爲參數. 如 Example 4-14 所示.

4.8.0.1. Example 4-14. 使用 copy_reg 模塊實現 code 對象的 pickle 操做

File: copy-reg-example-2.py

import copy_reg
import pickle, marshal, types

#
# register a pickle handler for code objects

def code_unpickler(data):
    return marshal.loads(data)

def code_pickler(code):
    return code_unpickler, (marshal.dumps(code),)

copy_reg.pickle(types.CodeType, code_pickler, code_unpickler)

#
# try it out

CODE = """
print "suppose he's got a pointed stick"
"""

code = compile(CODE, "<string>", "exec")

exec code
exec pickle.loads(pickle.dumps(code))

suppose he's got a pointed stick
suppose he's got a pointed stick

若是你是在網絡中傳輸 pickle 後的數據, 那麼請確保自定義的 unpickler 在數據接收端也是可用的.

Example 4-15 展現瞭如何實現 pickle 一個打開的文件對象.

4.8.0.2. Example 4-15. 使用 copy_reg 模塊實現文件對象的 pickle 操做

File: copy-reg-example-3.py

import copy_reg
import pickle, types
import StringIO

#
# register a pickle handler for file objects

def file_unpickler(position, data):
    file = StringIO.StringIO(data)
    file.seek(position)
    return file

def file_pickler(code):
    position = file.tell()
    file.seek(0)
    data = file.read()
    file.seek(position)
    return file_unpickler, (position, data)

copy_reg.pickle(types.FileType, file_pickler, file_unpickler)

#
# try it out

file = open("samples/sample.txt", "rb")

print file.read(120),
print "<here>",
print pickle.loads(pickle.dumps(file)).read()

We will perhaps eventually be writing only small
modules, which are identified by name as they are
used to build larger <here> ones, so that devices like
indentation, rather than delimiters, might become
feasible for expressing local structure in the
source language.
     -- Donald E. Knuth, December 1974

4.9. pprint 模塊

pprint 模塊( pretty printer )用於打印 Python 數據結構. 當你在命令行下打印 特定數據結構時你會發現它頗有用(輸出格式比較整齊, 便於閱讀).

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

File: pprint-example-1.py

import pprint

data = (
    "this is a string", [1, 2, 3, 4], ("more tuples",
    1.0, 2.3, 4.5), "this is yet another string"
    )

pprint.pprint(data)

('this is a string',
 [1, 2, 3, 4],
 ('more tuples', 1.0, 2.3, 4.5),
 'this is yet another string')

4.10. repr 模塊

repr 模塊提供了內建 repr 函數的另個版本. 它限制了不少(字符串長度, 遞歸等). Example 4-17 展現瞭如何使用該模塊.

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

File: repr-example-1.py

# note: this overrides the built-in 'repr' function
from repr import repr

# an annoyingly recursive data structure
data = (
    "X" * 100000,
    )
data = [data]
data.append(data)

print repr(data)

[('XXXXXXXXXXXX...XXXXXXXXXXXXX',), [('XXXXXXXXXXXX...XXXXXXXXXX
XXX',), [('XXXXXXXXXXXX...XXXXXXXXXXXXX',), [('XXXXXXXXXXXX...XX
XXXXXXXXXXX',), [('XXXXXXXXXXXX...XXXXXXXXXXXXX',), [(...), [...
]]]]]]]

4.11. base64 模塊

base64 編碼體系用於將任意二進制數據轉換爲純文本. 它將一個 3 字節的二進制字節組 轉換爲 4 個文本字符組儲存, 並且規定只容許如下集合中的字符出現:

ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
0123456789+/

另外, = 用於填充數據流的末尾.

Example 4-18 展現瞭如何使用 encode 和 decode 函數操做文件對象.

4.11.0.1. Example 4-18. 使用 base64 模塊編碼文件

File: base64-example-1.py

import base64

MESSAGE = "life of brian"

file = open("out.txt", "w")
file.write(MESSAGE)
file.close()

base64.encode(open("out.txt"), open("out.b64", "w"))
base64.decode(open("out.b64"), open("out.txt", "w"))

print "original:", repr(MESSAGE)
print "encoded message:", repr(open("out.b64").read())
print "decoded message:", repr(open("out.txt").read())

original:  'life of brian'
encoded message: 'bGlmZSBvZiBicmlhbg==/012'
decoded message: 'life of brian'

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

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

File: base64-example-2.py

import base64

MESSAGE = "life of brian"

data = base64.encodestring(MESSAGE)

original_data = base64.decodestring(data)

print "original:", repr(MESSAGE)
print "encoded data:", repr(data)
print "decoded data:", repr(original_data)

original: 'life of brian'
encoded data: 'bGlmZSBvZiBicmlhbg==/012'
decoded data: 'life of brian'

Example 4-20 展現瞭如何將用戶名和密碼轉換爲 HTTP 基自己份驗證字符串.

4.11.0.3. Example 4-20. 使用 base64 模塊作基本驗證

File: base64-example-3.py

import base64

def getbasic(user, password):
    # basic authentication (according to HTTP)
    return base64.encodestring(user + ":" + password)    

print getbasic("Aladdin", "open sesame")

'QWxhZGRpbjpvcGVuIHNlc2FtZQ=='

最後, Example 4-21 展現了一個實用小工具, 它能夠把 GIF 格式轉換爲 Python 腳本, 便於使用 Tkinter 庫.

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

File: base64-example-4.py

import base64, sys

if not sys.argv[1:]:
    print "Usage: gif2tk.py giffile >pyfile"
    sys.exit(1)

data = open(sys.argv[1], "rb").read()

if data[:4] != "GIF8":
    print sys.argv[1], "is not a GIF file"
    sys.exit(1)

print '# generated from', sys.argv[1], 'by gif2tk.py'
print
print 'from Tkinter import PhotoImage' 
print
print 'image = PhotoImage(data="""'
print base64.encodestring(data),
print '""")'

# generated from samples/sample.gif by gif2tk.py

from Tkinter import PhotoImage

image = PhotoImage(data="""
R0lGODlhoAB4APcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgAQEBIwEBIyMBJRUlISE/LRUBAQE
...
AjmQBFmQBnmQCJmQCrmQDNmQDvmQEBmREnkRAQEAOw==
""")

4.12. binhex 模塊

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

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

File: binhex-example-1.py

import binhex
import sys

infile = "samples/sample.jpg"

binhex.binhex(infile, sys.stdout)

(This file must be converted with BinHex 4.0)

:#R0KEA"XC5jUF'F!2j!)!*!%%TS!N!4RdrrBrq!!%%T'58B!!3%!!!%!!3!!rpX
!3`!)"JB("J8)"`F(#3N)#J`8$3`,#``C%K-2&"dD(aiG'K`F)#3Z*b!L,#-F(#J
h+5``-63d0"mR16di-M`Z-c3brpX!3`%*#3N-#``B$3dB-L%F)6+3-[r!!"%)!)!
!J!-")J!#%3%$%3(ra!!I!!!""3'3"J#3#!%#!`3&"JF)#3S,rm3!Y4!!!J%$!`)
%!`8&"!3!!!&p!3)$!!34"4)K-8%'%e&K"b*a&$+"ND%))d+a`495dI!N-f*bJJN

該模塊有兩個函數 binhex 和 hexbin .


4.13. quopri 模塊

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

這樣的編碼能夠將不包含或只包含一部分U.S. ASCII 文本的信息, 例如大多歐洲語言, 中文, 轉換爲只包含 U.S. ASCII 的信息. 在一些老式的 mail 代理中你會發現這頗有用, 由於它們通常不支持特殊. 如 Example 4-23 所示.

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

File: quopri-example-1.py

import quopri
import StringIO

# helpers (the quopri module only supports file-to-file conversion)

def encodestring(instring, tabs=0):
    outfile = StringIO.StringIO()
    quopri.encode(StringIO.StringIO(instring), outfile, tabs)
    return outfile.getvalue()

def decodestring(instring):
    outfile = StringIO.StringIO()
    quopri.decode(StringIO.StringIO(instring), outfile)
    return outfile.getvalue()

#
# try it out

MESSAGE = "å i åa ä e ö!"

encoded_message = encodestring(MESSAGE)
decoded_message = decodestring(encoded_message)

print "original:", MESSAGE
print "encoded message:", repr(encoded_message)
print "decoded message:", decoded_message

original: å i åa ä e ö!
encoded message: '=E5 i =E5a =E4 e =F6!/012'
decoded message: å i åa ä e ö!

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

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


4.14. uu 模塊

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

uu 編碼將每一個 3 字節( 24 位)的數據組轉換爲 4 個可打印字符(每一個字符 6 位), 使用從 chr(32) (空格) 到 chr(95) 的字符. uu 編碼一般會使數據大小增長 40% .

一個編碼後的數據流以一個新行開始, 它包含文件的權限( Unix 格式)和文件名, 以 end 行結尾:

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

uu 模塊提供了兩個函數: encode 和 decode .

encode(infile, outfile, filename) 函數從編碼輸入文件中的數據, 而後寫入到輸出文件中. 如 Example 4-24 所示. infile 和 outfile 能夠是文件名或文件對象. filename 參數做爲起始域的文件名寫入.

4.14.0.1. Example 4-24. 使用 uu 模塊編碼二進制文件

File: uu-example-1.py

import uu
import os, sys

infile = "samples/sample.jpg"

uu.encode(infile, sys.stdout, os.path.basename(infile))

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

decode(infile, outfile) 函數用來解碼 uu 編碼的數據. 一樣地, 參數能夠是文件名也能夠是文件對象. 如 Example 4-25 所示.

4.14.0.2. Example 4-25. 使用 uu 模塊解碼 uu 格式的文件

File: uu-example-2.py

import uu
import StringIO

infile = "samples/sample.uue"
outfile = "samples/sample.jpg"

#
# decode

fi = open(infile)
fo = StringIO.StringIO()

uu.decode(fi, fo)

#
# compare with original data file

data = open(outfile, "rb").read()

if fo.getvalue() == data:
    print len(data), "bytes ok"

4.15. binascii 模塊

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

2.0 及之後版本中, 你還可使用它在二進制數據和十六進制字符串中相互轉換.

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

File: binascii-example-1.py

import binascii

text = "hello, mrs teal"

data = binascii.b2a_base64(text)
text = binascii.a2b_base64(data)
print text, "<=>", repr(data)

data = binascii.b2a_uu(text)
text = binascii.a2b_uu(data)
print text, "<=>", repr(data)

data = binascii.b2a_hqx(text)
text = binascii.a2b_hqx(data)[0]
print text, "<=>", repr(data)

# 2.0 and newer
data = binascii.b2a_hex(text)
text = binascii.a2b_hex(data)
print text, "<=>", repr(data)

hello, mrs teal <=> 'aGVsbG8sIG1ycyB0ZWFs/012'
hello, mrs teal <=> '/:&5L;&//L(&UR<R!T96%L/012'
hello, mrs teal <=> 'D/'9XE/'mX)/'ebFb"dC@&X'
hello, mrs teal <=> '68656c6c6f2c206d7273207465616c'

5. 文件格式


5.1. 概覽

本章將描述用於處理不一樣文件格式的模塊.

5.1.1. Markup 語言

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

全部這些格式都有着相同的結構, 由於 HTML 和 XML 都來自 SGML . 每一個文檔都是由 起始標籤( start tags ), 結束標籤( end tags ), 文本(又叫字符數據), 以及實體引用( entity references )構成:

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

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

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

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

雖然 XML , HTML , SGML 使用相同的結構塊, 但它們還有一些不一樣點. 在 XML 中, 全部元素必須有起始和結束標籤, 全部標籤必須正確嵌套( well-formed ). 並且 XML 是區分大小寫的, 因此 <document> 和 <Document> 是不一樣的元素類型.

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

SGML 有着更高的靈活性, 你可使用本身的聲明( declaration ) 定義源文件如何轉換到元素結構, DTD ( document type description , 文件類型定義)能夠用來 檢查結構並補全缺失標籤. 技術上來講, HTML 和 XML 都是 SGML 應用, 有各自的 SGML 聲明, 並且 HTML 有一個標準 DTD .

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

Python 的 HTML 支持基於 SGML 分析器. htmllib 將具體的格式輸出工做交給 formatter 對象. formatter 模塊包含一些標準格式化標誌.

Python 的 XML 支持模塊很複雜. 先前是隻有與 sgmllib 相似的 xmllib , 後來加入了更高級的 expat 模塊(可選). 而最新版本中已經準備廢棄 xmllib ,啓用xml 包做爲工具集.

5.1.2. 配置文件

ConfigParser 模塊用於讀取簡單的配置文件, 相似 Windows 下的 INI 文件.

netrc 模塊用於讀取 .netrc 配置文件, shlex 模塊用於讀取相似 shell 腳本語法的配置文件.

5.1.3. 壓縮檔案格式

Python 的標準庫提供了對 GZIP 和 ZIP ( 2.0 及之後) 格式的支持. 基於 zlib 模塊, gzip 和 zipfile 模塊分別用來處理這類文件.


5.2. xmllib 模塊

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

xmlib 模塊提供了一個簡單的 XML 語法分析器, 使用正則表達式將 XML 數據分離, 如 Example 5-1 所示. 語法分析器只對文檔作基本的檢查, 例如是否只有一個頂層元素, 全部的標籤是否匹配.

XML 數據一塊一塊地發送給 xmllib 分析器(例如在網路中傳輸的數據). 分析器在遇到起始標籤, 數據區域, 結束標籤, 和實體的時候調用不一樣的方法.

若是你只是對某些標籤感興趣, 你能夠定義特殊的 start_tag 和 end_tag 方法, 這裏 tag 是標籤名稱. 這些 start 函數使用它們對應標籤的屬性做爲參數調用(傳遞時爲一個字典).

5.2.0.1. Example 5-1. 使用 xmllib 模塊獲取元素的信息

File: xmllib-example-1.py

import xmllib

class Parser(xmllib.XMLParser):
    # get quotation number

    def _ _init_ _(self, file=None):
        xmllib.XMLParser._ _init_ _(self)
        if file:
            self.load(file)

    def load(self, file):
        while 1:
            s = file.read(512)
            if not s:
                break
            self.feed(s)
        self.close()

    def start_quotation(self, attrs):
        print "id =>", attrs.get("id")
        raise EOFError

try:
    c = Parser()
    c.load(open("samples/sample.xml"))
except EOFError:
    pass

id => 031

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

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

File: xmllib-example-2.py

import xmllib
import string, sys

STYLESHEET = {
    # each element can contribute one or more style elements
    "quotation": {"style": "italic"},
    "lang": {"weight": "bold"},
    "name": {"weight": "medium"},
}

class Parser(xmllib.XMLParser):
    # a simple styling engine

    def _ _init_ _(self, renderer):
        xmllib.XMLParser._ _init_ _(self)
        self._ _data = []
        self._ _tags = []
        self._ _renderer = renderer

    def load(self, file):
        while 1:
            s = file.read(8192)
            if not s:
                break
            self.feed(s)
        self.close()

    def handle_data(self, data):
        self._ _data.append(data)

    def unknown_starttag(self, tag, attrs):
        if self._ _data:
            text = string.join(self._ _data, "")
            self._ _renderer.text(self._ _tags, text)
        self._ _tags.append(tag)
        self._ _data = []

    def unknown_endtag(self, tag):
        self._ _tags.pop()
        if self._ _data:
            text = string.join(self._ _data, "")
            self._ _renderer.text(self._ _tags, text)
        self._ _data = []

class DumbRenderer:

    def _ _init_ _(self):
        self.cache = {}

    def text(self, tags, text):
        # render text in the style given by the tag stack
        tags = tuple(tags)
        style = self.cache.get(tags)
        if style is None:
            # figure out a combined style
            style = {}
            for tag in tags:
                s = STYLESHEET.get(tag)
                if s:
                    style.update(s)
            self.cache[tags] = style # update cache
        # write to standard output
        sys.stdout.write("%s =>/n" % style)
        sys.stdout.write("  " + repr(text) + "/n")

#
# try it out

r = DumbRenderer()
c = Parser(r)
c.load(open("samples/sample.xml"))

{'style': 'italic'} =>
  'I/'ve had a lot of developers come up to me and/012say,
  "I haven/'t had this much fun in a long time. It sure
  beats/012writing '
{'style': 'italic', 'weight': 'bold'} =>
  'Cobol'
{'style': 'italic'} =>
  '" -- '
{'style': 'italic', 'weight': 'medium'} =>
  'James Gosling'
{'style': 'italic'} =>
  ', on/012'
{'weight': 'bold'} =>
  'Java'
{'style': 'italic'} =>
  '.'

5.3. xml.parsers.expat 模塊

(可選) xml.parsers.expat 模塊是 James Clark's Expat XML parser 的接口. Example 5-3 展現了這個功能完整且性能很好的語法分析器.

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

File: xml-parsers-expat-example-1.py

from xml.parsers import expat

class Parser:

    def _ _init_ _(self):
        self._parser = expat.ParserCreate()
        self._parser.StartElementHandler = self.start
        self._parser.EndElementHandler = self.end
        self._parser.CharacterDataHandler = self.data

    def feed(self, data):
        self._parser.Parse(data, 0)

    def close(self):
        self._parser.Parse("", 1) # end of data
        del self._parser # get rid of circular references

    def start(self, tag, attrs):
        print "START", repr(tag), attrs

    def end(self, tag):
        print "END", repr(tag)

    def data(self, data):
        print "DATA", repr(data)

p = Parser()
p.feed("<tag>data</tag>")
p.close()

START u'tag' {}
DATA u'data'
END u'tag'

注意即便你傳入的是普通的文本, 這裏的分析器仍然會返回 Unicode 字符串. 默認狀況下, 分析器將源文本做爲 UTF-8 解析. 若是要使用其餘編碼, 請確保 XML 文件包含 encoding 說明. 如 Example 5-4 所示.

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

File: xml-parsers-expat-example-2.py

from xml.parsers import expat

class Parser:

    def _ _init_ _(self):
        self._parser = expat.ParserCreate()
        self._parser.StartElementHandler = self.start
        self._parser.EndElementHandler = self.end
        self._parser.CharacterDataHandler = self.data

    def feed(self, data):
        self._parser.Parse(data, 0)

    def close(self):
        self._parser.Parse("", 1) # end of data
        del self._parser # get rid of circular references

    def start(self, tag, attrs):
        print "START", repr(tag), attrs

    def end(self, tag):
        print "END", repr(tag)

    def data(self, data):
        print "DATA", repr(data)

p = Parser()
p.feed("""/
<?xml version='1.0' encoding='iso-8859-1'?>
<author>
<name>fredrik lundh</name>
<city>linköping</city>
</author>
"""
)
p.close()

START u'author' {}
DATA u'/012'
START u'name' {}
DATA u'fredrik lundh'
END u'name'
DATA u'/012'
START u'city' {}
DATA u'link/366ping'
END u'city'
DATA u'/012'
END u'author'

5.4. sgmllib 模塊

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

和在 xmllib 中同樣, 這個分析器在遇到起始標籤, 數據區域, 結束標籤以及實體時調用內部方法. 若是你只是對某些標籤感興趣, 那麼你能夠定義特殊的方法.

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

File: sgmllib-example-1.py

import sgmllib
import string

class FoundTitle(Exception):
    pass

class ExtractTitle(sgmllib.SGMLParser):

    def _ _init_ _(self, verbose=0):
        sgmllib.SGMLParser._ _init_ _(self, verbose)
        self.title = self.data = None

    def handle_data(self, data):
        if self.data is not None:
            self.data.append(data)

    def start_title(self, attrs):
        self.data = []

    def end_title(self):
        self.title = string.join(self.data, "")
        raise FoundTitle # abort parsing!

def extract(file):
    # extract title from an HTML/SGML stream
    p = ExtractTitle()
    try:
        while 1:
            # read small chunks
            s = file.read(512)
            if not s:
                break
            p.feed(s)
        p.close()
    except FoundTitle:
        return p.title
    return None

#
# try it out

print "html", "=>", extract(open("samples/sample.htm"))
print "sgml", "=>", extract(open("samples/sample.sgm"))

html => A Title.
sgml => Quotations

重載 unknown_starttag 和 unknown_endtag 方法就能夠處理全部的標籤. 如 Example 5-6 所示.

5.4.0.2. Example 5-6. 使用 sgmllib 模塊格式化 SGML 文檔

File: sgmllib-example-2.py

import sgmllib
import cgi, sys

class PrettyPrinter(sgmllib.SGMLParser):
    # A simple SGML pretty printer

    def _ _init_ _(self):
        # initialize base class
        sgmllib.SGMLParser._ _init_ _(self)
        self.flag = 0

    def newline(self):
        # force newline, if necessary
        if self.flag:
            sys.stdout.write("/n")
        self.flag = 0

    def unknown_starttag(self, tag, attrs):
        # called for each start tag

        # the attrs argument is a list of (attr, value)
        # tuples. convert it to a string.
        text = ""
        for attr, value in attrs:
            text = text + " %s='%s'" % (attr, cgi.escape(value))

        self.newline()
        sys.stdout.write("<%s%s>/n" % (tag, text))

    def handle_data(self, text):
        # called for each text section
        sys.stdout.write(text)
        self.flag = (text[-1:] != "/n")

    def handle_entityref(self, text):
        # called for each entity
        sys.stdout.write("&%s;" % text)
        
    def unknown_endtag(self, tag):
        # called for each end tag
        self.newline()
        sys.stdout.write("<%s>" % tag)

#
# try it out

file = open("samples/sample.sgm")

p = PrettyPrinter()
p.feed(file.read())
p.close()

<chapter>
<title>
Quotations
<title>
<epigraph>
<attribution>
eff-bot, June 1997
<attribution>
<para>
<quote>
Nobody expects the Spanish Inquisition! Amongst
our weaponry are such diverse elements as fear, surprise,
ruthless efficiency, and an almost fanatical devotion to
Guido, and nice red uniforms &mdash; oh, damn!
<quote>
<para>
<epigraph>
<chapter>

Example 5-7 檢查 SGML 文檔是不是如 XML 那樣 "正確格式化", 全部的元素是否正確嵌套, 起始和結束標籤是否匹配等.

咱們使用列表保存全部起始標籤, 而後檢查每一個結束標籤是否匹配前個起始標籤. 最後確認到達文件末尾時沒有未關閉的標籤.

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

File: sgmllib-example-3.py

import sgmllib

class WellFormednessChecker(sgmllib.SGMLParser):
    # check that an SGML document is 'well-formed'
    # (in the XML sense).

    def _ _init_ _(self, file=None):
        sgmllib.SGMLParser._ _init_ _(self)
        self.tags = []
        if file:
            self.load(file)

    def load(self, file):
        while 1:
            s = file.read(8192)
            if not s:
                break
            self.feed(s)
        self.close()

    def close(self):
        sgmllib.SGMLParser.close(self)
        if self.tags:
            raise SyntaxError, "start tag %s not closed" % self.tags[-1]

    def unknown_starttag(self, start, attrs):
        self.tags.append(start)

    def unknown_endtag(self, end):
        start = self.tags.pop()
        if end != start:
            raise SyntaxError, "end tag %s does't match start tag %s" %/
                  (end, start)

try:
    c = WellFormednessChecker()
    c.load(open("samples/sample.htm"))
except SyntaxError:
    raise # report error
else:
    print "document is well-formed"

Traceback (innermost last):
...
SyntaxError: end tag head does't match start tag meta

最後, Example 5-8 中的類能夠用來過濾 HTML 和 SGML 文檔. 繼承這個類, 而後實現 start 和 end 方法便可.

5.4.0.4. Example 5-8. 使用 sgmllib 模塊過濾 SGML 文檔

File: sgmllib-example-4.py

import sgmllib
import cgi, string, sys

class SGMLFilter(sgmllib.SGMLParser):
    # sgml filter.  override start/end to manipulate
    # document elements

    def _ _init_ _(self, outfile=None, infile=None):
        sgmllib.SGMLParser._ _init_ _(self)
        if not outfile:
            outfile = sys.stdout
        self.write = outfile.write
        if infile:
            self.load(infile)

    def load(self, file):
        while 1:
            s = file.read(8192)
            if not s:
                break
            self.feed(s)
        self.close()

    def handle_entityref(self, name):
        self.write("&%s;" % name)

    def handle_data(self, data):
        self.write(cgi.escape(data))

    def unknown_starttag(self, tag, attrs):
        tag, attrs = self.start(tag, attrs)
        if tag:
            if not attrs:
                self.write("<%s>" % tag)
            else:
                self.write("<%s" % tag)
                for k, v in attrs:
                    self.write(" %s=%s" % (k, repr(v)))
                self.write(">")

    def unknown_endtag(self, tag):
        tag = self.end(tag)
        if tag:
            self.write("</%s>" % tag)

    def start(self, tag, attrs):
        return tag, attrs # override

    def end(self, tag):
        return tag # override

class Filter(SGMLFilter):

    def fixtag(self, tag):
        if tag == "em":
            tag = "i"
        if tag == "string":
            tag = "b"
        return string.upper(tag)

    def start(self, tag, attrs):
        return self.fixtag(tag), attrs

    def end(self, tag):
        return self.fixtag(tag)

c = Filter()
c.load(open("samples/sample.htm"))

5.5. htmllib 模塊

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

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

File: htmllib-example-1.py

import htmllib
import formatter
import string

class Parser(htmllib.HTMLParser):
    # return a dictionary mapping anchor texts to lists
    # of associated hyperlinks

    def _ _init_ _(self, verbose=0):
        self.anchors = {}
        f = formatter.NullFormatter()
        htmllib.HTMLParser._ _init_ _(self, f, verbose)

    def anchor_bgn(self, href, name, type):
        self.save_bgn()
        self.anchor = href

    def anchor_end(self):
        text = string.strip(self.save_end())
        if self.anchor and text:
            self.anchors[text] = self.anchors.get(text, []) + [self.anchor]

file = open("samples/sample.htm")
html = file.read()
file.close()

p = Parser()
p.feed(html)
p.close()

for k, v in p.anchors.items():
    print k, "=>", v

print

link => ['http://www.python.org']

若是你只是想解析一個 HTML 文件, 而不是將它交給輸出設備, 那麼 sgmllib 模塊會是更好的選擇.


5.6. htmlentitydefs 模塊

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

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

File: htmlentitydefs-example-1.py

import htmlentitydefs

entities = htmlentitydefs.entitydefs

for entity in "amp", "quot", "copy", "yen":
    print entity, "=", entities[entity]

amp = &
quot = "
copy = /302/251
yen = /302/245

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

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

File: htmlentitydefs-example-2.py

import htmlentitydefs
import re
import cgi

pattern = re.compile("&(/w+?);")

def descape_entity(m, defs=htmlentitydefs.entitydefs):
    # callback: translate one entity to its ISO Latin value
    try:
        return defs[m.group(1)]
    except KeyError:
        return m.group(0) # use as is

def descape(string):
    return pattern.sub(descape_entity, string)

print descape("&lt;spam&amp;eggs&gt;")
print descape(cgi.escape("<spam&eggs>"))

<spam&eggs>
<spam&eggs>

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

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

File: htmlentitydefs-example-3.py

import htmlentitydefs
import re, string

# this pattern matches substrings of reserved and non-ASCII characters
pattern = re.compile(r"[&<>/"/x80-/xff]+")

# create character map
entity_map = {}

for i in range(256):
    entity_map[chr(i)] = "&%d;" % i

for entity, char in htmlentitydefs.entitydefs.items():
    if entity_map.has_key(char):
        entity_map[char] = "&%s;" % entity

def escape_entity(m, get=entity_map.get):
    return string.join(map(get, m.group()), "")

def escape(string):
    return pattern.sub(escape_entity, string)

print escape("<spam&eggs>")
print escape("/303/245 i /303/245a /303/244 e /303/266")

&lt;spam&amp;eggs&gt;
&aring; i &aring;a &auml; e &ouml;

5.7. formatter 模塊

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

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

大多狀況下, 你可使用 AbstractFormatter 類進行格式化. 它會根據不一樣的格式化事件調用 writer 對象的方法. AbstractWriter 類在每次方法調用時打印一條信息.

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

File: formatter-example-1.py

import formatter
import htmllib

w = formatter.AbstractWriter()
f = formatter.AbstractFormatter(w)

file = open("samples/sample.htm")

p = htmllib.HTMLParser(f)
p.feed(file.read())
p.close()

file.close()

send_paragraph(1)
new_font(('h1', 0, 1, 0))
send_flowing_data('A Chapter.')
send_line_break()
send_paragraph(1)
new_font(None)
send_flowing_data('Some text. Some more text. Some')
send_flowing_data(' ')
new_font((None, 1, None, None))
send_flowing_data('emphasized')
new_font(None)
send_flowing_data(' text. A')
send_flowing_data(' link')
send_flowing_data('[1]')
send_flowing_data('.')

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

5.7.0.2. Example 5-14. 使用 formatter 模塊將 HTML 轉換爲純文本

File: formatter-example-2.py

import formatter
import htmllib

w = formatter.DumbWriter() # plain text
f = formatter.AbstractFormatter(w)

file = open("samples/sample.htm")

# print html body as plain text
p = htmllib.HTMLParser(f)
p.feed(file.read())
p.close()

file.close()

# print links
print
print
i = 1
for link in p.anchorlist:
    print i, "=>", link
    i = i + 1

A Chapter.

Some text. Some more text. Some emphasized text. A link[1].

1 => http://www.python.org

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

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

File: formatter-example-3.py

import formatter
import htmllib, string

class Writer(formatter.DumbWriter):

    def _ _init_ _(self):
        formatter.DumbWriter._ _init_ _(self)
        self.tag = ""
        self.bold = self.italic = 0
        self.fonts = []

    def new_font(self, font):
        if font is None:
            font = self.fonts.pop()
            self.tag, self.bold, self.italic = font
        else:
            self.fonts.append((self.tag, self.bold, self.italic))
            tag, bold, italic, typewriter = font
            if tag is not None:
                self.tag = tag
            if bold is not None:
                self.bold = bold
            if italic is not None:
                self.italic = italic

    def send_flowing_data(self, data):
        if not data:
            return
        atbreak = self.atbreak or data[0] in string.whitespace
        for word in string.split(data):
            if atbreak:
                self.file.write(" ")
            if self.tag in ("h1", "h2", "h3"):
                word = string.upper(word)
            if self.bold:
                word = "*" + word + "*"
            if self.italic:
                word = "_" + word + "_"
            self.file.write(word)
            atbreak = 1
        self.atbreak = data[-1] in string.whitespace

w = Writer()
f = formatter.AbstractFormatter(w)

file = open("samples/sample.htm")

# print html body as plain text
p = htmllib.HTMLParser(f)
p.feed(file.read())
p.close()

_A_ _CHAPTER._

Some text. Some more text. Some *emphasized* text. A link[1].

5.8. ConfigParser 模塊

ConfigParser 模塊用於讀取配置文件.

配置文件的格式與 Windows INI 文件相似, 能夠包含一個或多個區域( section ), 每一個區域能夠有多個配置條目.

這裏有個樣例配置文件, 在 Example 5-16 用到了這個文件:

[book]
title: The Python Standard Library
author: Fredrik Lundh
email: fredrik@pythonware.com
version: 2.0-001115

[ematter]
pages: 250

[hardcopy]
pages: 350

Example 5-16 使用 ConfigParser 模塊讀取這個配製文件.

5.8.0.1. Example 5-16. 使用 ConfigParser 模塊

File: configparser-example-1.py

import ConfigParser
import string

config = ConfigParser.ConfigParser()

config.read("samples/sample.ini")

# print summary
print
print string.upper(config.get("book", "title"))
print "by", config.get("book", "author"),
print  "(" + config.get("book", "email") + ")"
print
print config.get("ematter", "pages"), "pages"
print

# dump entire config file
for section in config.sections():
    print section
    for option in config.options(section):
        print " ", option, "=", config.get(section, option)

THE PYTHON STANDARD LIBRARY
by Fredrik Lundh (fredrik@pythonware.com)

250 pages

book
  title = The Python Standard Library
  email = fredrik@pythonware.com
  author = Fredrik Lundh
  version = 2.0-001115
  _ _name_ _ = book
ematter
  _ _name_ _ = ematter
  pages = 250
hardcopy
  _ _name_ _ = hardcopy
  pages = 350

Python 2.0 之後, ConfigParser 模塊也能夠將配置數據寫入文件, 如 Example 5-17 所示.

5.8.0.2. Example 5-17. 使用 ConfigParser 模塊寫入配置數據

File: configparser-example-2.py

import ConfigParser
import sys

config = ConfigParser.ConfigParser()

# set a number of parameters
config.add_section("book")
config.set("book", "title", "the python standard library")
config.set("book", "author", "fredrik lundh")

config.add_section("ematter")
config.set("ematter", "pages", 250)

# write to screen
config.write(sys.stdout)

[book]
title = the python standard library
author = fredrik lundh

[ematter]
pages = 250

5.9. netrc 模塊

netrc 模塊能夠用來解析 .netrc 配置文件, 如 Example 5-18 所示. 該文件用於在用戶的 home 目錄儲存 FTP 用戶名和密碼. (別忘記設置這個文件的屬性爲: "chmod 0600 ~/.netrc," 這樣只有當前用戶能訪問).

5.9.0.1. Example 5-18. 使用 netrc 模塊

File: netrc-example-1.py

import netrc


# default is $HOME/.netrc
info = netrc.netrc("samples/sample.netrc")

login, account, password = info.authenticators("secret.fbi")
print "login", "=>", repr(login)
print "account", "=>", repr(account)
print "password", "=>", repr(password)

login => 'mulder'
account => None
password => 'trustno1'

5.10. shlex 模塊

shlex 模塊爲基於 Unix shell 語法的語言提供了一個簡單的 lexer (也就是 tokenizer). 如 Example 5-19 所示.

5.10.0.1. Example 5-19. 使用 shlex 模塊

File: shlex-example-1.py

import shlex

lexer = shlex.shlex(open("samples/sample.netrc", "r"))
lexer.wordchars = lexer.wordchars + "._"

while 1:
    token = lexer.get_token()
    if not token:
        break
    print repr(token)

'machine'
'secret.fbi'
'login'
'mulder'
'password'
'trustno1'
'machine'
'non.secret.fbi'
'login'
'scully'
'password'
'noway'

5.11. zipfile 模塊

( 2.0 新增) zipfile 模塊能夠用來讀寫 ZIP 格式.

5.11.1. 列出內容

使用 namelist 和 infolist 方法能夠列出壓縮檔的內容, 前者返回由文件名組成的列表, 後者返回由 ZipInfo 實例組成的列表. 如 Example 5-20 所示.

5.11.1.1. Example 5-20. 使用 zipfile 模塊列出 ZIP 文檔中的文件

File: zipfile-example-1.py

import zipfile

file = zipfile.ZipFile("samples/sample.zip", "r")

# list filenames
for name in file.namelist():
    print name,
print

# list file information
for info in file.infolist():
    print info.filename, info.date_time, info.file_size

sample.txt sample.jpg
sample.txt (1999, 9, 11, 20, 11, 8) 302
sample.jpg (1999, 9, 18, 16, 9, 44) 4762

5.11.2. 從 ZIP 文件中讀取數據

調用 read 方法就能夠從 ZIP 文檔中讀取數據. 它接受一個文件名做爲參數, 返回字符串. 如 Example 5-21 所示.

5.11.2.1. Example 5-21. 使用 zipfile 模塊從 ZIP 文件中讀取數據

File: zipfile-example-2.py

import zipfile

file = zipfile.ZipFile("samples/sample.zip", "r")

for name in file.namelist():
    data = file.read(name)
    print name, len(data), repr(data[:10])

sample.txt 302 'We will pe'
sample.jpg 4762 '/377/330/377/340/000/020JFIF'

5.11.3. 向 ZIP 文件寫入數據

向壓縮檔加入文件很簡單, 將文件名, 文件在 ZIP 檔中的名稱傳遞給 write 方法便可.

Example 5-22 將 samples 目錄中的全部文件打包爲一個 ZIP 文件.

5.11.3.1. Example 5-22. 使用 zipfile 模塊將文件儲存在 ZIP 文件裏

File: zipfile-example-3.py

import zipfile
import glob, os

# open the zip file for writing, and write stuff to it

file = zipfile.ZipFile("test.zip", "w")

for name in glob.glob("samples/*"):
    file.write(name, os.path.basename(name), zipfile.ZIP_DEFLATED)

file.close()

# open the file again, to see what's in it

file = zipfile.ZipFile("test.zip", "r")
for info in file.infolist():
    print info.filename, info.date_time, info.file_size, info.compress_size

sample.wav (1999, 8, 15, 21, 26, 46) 13260 10985
sample.jpg (1999, 9, 18, 16, 9, 44) 4762 4626
sample.au (1999, 7, 18, 20, 57, 34) 1676 1103
...

write 方法的第三個可選參數用於控制是否使用壓縮. 默認爲 zipfile.ZIP_STORED , 意味着只是將數據儲存在檔案裏而不進行任何壓縮. 若是安裝了 zlib 模塊, 那麼就可使用 zipfile.ZIP_DEFLATED 進行壓縮.

zipfile 模塊也能夠向檔案中添加字符串. 不過, 這須要一點技巧, 你須要建立一個 ZipInfo 實例, 並正確配置它. Example 5-23 提供了一種簡單的解決辦法.

5.11.3.2. Example 5-23. 使用 zipfile 模塊在 ZIP 文件中儲存字符串

File: zipfile-example-4.py

import zipfile
import glob, os, time

file = zipfile.ZipFile("test.zip", "w")

now = time.localtime(time.time())[:6]

for name in ("life", "of", "brian"):
    info = zipfile.ZipInfo(name)
    info.date_time = now
    info.compress_type = zipfile.ZIP_DEFLATED
    file.writestr(info, name*1000)

file.close()

# open the file again, to see what's in it

file = zipfile.ZipFile("test.zip", "r")

for info in file.infolist():
    print info.filename, info.date_time, info.file_size, info.compress_size

life (2000, 12, 1, 0, 12, 1) 4000 26
of (2000, 12, 1, 0, 12, 1) 2000 18
brian (2000, 12, 1, 0, 12, 1) 5000 31

5.12. gzip 模塊

gzip 模塊用來讀寫 gzip 格式的壓縮文件, 如 Example 5-24 所示.

5.12.0.1. Example 5-24. 使用 gzip 模塊讀取壓縮文件

File: gzip-example-1.py

import gzip

file = gzip.GzipFile("samples/sample.gz")

print file.read()

Well it certainly looks as though we're in for
a splendid afternoon's sport in this the 127th
Upperclass Twit of the Year Show.

標準的實現並不支持 seek 和 tell 方法. 不過 Example 5-25 能夠解決這個問題.

5.12.0.2. Example 5-25. 給 gzip 模塊添加 seek/tell 支持

File: gzip-example-2.py

import gzip

class gzipFile(gzip.GzipFile):
    # adds seek/tell support to GzipFile

    offset = 0

    def read(self, size=None):
        data = gzip.GzipFile.read(self, size)
        self.offset = self.offset + len(data)
        return data

    def seek(self, offset, whence=0):
        # figure out new position (we can only seek forwards)
        if whence == 0:
            position = offset
        elif whence == 1:
            position = self.offset + offset
        else:
            raise IOError, "Illegal argument"
        if position < self.offset:
            raise IOError, "Cannot seek backwards"

        # skip forward, in 16k blocks
        while position > self.offset:
            if not self.read(min(position - self.offset, 16384)):
                break

    def tell(self):
        return self.offset

#
# try it

file = gzipFile("samples/sample.gz")
file.seek(80)

print file.read()

this the 127th
Upperclass Twit of the Year Show.

6. 郵件和新聞消息處理

"To be removed from our list of future commercial postings by [SOME] PUBLISHING COMPANY an Annual Charge of Ninety Five dollars is required. Just send $95.00 with your Name, Address and Name of the Newsgroup to be removed from our list."
- Newsgroup spammer, July 1996
"想要退出 '某' 宣傳公司的將來商業廣告列表嗎, 您須要付 95 美圓. 只要您支付95美圓, 而且告訴咱們您的姓名, 地址, 和須要退出的新聞組, 咱們就會把您從列表中移除."
- 新聞組垃圾發送者, 1996 年 7 月

6.1. 概覽

Python 有大量用於處理郵件和新聞組的模塊, 其中包括了許多常見的郵件格式.


6.2. rfc822 模塊

rfc822 模塊包括了一個郵件和新聞組的解析器 (也可用於其它符合 RFC 822 標準的消息, 好比 HTTP 頭).

一般, RFC 822 格式的消息包含一些標頭字段, 後面至少有一個空行, 而後是信息主體.

For example, here's a short mail message. The first five lines make up the message header, and the actual message (a single line, in this case) follows after an empty line:

例如這裏的郵件信息. 前五行組成了消息標頭, 隔一個空行後是消息主體.

Message-Id: <20001114144603.00abb310@oreilly.com>
Date: Tue, 14 Nov 2000 14:55:07 -0500
To: "Fredrik Lundh" <fredrik@effbot.org>
From: Frank
Subject: Re: python library book!

Where is it?

消息解析器讀取標頭字段後會返回一個以消息標頭爲鍵的類字典對象, 如 Example 6-1 所示.

6.2.0.1. Example 6-1. 使用 rfc822 模塊

File: rfc822-example-1.py

import rfc822

file = open("samples/sample.eml")

message = rfc822.Message(file)

for k, v in message.items():
    print k, "=", v

print len(file.read()), "bytes in body"

subject = Re: python library book!
from = "Frank" <your@editor>
message-id = <20001114144603.00abb310@oreilly.com>
to = "Fredrik Lundh" <fredrik@effbot.org>
date = Tue, 14 Nov 2000 14:55:07 -0500
25 bytes in body

消息對象( message object )還提供了一些用於解析地址字段和數據的, 如 Example 6-2 所示.

6.2.0.2. Example 6-2. 使用 rfc822 模塊解析標頭字段

File: rfc822-example-2.py

import rfc822

file = open("samples/sample.eml")

message = rfc822.Message(file)

print message.getdate("date")
print message.getaddr("from")
print message.getaddrlist("to")

(2000, 11, 14, 14, 55, 7, 0, 0, 0)
('Frank', 'your@editor')
[('Fredrik Lundh', 'fredrik@effbot.org')]

地址字段被解析爲 (實際名稱, 郵件地址) 這樣的元組. 數據字段被解析爲 9 元時間元組, 可使用 time 模塊處理.


6.3. mimetools 模塊

多用途因特網郵件擴展 ( Multipurpose Internet Mail Extensions, MIME ) 標準定義瞭如何在 RFC 822 格式的消息中儲存非 ASCII 文本, 圖像以及其它數據.

mimetools 模塊包含一些讀寫 MIME 信息的工具. 它還提供了一個相似 rfc822 模塊中 Message 的類, 用於處理 MIME 編碼的信息. 如 Example 6-3 所示.

6.3.0.1. Example 6-3. 使用 mimetools 模塊

File: mimetools-example-1.py

import mimetools

file = open("samples/sample.msg")

msg = mimetools.Message(file)

print "type", "=>", msg.gettype()
print "encoding", "=>", msg.getencoding()
print "plist", "=>", msg.getplist()

print "header", "=>"
for k, v in msg.items():
    print "  ", k, "=", v

type => text/plain
encoding => 7bit
plist => ['charset="iso-8859-1"']
header =>
   mime-version = 1.0
   content-type = text/plain;
 charset="iso-8859-1"
   to = effbot@spam.egg
   date = Fri, 15 Oct 1999 03:21:15 -0400
   content-transfer-encoding = 7bit
   from = "Fredrik Lundh" <fredrik@pythonware.com>
   subject = By the way...
...

6.4. MimeWriter 模塊

MimeWriter 模塊用於生成符合 MIME 郵件標準的 "多部分" 的信息, 如 Example 6-4 所示.

6.4.0.1. Example 6-4. 使用 MimeWriter 模塊

File: mimewriter-example-1.py

import MimeWriter

# data encoders
# 數據編碼
import quopri
import base64
import StringIO

import sys

TEXT = """
here comes the image you asked for.  hope
it's what you expected.

</F>"""

FILE = "samples/sample.jpg"

file = sys.stdout

#
# create a mime multipart writer instance

mime = MimeWriter.MimeWriter(file)
mime.addheader("Mime-Version", "1.0")

mime.startmultipartbody("mixed")

# add a text message
# 加入文字信息

part = mime.nextpart()
part.addheader("Content-Transfer-Encoding", "quoted-printable")
part.startbody("text/plain")

quopri.encode(StringIO.StringIO(TEXT), file, 0)

# add an image
# 加入圖片

part = mime.nextpart()
part.addheader("Content-Transfer-Encoding", "base64")
part.startbody("image/jpeg")

base64.encode(open(FILE, "rb"), file)

mime.lastpart()

輸出結果以下:

Content-Type: multipart/mixed;
    boundary='host.1.-852461.936831373.130.24813'

--host.1.-852461.936831373.130.24813
Content-Type: text/plain
Context-Transfer-Encoding: quoted-printable

here comes the image you asked for.  hope
it's what you expected.

</F>

--host.1.-852461.936831373.130.24813
Content-Type: image/jpeg
Context-Transfer-Encoding: base64

/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRof
HBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIy
...
1e5vLrSYbJnEVpEgjCLx5mPU0qsVK0UaxjdNlS+1U6pfzTR8IzEhj2HrVG6m8m18xc8cIKSC
tCuFyC746j/Cq2pTia4WztfmKjGBXTCmo6IUpt==

--host.1.-852461.936831373.130.24813--

[Example 6-5 #eg-6-5 ] 使用輔助類儲存每一個子部分.

6.4.0.2. Example 6-5. MimeWriter 模塊的輔助類

File: mimewriter-example-2.py

import MimeWriter
import string, StringIO, sys
import re, quopri, base64

# check if string contains non-ascii characters
must_quote = re.compile("[/177-/377]").search


#
# encoders

def encode_quoted_printable(infile, outfile):
    quopri.encode(infile, outfile, 0)

class Writer:

    def _ _init_ _(self, file=None, blurb=None):
        if file is None:
            file = sys.stdout
        self.file = file
        self.mime = MimeWriter.MimeWriter(file)
        self.mime.addheader("Mime-Version", "1.0")

        file = self.mime.startmultipartbody("mixed")
        if blurb:
            file.write(blurb)

    def close(self):
        "End of message"
        self.mime.lastpart()
        self.mime = self.file = None

    def write(self, data, mimetype="text/plain"):
        "Write data from string or file to message"

        # data is either an opened file or a string
        if type(data) is type(""):
            file = StringIO.StringIO(data)
        else:
            file = data
            data = None

        part = self.mime.nextpart()

        typ, subtyp = string.split(mimetype, "/", 1)

        if typ == "text":

            # text data
            encoding = "quoted-printable"
            encoder = lambda i, o: quopri.encode(i, o, 0)

            if data and not must_quote(data):
                # copy, don't encode
                encoding = "7bit"
                encoder = None

        else:

            # binary data (image, audio, application, ...)
            encoding = "base64"
            encoder = base64.encode

        #
        # write part headers

        if encoding:
            part.addheader("Content-Transfer-Encoding", encoding)

        part.startbody(mimetype)

        #
        # write part body

        if encoder:
            encoder(file, self.file)
        elif data:
            self.file.write(data)
        else:
            while 1:
                data = infile.read(16384)
                if not data:
                    break
                outfile.write(data)

#
# try it out

BLURB = "if you can read this, your mailer is not MIME-aware/n"

mime = Writer(sys.stdout, BLURB)

# add a text message
mime.write("""/
here comes the image you asked for.  hope
it's what you expected.
""", "text/plain")

# add an image
mime.write(open("samples/sample.jpg", "rb"), "image/jpeg")

mime.close()

6.5. mailbox 模塊

mailbox 模塊用來處理各類不一樣類型的郵箱格式, 如 Example 6-6 所示. 大部分郵箱格式使用文本文件儲存純 RFC 822 信息, 用分割行區別不一樣的信息.

6.5.0.1. Example 6-6. 使用 mailbox 模塊

File: mailbox-example-1.py

import mailbox

mb = mailbox.UnixMailbox(open("/var/spool/mail/effbot"))

while 1:
    msg = mb.next()
    if not msg:
        break
    for k, v in msg.items():
        print k, "=", v
    body = msg.fp.read()
    print len(body), "bytes in body"

subject = for he's a ...
message-id = <199910150027.CAA03202@spam.egg>
received = (from fredrik@pythonware.com)
 by spam.egg (8.8.7/8.8.5) id CAA03202
 for effbot; Fri, 15 Oct 1999 02:27:36 +0200
from = Fredrik Lundh <fredrik@pythonware.com>
date = Fri, 15 Oct 1999 12:35:36 +0200
to = effbot@spam.egg
1295 bytes in body

6.6. mailcap 模塊

mailcap 模塊用於處理 mailcap 文件, 該文件指定了不一樣的文檔格式的處理方法( Unix 系統下). 如 Example 6-7 所示.

6.6.0.1. Example 6-7. 使用 mailcap 模塊得到 Capability 字典

File: mailcap-example-1.py

import mailcap

caps = mailcap.getcaps()

for k, v in caps.items():
    print k, "=", v

image/* = [{'view': 'pilview'}]
application/postscript = [{'view': 'ghostview'}]

Example 6-7 中, 系統使用 pilview 來預覽( view )全部類型的圖片, 使用 ghostscript viewer 預覽 PostScript 文檔. Example 6-8 展現瞭如何使用 mailcap 得到特定操做的命令.

6.6.0.2. Example 6-8. 使用 mailcap 模塊得到打開

File: mailcap-example-2.py

import mailcap

caps = mailcap.getcaps()

command, info = mailcap.findmatch(
    caps, "image/jpeg", "view", "samples/sample.jpg"
    )

print command

pilview samples/sample.jpg

6.7. mimetypes 模塊

mimetypes 模塊能夠判斷給定 url ( uniform resource locator , 統一資源定位符) 的 MIME 類型. 它基於一個內建的表, 還可能搜索 Apache 和 Netscape 的配置文件. 如 Example 6-9 所示.

6.7.0.1. Example 6-9. 使用 mimetypes 模塊

File: mimetypes-example-1.py

import mimetypes
import glob, urllib

for file in glob.glob("samples/*"):
    url = urllib.pathname2url(file)
    print file, mimetypes.guess_type(url)

samples/sample.au ('audio/basic', None)
samples/sample.ini (None, None)
samples/sample.jpg ('image/jpeg', None)
samples/sample.msg (None, None)
samples/sample.tar ('application/x-tar', None)
samples/sample.tgz ('application/x-tar', 'gzip')
samples/sample.txt ('text/plain', None)
samples/sample.wav ('audio/x-wav', None)
samples/sample.zip ('application/zip', None)

6.8. packmail 模塊

(已廢棄) packmail 模塊能夠用來建立 Unix shell 檔案. 若是安裝了合適的工具, 那麼你就能夠直接經過運行來解開這樣的檔案. Example 6-10 展現瞭如何打包單個文件, Example 6-11 則打包了整個目錄樹.

6.8.0.1. Example 6-10. 使用 packmail 打包單個文件

File: packmail-example-1.py

import packmail
import sys

packmail.pack(sys.stdout, "samples/sample.txt", "sample.txt")

echo sample.txt
sed "s/^X//" >sample.txt <<"!"
XWe will perhaps eventually be writing only small
Xmodules, which are identified by name as they are
Xused to build larger ones, so that devices like
Xindentation, rather than delimiters, might become
Xfeasible for expressing local structure in the
Xsource language.
X    -- Donald E. Knuth, December 1974
!

====Example 6-11. 使用 packmail 打包整個目錄樹===[eg-6-11]

File: packmail-example-2.py

import packmail
import sys

packmail.packtree(sys.stdout, "samples")

注意, 這個模塊不能處理二進制文件, 例如聲音或者圖像文件.


6.9. mimify 模塊

mimify 模塊用於在 MIME 編碼的文本信息和普通文本信息(例如 ISO Latin 1 文本)間相互轉換. 它能夠用做命令行工具, 或是特定郵件代理的轉換過濾器:

$ mimify.py -e raw-message mime-message
$ mimify.py -d mime-message raw-message

做爲模塊使用, 如 Example 6-12 所示.

6.9.0.1. Example 6-12. 使用 mimify 模塊解碼信息

File: mimify-example-1.py

import mimify
import sys

mimify.unmimify("samples/sample.msg", sys.stdout, 1)

這裏是一個包含兩部分的 MIME 信息, 一個是引用的可打印信息, 另個是 base64 編碼信息. unmimify 的第三個參數決定是否自動解碼 base64 編碼的部分:

MIME-Version: 1.0
Content-Type: multipart/mixed; boundary='boundary'

this is a multipart sample file.  the two
parts both contain ISO Latin 1 text, with
different encoding techniques.

--boundary
Content-Type: text/plain
Content-Transfer-Encoding: quoted-printable

sillmj=F6lke! blindstyre! medisterkorv!

--boundary
Content-Type: text/plain
Content-Transfer-Encoding: base64

a29tIG5lciBiYXJhLCBvbSBkdSB09nJzIQ==

--boundary--

解碼結果以下 (可讀性相對來講更好些):

MIME-Version: 1.0
Content-Type: multipart/mixed; boundary= 'boundary'

this is a multipart sample file.  the two
parts both contain ISO Latin 1 text, with
different encoding techniques.

--boundary
Content-Type: text/plain

sillmjölke! blindstyre! medisterkorv!

--boundary
Content-Type: text/plain

kom ner bara, om du törs!

Example 6-13 展現瞭如何編碼信息.

6.9.0.2. Example 6-13. 使用 mimify 模塊編碼信息

File: mimify-example-2.py

import mimify
import StringIO, sys

#
# decode message into a string buffer

file = StringIO.StringIO()

mimify.unmimify("samples/sample.msg", file, 1)

#
# encode message from string buffer

file.seek(0) # rewind

mimify.mimify(file, sys.stdout)

6.10. multifile 模塊

multifile 模塊容許你將一個多部分的 MIME 信息的每部分做爲單獨的文件處理. 如 Example 6-14 所示.

6.10.0.1. Example 6-14. 使用 multifile 模塊

File: multifile-example-1.py

import multifile
import cgi, rfc822

infile = open("samples/sample.msg")

message = rfc822.Message(infile)

# print parsed header
for k, v in message.items():
    print k, "=", v

# use cgi support function to parse content-type header
type, params = cgi.parse_header(message["content-type"])

if type[:10] == "multipart/":

    # multipart message
    boundary = params["boundary"]

    file = multifile.MultiFile(infile)

    file.push(boundary)

    while file.next():

        submessage = rfc822.Message(file)

        # print submessage
        print "-" * 68
        for k, v in submessage.items():
            print k, "=", v
        print
        print file.read()

    file.pop()

else:

    # plain message
    print infile.read()

7. 網絡協議

"Increasingly, people seem to misinterpret complexity as sophistication, which is baffling - the incomprehensible should cause suspicion rather than admiration. Possibly this trend results from a mistaken belief that using a somewhat mysterious device confers an aura of power on the user."
- Niklaus Wirth

7.1. 概覽

本章描述了 Python 的 socket 協議支持以及其餘創建在 socket 模塊上的網絡 模塊. 這些包含了對大多流行 Internet 協議客戶端的支持, 以及一些可用來 實現 Internet 服務器的框架.

對於那些本章中的底層的例子, 我將使用兩個協議做爲樣例: Internet Time Protocol ( Internet 時間協議 ) 以及 Hypertext Transfer Protocol (超文本傳輸協議, HTTP 協議).

7.1.1. Internet 時間協議

Internet 時間協議 ( RFC 868, Postel 和 Harrenstien, 1983) 可讓 一個網絡客戶端得到一個服務器的當前時間.

由於這個協議是輕量級的, 許多 Unix 系統(但不是全部)都提供了這個服務. 它多是最簡單的網絡協議了. 服務器等待鏈接請求並在鏈接後返回當前時間 ( 4 字節整數, 自從 1900 年 1 月 1 日到當前的秒數).

協議很簡單, 這裏咱們提供規格書給你們:

File: rfc868.txt

Network Working Group                                    J. Postel - ISI
Request for Comments: 868                           K. Harrenstien - SRI
                                                                May 1983

                             Time Protocol

This RFC specifies a standard for the ARPA Internet community.  Hosts on
the ARPA Internet that choose to implement a Time Protocol are expected
to adopt and implement this standard.

本 RFC 規範提供了一個 ARPA Internet community 上的標準. 
在 ARPA Internet 上的全部主機應當採用並實現這個標準.

This protocol provides a site-independent, machine readable date and
time.  The Time service sends back to the originating source the time in
seconds since midnight on January first 1900.

此協議提供了一個獨立於站點的, 機器可讀的日期和時間信息. 
時間服務返回的是從 1900 年 1 月 1 日午夜到如今的秒數.

One motivation arises from the fact that not all systems have a
date/time clock, and all are subject to occasional human or machine
error.  The use of time-servers makes it possible to quickly confirm or
correct a system's idea of the time, by making a brief poll of several
independent sites on the network.

設計這個協議的一個重要目的在於, 網絡上的一些主機並無時鐘, 
這有可能致使人工或者機器錯誤. 咱們能夠依靠時間服務器快速確認或者修改
一個系統的時間.

This protocol may be used either above the Transmission Control Protocol
(TCP) or above the User Datagram Protocol (UDP).

該協議能夠用在 TCP 協議或是 UDP 協議上.

When used via TCP the time service works as follows:

經過 TCP 訪問時間服務器的步驟:

 *  S: Listen on port 37 (45 octal).
 *  U: Connect to port 37.
 *  S: Send the time as a 32 bit binary number.
 *  U: Receive the time.
 *  U: Close the connection.
 *  S: Close the connection.
 
 *  S: 監聽 37 ( 45 的八進制) 端口.
 *  U: 鏈接 37 端口.
 *  S: 將時間做爲 32 位二進制數字發送.
 *  U: 接收時間.
 *  U: 關閉鏈接.
 *  S: 關閉鏈接.  

   The server listens for a connection on port 37.  When the connection
   is established, the server returns a 32-bit time value and closes the
   connection.  If the server is unable to determine the time at its
   site, it should either refuse the connection or close it without
   sending anything.
   
   服務器在 37 端口監聽. 當鏈接創建的時候, 服務器返回一個 32 位的數字值
   並關閉鏈接. 若是服務器本身沒法決定當前時間, 那麼它應該拒絕這個鏈接或者
   不發送任何數據當即關閉鏈接.

When used via UDP the time service works as follows:

經過 TCP 訪問時間服務器的步驟:
   
   S: Listen on port 37 (45 octal).
   U: Send an empty datagram to port 37.
   S: Receive the empty datagram.
   S: Send a datagram containing the time as a 32 bit binary number.
   U: Receive the time datagram.

   S: 監聽 37 ( 45 的八進制) 端口.
   U: 發送空數據報文到 37 端口.
   S: 接受空報文.
   S: 發送包含時間( 32 位二進制數字 )的報文.
   U: 接受時間報文.
   
   The server listens for a datagram on port 37.  When a datagram
   arrives, the server returns a datagram containing the 32-bit time
   value.  If the server is unable to determine the time at its site, it
   should discard the arriving datagram and make no reply.
   
   服務器在 37 端口監聽報文. 當報文到達時, 服務器返回包含 32 位時間值
   的報文. 若是服務器沒法決定當前時間, 那麼它應該丟棄到達的報文, 
   不作任何回覆.

The Time

時間

The time is the number of seconds since 00:00 (midnight) 1 January 1900
GMT, such that the time 1 is 12:00:01 am on 1 January 1900 GMT; this
base will serve until the year 2036.

時間是自 1900 年 1 月 1 日 0 時到當前的秒數, 
這個協議標準會一直服務到2036年. 到時候數字不夠用再說.

For example:

   the time  2,208,988,800 corresponds to 00:00  1 Jan 1970 GMT,
             2,398,291,200 corresponds to 00:00  1 Jan 1976 GMT,
             2,524,521,600 corresponds to 00:00  1 Jan 1980 GMT,
             2,629,584,000 corresponds to 00:00  1 May 1983 GMT,
        and -1,297,728,000 corresponds to 00:00 17 Nov 1858 GMT.
		

例如:

     時間值  2,208,988,800 對應 to 00:00  1 Jan 1970 GMT,
             2,398,291,200 對應 to 00:00  1 Jan 1976 GMT,
             2,524,521,600 對應 to 00:00  1 Jan 1980 GMT,
             2,629,584,000 對應 to 00:00  1 May 1983 GMT,
       最後 -1,297,728,000 對應 to 00:00 17 Nov 1858 GMT.

	   
RFC868.txt Translated By Andelf(gt: andelf@gmail.com )
非商業用途, 轉載請保留做者信息. Thx.

7.1.2. HTTP 協議

超文本傳輸協議 ( HTTP, RFC 2616 ) 是另個徹底不一樣的東西. 最近的格式說明書( Version 1.1 )超過了 100 頁.

從它最簡單的格式來看, 這個協議是很簡單的. 客戶端發送以下的請求到服務器, 請求一個文件:

GET /hello.txt HTTP/1.0
Host: hostname
User-Agent: name

[optional request body , 可選的請求正文]

服務器返回對應的響應:

HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: 7

Hello

請求和響應的 headers (報頭)通常會包含更多的域, 可是請求 header 中的 Host 域/字段是必須提供的.

header 行使用 "/r/n" 分割, 並且 header 後必須有一個空行, 即便沒有正文 (請求和響應都必須符合這條規則).

剩下的 HTTP 協議格式說明書細節, 例如內容協商, 緩存機制, 保持鏈接, 等等, 請參閱 Hypertext TransferProtocol - HTTP/1.1 ( http://www.w3.org/Protocols).


7.2. socket 模塊

socket 模塊實現了到 socket 通信層的接口. 你可使用該模塊建立 客戶端或是服務器的 socket .

咱們首先以一個客戶端爲例, Example 7-1 中的客戶端鏈接到一個時間協議服務器, 讀取 4 字節的返回數據, 並把它轉換爲一個時間值.

7.2.0.1. Example 7-1. 使用 socket 模塊實現一個時間客戶端

File: socket-example-1.py

import socket
import struct, time

# server
HOST = "www.python.org"
PORT = 37

# reference time (in seconds since 1900-01-01 00:00:00)
TIME1970 = 2208988800L # 1970-01-01 00:00:00

# connect to server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

# read 4 bytes, and convert to time value
t = s.recv(4)
t = struct.unpack("!I", t)[0]
t = int(t - TIME1970)

s.close()

# print results
print "server time is", time.ctime(t)
print "local clock is", int(time.time()) - t, "seconds off"

server time is Sat Oct 09 16:42:36 1999
local clock is 8 seconds off

socket 工廠函數( factory function )根據給定類型(該例子中爲 Internet stream socket , 即就是 TCP socket )建立一個新的 socket . connect 方法嘗試將這個 socket 鏈接到指定服務器上. 成功後, 就可使用 recv 方法讀取數據.

建立一個服務器 socket 使用的是相同的方法, 不過這裏不是鏈接到服務器, 而是將 socket bind (綁定)到本機的一個端口上, 告訴它去監聽鏈接請求, 而後儘快處理每一個到達的請求.

Example 7-2 建立了一個時間服務器, 綁定到本機的 8037 端口( 1024 前的全部端口 是爲系統服務保留的, Unix 系統下訪問它們你必需要有 root 權限).

7.2.0.2. Example 7-2. 使用 socket 模塊實現一個時間服務器

File: socket-example-2.py

import socket
import struct, time

# user-accessible port
PORT = 8037

# reference time
TIME1970 = 2208988800L

# establish server
service = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
service.bind(("", PORT))
service.listen(1)

print "listening on port", PORT

while 1:
    # serve forever
    channel, info = service.accept()
    print "connection from", info
    t = int(time.time()) + TIME1970
    t = struct.pack("!I", t)
    channel.send(t) # send timestamp
    channel.close() # disconnect

listening on port 8037
connection from ('127.0.0.1', 1469)
connection from ('127.0.0.1', 1470)
...

listen 函數的調用告訴 socket 咱們指望接受鏈接. 參數表明鏈接 的隊列(用於在程序沒有處理前保持鏈接)大小. 最後 accept 循環將當前時間返回 給每一個鏈接的客戶端.

注意這裏的 accept 函數返回一個新的 socket 對象, 這個對象是直接鏈接到客戶端 的. 而原 socket 只是用來保持鏈接; 全部後來的數據傳輸操做都使用新的 socket .

咱們可使用 Example 7-3 , ( Example 7-1 的通用化版本)來測試這個服務器, .

7.2.0.3. Example 7-3. 一個時間協議客戶端

File: timeclient.py

import socket
import struct, sys, time

# default server
host = "localhost"
port = 8037

# reference time (in seconds since 1900-01-01 00:00:00)
TIME1970 = 2208988800L # 1970-01-01 00:00:00

def gettime(host, port):
    # fetch time buffer from stream server
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))
    t = s.recv(4)
    s.close()
    t = struct.unpack("!I", t)[0]
    return int(t - TIME1970)

if _ _name_ _ == "_ _main_ _":
    # command-line utility
    if sys.argv[1:]:
        host = sys.argv[1]
        if sys.argv[2:]:
            port = int(sys.argv[2])
        else:
            port = 37 # default for public servers

    t = gettime(host, port)
    print "server time is", time.ctime(t)
    print "local clock is", int(time.time()) - t, "seconds off"

server time is Sat Oct 09 16:58:50 1999
local clock is 0 seconds off

Example 7-3 所示的腳本也能夠做爲模塊使用; 你只須要導入 timeclient 模塊, 而後調用它的 gettime 函數.

目前爲止, 咱們已經使用了流( TCP ) socket . 時間協議還提到了 UDP sockets (報文). 流 socket 的工做模式和電話線相似; 你會知道在遠端 是否有人拿起接聽器, 在對方掛斷的時候你也會注意到. 相比之下, 發送報文更像 是在一間黑屋子裏大聲喊. 可能某人會在那裏, 但你只有在他回覆的時候纔會知道.

如 Example 7-4 所示, 你不須要在經過報文 socket 發送數據時鏈接遠程機器. 只需使用 sendto 方法, 它接受數據和接收者地址做爲參數. 讀取報文的時候使用recvfrom 方法.

7.2.0.4. Example 7-4. 使用 socket 模塊實現一個報文時間客戶端

File: socket-example-4.py

import socket
import struct, time

# server
HOST = "localhost"
PORT = 8037

# reference time (in seconds since 1900-01-01 00:00:00)
TIME1970 = 2208988800L # 1970-01-01 00:00:00

# connect to server
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# send empty packet
s.sendto("", (HOST, PORT))

# read 4 bytes from server, and convert to time value
t, server = s.recvfrom(4)
t = struct.unpack("!I", t)[0]
t = int(t - TIME1970)

s.close()

print "server time is", time.ctime(t)
print "local clock is", int(time.time()) - t, "seconds off"

server time is Sat Oct 09 16:42:36 1999
local clock is 8 seconds off

這裏的 recvfrom 返回兩個值: 數據和發送者的地址. 後者用於發送回覆數據.

Example 7-5 展現了對應的服務器代碼.

Example 7-5. 使用 socket 模塊實現一個報文時間服務器

File: socket-example-5.py

import socket
import struct, time

# user-accessible port
PORT = 8037

# reference time
TIME1970 = 2208988800L

# establish server
service = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
service.bind(("", PORT))

print "listening on port", PORT

while 1:
    # serve forever
    data, client = service.recvfrom(0)
    print "connection from", client
    t = int(time.time()) + TIME1970
    t = struct.pack("!I", t)
    service.sendto(t, client) # send timestamp

listening on port 8037
connection from ('127.0.0.1', 1469)
connection from ('127.0.0.1', 1470)
...

最主要的不一樣在於服務器使用 bind 來分配一個已知端口給 socket , 根據 recvfrom 函數返回的地址向客戶端發送數據.


7.3. select 模塊

select 模塊容許你檢查一個或多個 socket , 管道, 以及其餘流兼容對象所接受的數據, 如 Example 7-6 所示.

你能夠將一個或更多 socket 傳遞給 select 函數, 而後等待它們狀態改變(可讀, 可寫, 或是發送錯誤信號):

  • 若是某人在調用了 listen 函數後鏈接, 當遠端數據到達時, socket 就成爲可讀的(這意味着 accept 不會阻塞). 或者是 socket 被關閉或重置時(在此狀況下, recv 會返回一個空字符串).
  • 當非阻塞調用 connect 方法後創建鏈接或是數據能夠被寫入到 socket 時, socket 就成爲可寫的.
  • 當非阻塞調用 connect 方法後鏈接失敗後, socket 會發出一個錯誤信號.

7.3.0.1. Example 7-6. 使用 select 模塊等待經 socket 發送的數據

File: select-example-1.py

import select
import socket
import time

PORT = 8037

TIME1970 = 2208988800L

service = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
service.bind(("", PORT))
service.listen(1)

print "listening on port", PORT

while 1:
    is_readable = [service]
    is_writable = []
    is_error = []
    r, w, e = select.select(is_readable, is_writable, is_error, 1.0)
    if r:
        channel, info = service.accept()
        print "connection from", info
        t = int(time.time()) + TIME1970
        t = chr(t>>24&255) + chr(t>>16&255) + chr(t>>8&255) + chr(t&255)
        channel.send(t) # send timestamp
        channel.close() # disconnect
    else:
        print "still waiting"

listening on port 8037
still waiting
still waiting
connection from ('127.0.0.1', 1469)
still waiting
connection from ('127.0.0.1', 1470)
...

在 Example 7-6 中, 咱們等待監聽 socket 變成可讀狀態, 這表明有一個鏈接請求到達. 咱們用和以前同樣的方法處理 channel socket , 由於它不可能由於等待 4 字節而填充網絡 緩衝區. 若是你須要向客戶端發送大量的數據, 那麼你應該在循環的頂端把數據加入到 is_writable 列表中, 而且只在 select 容許的狀況下寫入.

若是你設置 socket 爲非阻塞模式(經過調用 setblocking 方法), 那麼你就可使用 select 來等待 socket 鏈接. 不過 asyncore 模塊(參見下一節)提供了一個強大的框架, 它自動爲你處理好了這一切. 因此我不許備在這裏多說什麼, 看下一節吧.


7.4. asyncore 模塊

asyncore 模塊提供了一個 "反饋性的( reactive )" socket 實現. 該模塊容許你定義特定過程完成後所執行的代碼, 而不是建立 socket 對象, 調用它們的方法. 你只須要繼承 dispatcher 類, 而後重載以下方法 (能夠選擇重載某一個或多個)就能夠實現異步的 socket 處理器.

  • handle_connect : 一個鏈接成功創建後被調用.
  • handle_expt : 鏈接失敗後被調用.
  • handle_accept : 鏈接請求創建到一個監聽 socket 上時被調用. 回調時( callback )應該使用 accept 方法來得到客戶端 socket .
  • handle_read : 有來自 socket 的數據等待讀取時被調用. 回調時應該使用 recv 方法來得到數據.
  • handle_write : socket 能夠寫入數據的時候被調用. 使用 send 方法寫入數據.
  • handle_close : 當 socket 被關閉或復位時被調用.
  • handle_error(type, value, traceback) 在任何一個回調函數發生 Python 錯誤時被調用. 默認的實現會打印跟蹤返回消息到 sys.stdout .

Example 7-7 展現了一個時間客戶端, 和 socket 模塊中的那個相似.

7.4.0.1. Example 7-7. 使用 asyncore 模塊從時間服務器得到時間

File: asyncore-example-1.py

import asyncore
import socket, time

# reference time (in seconds since 1900-01-01 00:00:00)
TIME1970 = 2208988800L # 1970-01-01 00:00:00

class TimeRequest(asyncore.dispatcher):
    # time requestor (as defined in RFC 868)

    def _ _init_ _(self, host, port=37):
        asyncore.dispatcher._ _init_ _(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((host, port))

    def writable(self):
        return 0 # don't have anything to write

    def handle_connect(self):
        pass # connection succeeded

    def handle_expt(self):
        self.close() # connection failed, shutdown

    def handle_read(self):
        # get local time
        here = int(time.time()) + TIME1970

        # get and unpack server time
        s = self.recv(4)
        there = ord(s[3]) + (ord(s[2])<<8) + (ord(s[1])<<16) + (ord(s[0])<<24L)

        self.adjust_time(int(here - there))

        self.handle_close() # we don't expect more data

    def handle_close(self):
        self.close()

    def adjust_time(self, delta):
        # override this method!
        print "time difference is", delta

#
# try it out

request = TimeRequest("www.python.org")

asyncore.loop()

log: adding channel <TimeRequest  at 8cbe90>
time difference is 28
log: closing channel 192:<TimeRequest connected at 8cbe90>

若是你不想記錄任何信息, 那麼你能夠在你的 dispatcher 類裏重載 log 方法.

Example 7-8 展現了對應的時間服務器. 注意這裏它使用了兩個 dispatcher 子類, 一個用於監聽 socket , 另個用於與客戶端通信.

7.4.0.2. Example 7-8. 使用 asyncore 模塊實現時間服務器

File: asyncore-example-2.py

import asyncore
import socket, time

# reference time
TIME1970 = 2208988800L

class TimeChannel(asyncore.dispatcher):

    def handle_write(self):
        t = int(time.time()) + TIME1970
        t = chr(t>>24&255) + chr(t>>16&255) + chr(t>>8&255) + chr(t&255)
        self.send(t)
        self.close()

class TimeServer(asyncore.dispatcher):

    def _ _init_ _(self, port=37):
        self.port = port
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.bind(("", port))
        self.listen(5)
        print "listening on port", self.port

    def handle_accept(self):
        channel, addr = self.accept()
        TimeChannel(channel)

server = TimeServer(8037)
asyncore.loop()

log: adding channel <TimeServer  at 8cb940>
listening on port 8037
log: adding channel <TimeChannel  at 8b2fd0>
log: closing channel 52:<TimeChannel connected at 8b2fd0>

除了 dispatcher 外, 這個模塊還包含一個 dispatcher_with_send 類. 你可使用這個類發送大量的數據而不會阻塞網絡通信緩衝區.

Example 7-9 中的模塊經過繼承 dispatcher_with_send 類定義了一個 AsyncHTTP 類. 當你建立一個它的實例後, 它會發出一個 HTTP GET 請求並把 接受到的數據發送到一個 "consumer" 目標對象

7.4.0.3. Example 7-9. 使用 asyncore 模塊發送 HTTP 請求

File: SimpleAsyncHTTP.py

import asyncore
import string, socket
import StringIO
import mimetools, urlparse

class AsyncHTTP(asyncore.dispatcher_with_send):
    # HTTP requester

    def _ _init_ _(self, uri, consumer):
        asyncore.dispatcher_with_send._ _init_ _(self)

        self.uri = uri
        self.consumer = consumer

        # turn the uri into a valid request
        scheme, host, path, params, query, fragment = urlparse.urlparse(uri)
        assert scheme == "http", "only supports HTTP requests"
        try:
            host, port = string.split(host, ":", 1)
            port = int(port)
        except (TypeError, ValueError):
            port = 80 # default port
        if not path:
            path = "/"
        if params:
            path = path + ";" + params
        if query:
            path = path + "?" + query

        self.request = "GET %s HTTP/1.0/r/nHost: %s/r/n/r/n" % (path, host)

        self.host = host
        self.port = port

        self.status = None
        self.header = None

        self.data = ""

        # get things going!
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((host, port))

    def handle_connect(self):
        # connection succeeded
        self.send(self.request)

    def handle_expt(self):
        # connection failed; notify consumer (status is None)
        self.close()
        try:
            http_header = self.consumer.http_header
        except AttributeError:
            pass
        else:
            http_header(self)

    def handle_read(self):
        data = self.recv(2048)
        if not self.header:
            self.data = self.data + data
            try:
                i = string.index(self.data, "/r/n/r/n")
            except ValueError:
                return # continue
            else:
                # parse header
                fp = StringIO.StringIO(self.data[:i+4])
                # status line is "HTTP/version status message"
                status = fp.readline()
                self.status = string.split(status, " ", 2)
                # followed by a rfc822-style message header
                self.header = mimetools.Message(fp)
                # followed by a newline, and the payload (if any)
                data = self.data[i+4:]
                self.data = ""
                # notify consumer (status is non-zero)
                try:
                    http_header = self.consumer.http_header
                except AttributeError:
                    pass
                else:
                    http_header(self)
                if not self.connected:
                    return # channel was closed by consumer

        self.consumer.feed(data)

    def handle_close(self):
        self.consumer.close()
        self.close()

Example 7-10 中的小腳本展現瞭如何使用這個類.

7.4.0.4. Example 7-10. 使用 SimpleAsyncHTTP 類

File: asyncore-example-3.py

import SimpleAsyncHTTP
import asyncore

class DummyConsumer:
    size = 0

    def http_header(self, request):
        # handle header
        if request.status is None:
            print "connection failed"
        else:
            print "status", "=>", request.status
            for key, value in request.header.items():
                print key, "=", value

    def feed(self, data):
        # handle incoming data
        self.size = self.size + len(data)

    def close(self):
        # end of data
        print self.size, "bytes in body"

#
# try it out

consumer = DummyConsumer()

request = SimpleAsyncHTTP.AsyncHTTP(
    "http://www.pythonware.com",
    consumer
    )

asyncore.loop()

log: adding channel <AsyncHTTP  at 8e2850>
status => ['HTTP/1.1', '200', 'OK/015/012']
server = Apache/Unix (Unix)
content-type = text/html
content-length = 3730
...
3730 bytes in body
log: closing channel 156:<AsyncHTTP connected at 8e2850>

這裏的 consumer 接口設計時是爲了與 htmllib 和 xmllib 分析器兼容的, 這樣你就能夠直接方便地解析 HTML 或是 XML 數據. http_header 方法是可選的; 若是沒有定義它, 那麼它將被忽略.

Example 7-10 的一個問題是它不能很好地處理重定向資源. Example 7-11 加入了一個額外的 consumer 層, 它能夠很好地處理重定向.

7.4.0.5. Example 7-11. 使用 SimpleAsyncHTTP 類處理重定向

File: asyncore-example-4.py

import SimpleAsyncHTTP
import asyncore

class DummyConsumer:
    size = 0

    def http_header(self, request):
        # handle header
        if request.status is None:
            print "connection failed"
        else:
            print "status", "=>", request.status
            for key, value in request.header.items():
                print key, "=", value

    def feed(self, data):
        # handle incoming data
        self.size = self.size + len(data)

    def close(self):
        # end of data
        print self.size, "bytes in body"

class RedirectingConsumer:

    def _ _init_ _(self, consumer):
        self.consumer = consumer

    def http_header(self, request):
        # handle header
        if request.status is None or/
           request.status[1] not in ("301", "302"):
            try:
                http_header = self.consumer.http_header
            except AttributeError:
                pass
            else:
                return http_header(request)
        else:
            # redirect!
            uri = request.header["location"]
            print "redirecting to", uri, "..."
            request.close()
            SimpleAsyncHTTP.AsyncHTTP(uri, self)

    def feed(self, data):
        self.consumer.feed(data)

    def close(self):
        self.consumer.close()

#
# try it out

consumer = RedirectingConsumer(DummyConsumer())

request = SimpleAsyncHTTP.AsyncHTTP(
    "http://www.pythonware.com/library",
    consumer
    )

asyncore.loop()

log: adding channel <AsyncHTTP  at 8e64b0>
redirecting to http://www.pythonware.com/library/ ...
log: closing channel 48:<AsyncHTTP connected at 8e64b0>
log: adding channel <AsyncHTTP  at 8ea790>
status => ['HTTP/1.1', '200', 'OK/015/012']
server = Apache/Unix (Unix)
content-type = text/html
content-length = 387
...
387 bytes in body
log: closing channel 236:<AsyncHTTP connected at 8ea790>

若是服務器返回狀態 301 (永久重定向) 或者是 302 (臨時重定向), 重定向的 consumer 會關閉當前請求並向新地址發出新請求. 全部對 consumer 的其餘調用傳遞給原來的 consumer .


7.5. asynchat 模塊

asynchat 模塊是對 asyncore 的一個擴展. 它提供對面向行( line-oriented )的協議的額外支持. 它還提供了加強的緩衝區支持(經過 push 方法和 "producer" 機制.

Example 7-12 實現了一個很小的 HTTP 服務器. 它只是簡單地返回包含 HTTP 請求信息的 HTML 文檔(瀏覽器窗口出現的輸出).

7.5.0.1. Example 7-12. 使用 asynchat 模塊實現一個迷你 HTTP 服務器

File: asynchat-example-1.py

import asyncore, asynchat
import os, socket, string

PORT = 8000

class HTTPChannel(asynchat.async_chat):

    def _ _init_ _(self, server, sock, addr):
        asynchat.async_chat._ _init_ _(self, sock)
        self.set_terminator("/r/n")
        self.request = None
        self.data = ""
        self.shutdown = 0

    def collect_incoming_data(self, data):
        self.data = self.data + data

    def found_terminator(self):
        if not self.request:
            # got the request line
            self.request = string.split(self.data, None, 2)
            if len(self.request) != 3:
                self.shutdown = 1
            else:
                self.push("HTTP/1.0 200 OK/r/n")
                self.push("Content-type: text/html/r/n")
                self.push("/r/n")
            self.data = self.data + "/r/n"
            self.set_terminator("/r/n/r/n") # look for end of headers
        else:
            # return payload.
            self.push("<html><body><pre>/r/n")
            self.push(self.data)
            self.push("</pre></body></html>/r/n")
            self.close_when_done()

class HTTPServer(asyncore.dispatcher):

    def _ _init_ _(self, port):
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.bind(("", port))
        self.listen(5)

    def handle_accept(self):
        conn, addr = self.accept()
        HTTPChannel(self, conn, addr)

#
# try it out

s = HTTPServer(PORT)
print "serving at port", PORT, "..."
asyncore.loop()

GET / HTTP/1.1
Accept: */*
Accept-Language: en, sv
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; Bruce/1.0)
Host: localhost:8000
Connection: Keep-Alive

producer 接口容許你傳入( "push" )太大以致於沒法在內存中儲存的對象. asyncore 在須要更多數據的時候自動調用 producer 的 more 方法. 另外, 它使用一個空字符串標記文件的末尾.

Example 7-13 實現了一個很簡單的基於文件的 HTTP 服務器, 它使用了一個簡單的 FileProducer 類來從文件中讀取數據, 每次只讀取幾 kb .

7.5.0.2. Example 7-13. 使用 asynchat 模塊實現一個簡單的 HTTP 服務器

File: asynchat-example-2.py

import asyncore, asynchat
import os, socket, string, sys
import StringIO, mimetools

ROOT = "."

PORT = 8000

class HTTPChannel(asynchat.async_chat):

    def _ _init_ _(self, server, sock, addr):
        asynchat.async_chat._ _init_ _(self, sock)
        self.server = server
        self.set_terminator("/r/n/r/n")
        self.header = None
        self.data = ""
        self.shutdown = 0

    def collect_incoming_data(self, data):
        self.data = self.data + data
        if len(self.data) > 16384:
            # limit the header size to prevent attacks
            self.shutdown = 1

    def found_terminator(self):
        if not self.header:
            # parse http header
            fp = StringIO.StringIO(self.data)
            request = string.split(fp.readline(), None, 2)
            if len(request) != 3:
                # badly formed request; just shut down
                self.shutdown = 1
            else:
                # parse message header
                self.header = mimetools.Message(fp)
                self.set_terminator("/r/n")
                self.server.handle_request(
                    self, request[0], request[1], self.header
                    )
                self.close_when_done()
            self.data = ""
        else:
            pass # ignore body data, for now

    def pushstatus(self, status, explanation="OK"):
        self.push("HTTP/1.0 %d %s/r/n" % (status, explanation))


class FileProducer:
    # a producer that reads data from a file object

    def _ _init_ _(self, file):
        self.file = file

    def more(self):
        if self.file:
            data = self.file.read(2048)
            if data:
                return data
            self.file = None
        return ""


class HTTPServer(asyncore.dispatcher):

    def _ _init_ _(self, port=None, request=None):
        if not port:
            port = 80
        self.port = port
        if request:
            self.handle_request = request # external request handler
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.bind(("", port))
        self.listen(5)

    def handle_accept(self):
        conn, addr = self.accept()
        HTTPChannel(self, conn, addr)

    def handle_request(self, channel, method, path, header):
        try:
            # this is not safe!
            while path[:1] == "/":
                path = path[1:]
            filename = os.path.join(ROOT, path)
            print path, "=>", filename
            file = open(filename, "r")
        except IOError:
            channel.pushstatus(404, "Not found")
            channel.push("Content-type: text/html/r/n")
            channel.push("/r/n")
            channel.push("<html><body>File not found.</body></html>/r/n")
        else:
            channel.pushstatus(200, "OK")
            channel.push("Content-type: text/html/r/n")
            channel.push("/r/n")
            channel.push_with_producer(FileProducer(file))

#
# try it out

s = HTTPServer(PORT)
print "serving at port", PORT
asyncore.loop()

serving at port 8000
log: adding channel <HTTPServer  at 8e54d0>
log: adding channel <HTTPChannel  at 8e64a0>
samples/sample.htm => ./samples/sample.htm
log: closing channel 96:<HTTPChannel connected at 8e64a0>

7.6. urllib 模塊

urlib 模塊爲 HTTP , FTP , 以及 gopher 提供了一個統一的客戶端接口. 它會自動地根據 URL 選擇合適的協議處理器.

從 URL 獲取數據是很是簡單的. 只須要調用 urlopen 方法, 而後從返回的流對象中讀取數據便可, 如 Example 7-14 所示.

7.6.0.1. Example 7-14. 使用 urllib 模塊獲取遠程資源

File: urllib-example-1.py

import urllib

fp = urllib.urlopen("http://www.python.org")

op = open("out.html", "wb")

n = 0

while 1:
    s = fp.read(8192)
    if not s:
        break
    op.write(s)
    n = n + len(s)

fp.close()
op.close()

for k, v in fp.headers.items():
    print k, "=", v

print "copied", n, "bytes from", fp.url

server = Apache/1.3.6 (Unix)
content-type = text/html
accept-ranges = bytes
date = Mon, 11 Oct 1999 20:11:40 GMT
connection = close
etag = "741e9-7870-37f356bf"
content-length = 30832
last-modified = Thu, 30 Sep 1999 12:25:35 GMT
copied 30832 bytes from http://www.python.org

這個流對象提供了一些非標準的屬性. headers 是一個 Message 對象(在 mimetools 模塊中定義), url 是實際的 URL . 後者會根據服務器的重定向而更新.

urlopen 函數其實是一個輔助函數, 它會建立一個 FancyURLopener 類的實例並調用它的 open 方法. 你也能夠繼承這個類來完成特殊的行爲. 例如Example 7-15 中的類會自動地 在必要時登錄服務器.

7.6.0.2. Example 7-15. 用 urllib 模塊實現自動身份驗證

File: urllib-example-3.py

import urllib

class myURLOpener(urllib.FancyURLopener):
    # read an URL, with automatic HTTP authentication

    def setpasswd(self, user, passwd):
        self._ _user = user
        self._ _passwd = passwd

    def prompt_user_passwd(self, host, realm):
        return self._ _user, self._ _passwd

urlopener = myURLOpener()
urlopener.setpasswd("mulder", "trustno1")

fp = urlopener.open("http://www.secretlabs.com")
print fp.read()

7.7. urlparse 模塊

urlparse 模塊包含用於處理 URL 的函數, 能夠在 URL 和平臺特定的文件名間相互轉換. 如 Example 7-16 所示.

7.7.0.1. Example 7-16. 使用 urlparse 模塊

File: urlparse-example-1.py

import urlparse

print urlparse.urlparse("http://host/path;params?query#fragment")

('http', 'host', '/path', 'params', 'query', 'fragment')

一個常見用途就是把 HTTP URL 分割爲主機名和路徑組件(一個 HTTP 請求會涉及到 主機名以及請求路徑), 如 Example 7-17 所示.

7.7.0.2. Example 7-17. 使用 urlparse 模塊處理 HTTP 定位器( HTTP Locators )

File: urlparse-example-2.py

import urlparse

scheme, host, path, params, query, fragment =/
        urlparse.urlparse("http://host/path;params?query#fragment")

if scheme == "http":
    print "host", "=>", host
    if params:
        path = path + ";" + params
    if query:
        path = path + "?" + query
    print "path", "=>", path

host => host
path => /path;params?query

Example 7-18 展現瞭如何使用 urlunparse 函數將各組成部分合並回一個 URL .

7.7.0.3. Example 7-18. 使用 urlparse 模塊處理 HTTP 定位器( HTTP Locators )

File: urlparse-example-3.py

import urlparse

scheme, host, path, params, query, fragment =/
        urlparse.urlparse("http://host/path;params?query#fragment")

if scheme == "http":
    print "host", "=>", host
    print "path", "=>", urlparse.urlunparse(
    (None, None, path, params, query, None)
    )

host => host
path => /path;params?query

Example 7-19 使用 urljoin 函數將絕對路徑和相對路徑組合起來.

7.7.0.4. Example 7-19. 使用 urlparse 模塊組合相對定位器

File: urlparse-example-4.py

import urlparse

base = "http://spam.egg/my/little/pony"

for path in "/index", "goldfish", "../black/cat":
    print path, "=>", urlparse.urljoin(base, path)

/index => http://spam.egg/index
goldfish => http://spam.egg/my/little/goldfish
../black/cat => http://spam.egg/my/black/cat

7.8. cookie 模塊

(2.0 中新增) 該模塊爲 HTTP 客戶端和服務器提供了基本的 cookie 支持. Example 7-20 展現了它的使用.

7.8.0.1. Example 7-20. 使用 cookie 模塊

File: cookie-example-1.py

import Cookie
import os, time

cookie = Cookie.SimpleCookie()
cookie["user"] = "Mimi"
cookie["timestamp"] = time.time()

print cookie

# simulate CGI roundtrip
os.environ["HTTP_COOKIE"] = str(cookie)

print

cookie = Cookie.SmartCookie()
cookie.load(os.environ["HTTP_COOKIE"])

for key, item in cookie.items():
    # dictionary items are "Morsel" instances
    # use value attribute to get actual value
    print key, repr(item.value)


Set-Cookie: timestamp=736513200;
Set-Cookie: user=Mimi;

user 'Mimi'
timestamp '736513200'

7.9. robotparser 模塊

(2.0 中新增) robotparser 模塊用來讀取 robots.txt 文件, 該文件用於 Robot Exclusion Protocol (搜索機器人排除協議?http://info.webcrawler.com/mak/projects/robots/robots.html).

若是你實現的一個 HTTP 機器人會訪問網路上的任意站點(並不僅是你本身的站點), 那麼最好仍是用該模塊檢查下你所作的一切是否是受歡迎的. Example 7-21 展現了該模塊的使用.

7.9.0.1. Example 7-21. 使用 robotparser 模塊

File: robotparser-example-1.py

import robotparser

r = robotparser.RobotFileParser()
r.set_url("http://www.python.org/robots.txt")
r.read()

if r.can_fetch("*", "/index.html"):
    print "may fetch the home page"

if r.can_fetch("*", "/tim_one/index.html"):
    print "may fetch the tim peters archive"

may fetch the home page

7.10. ftplib 模塊

ftplib 模塊包含了一個 File Transfer Protocol (FTP , 文件傳輸協議)客戶端的實現.

Example 7-22 展現瞭如何登錄並得到登錄目錄的文件列表. 注意這裏的文件列表 (列目錄操做)格式與服務器有關(通常和主機平臺的列目錄工具輸出格式相同, 例如 Unix 下的 ls 和 Windows/DOS 下的 dir ).

7.10.0.1. Example 7-22. 使用 ftplib 模塊得到目錄列表

File: ftplib-example-1.py

import ftplib

ftp = ftplib.FTP("www.python.org")
ftp.login("anonymous", "ftplib-example-1")

print ftp.dir()

ftp.quit()

total 34
drwxrwxr-x  11 root     4127         512 Sep 14 14:18 .
drwxrwxr-x  11 root     4127         512 Sep 14 14:18 ..
drwxrwxr-x   2 root     4127         512 Sep 13 15:18 RCS
lrwxrwxrwx   1 root     bin           11 Jun 29 14:34 README -> welcome.msg
drwxr-xr-x   3 root     wheel        512 May 19  1998 bin
drwxr-sr-x   3 root     1400         512 Jun  9  1997 dev
drwxrwxr--   2 root     4127         512 Feb  8  1998 dup
drwxr-xr-x   3 root     wheel        512 May 19  1998 etc
...

下載文件很簡單; 使用合適的 retr 函數便可. 注意當你下載文本文件時, 你必須本身加上行結束符. Example 7-23 中使用了一個 lambda 表達式完成這項工做.

7.10.0.2. Example 7-23. 使用 ftplib 模塊下載文件

File: ftplib-example-2.py

import ftplib
import sys

def gettext(ftp, filename, outfile=None):
    # fetch a text file
    if outfile is None:
        outfile = sys.stdout
    # use a lambda to add newlines to the lines read from the server
    ftp.retrlines("RETR " + filename, lambda s, w=outfile.write: w(s+"/n"))

def getbinary(ftp, filename, outfile=None):
    # fetch a binary file
    if outfile is None:
        outfile = sys.stdout
    ftp.retrbinary("RETR " + filename, outfile.write)

ftp = ftplib.FTP("www.python.org")
ftp.login("anonymous", "ftplib-example-2")

gettext(ftp, "README")
getbinary(ftp, "welcome.msg")

WELCOME to python.org, the Python programming language home site.

You are number %N of %M allowed users.  Ni!

Python Web site: http://www.python.org/

CONFUSED FTP CLIENT?  Try begining your login password with '-' dash.
This turns off continuation messages that may be confusing your client.
...

最後, Example 7-24 將文件複製到 FTP 服務器上. 這個腳本使用文件擴展名來 判斷文件是文本文件仍是二進制文件.

7.10.0.3. Example 7-24. 使用 ftplib 模塊上傳文件

File: ftplib-example-3.py

import ftplib
import os

def upload(ftp, file):
    ext = os.path.splitext(file)[1]
    if ext in (".txt", ".htm", ".html"):
        ftp.storlines("STOR " + file, open(file))
    else:
        ftp.storbinary("STOR " + file, open(file, "rb"), 1024)

ftp = ftplib.FTP("ftp.fbi.gov")
ftp.login("mulder", "trustno1")

upload(ftp, "trixie.zip")
upload(ftp, "file.txt")
upload(ftp, "sightings.jpg")

7.11. gopherlib 模塊

gopherlib 模塊包含了一個 gopher 客戶端實現, 如 Example 7-25 所示.

7.11.0.1. Example 7-25. 使用 gopherlib 模塊

File: gopherlib-example-1.py

import gopherlib

host = "gopher.spam.egg"

f = gopherlib.send_selector("1/", host)
for item in gopherlib.get_directory(f):
    print item

['0', "About Spam.Egg's Gopher Server", "0/About's Spam.Egg's
Gopher Server", 'gopher.spam.egg', '70', '+']
['1', 'About Spam.Egg', '1/Spam.Egg', 'gopher.spam.egg', '70', '+']
['1', 'Misc', '1/Misc', 'gopher.spam.egg', '70', '+']
...

7.12. httplib 模塊

httplib 模塊提供了一個 HTTP 客戶端接口, 如 Example 7-26 所示.

7.12.0.1. Example 7-26. 使用 httplib 模塊

File: httplib-example-1.py

import httplib

USER_AGENT = "httplib-example-1.py"

class Error:
    # indicates an HTTP error
    def _ _init_ _(self, url, errcode, errmsg, headers):
        self.url = url
        self.errcode = errcode
        self.errmsg = errmsg
        self.headers = headers
    def _ _repr_ _(self):
        return (
            "<Error for %s: %s %s>" %
            (self.url, self.errcode, self.errmsg)
            )

class Server:

    def _ _init_ _(self, host):
        self.host = host

    def fetch(self, path):
        http = httplib.HTTP(self.host)

        # write header
        http.putrequest("GET", path)
        http.putheader("User-Agent", USER_AGENT)
        http.putheader("Host", self.host)
        http.putheader("Accept", "*/*")
        http.endheaders()

        # get response
        errcode, errmsg, headers = http.getreply()

        if errcode != 200:
            raise Error(errcode, errmsg, headers)

        file = http.getfile()
        return file.read()

if _ _name_ _ == "_ _main_ _":

    server = Server("www.pythonware.com")
    print server.fetch("/index.htm")

注意 httplib 提供的 HTTP 客戶端在等待服務器回覆的時候會阻塞程序. 異步的解決方法請參閱 asyncore 模塊中的例子.

7.12.1. 將數據發送給服務器

httplib 能夠用來發送其餘 HTTP 命令, 例如 POST , 如 Example 7-27 所示.

7.12.1.1. Example 7-27. 使用 httplib 發送數據

File: httplib-example-2.py

import httplib

USER_AGENT = "httplib-example-2.py"

def post(host, path, data, type=None):

    http = httplib.HTTP(host)

    # write header
    http.putrequest("PUT", path)
    http.putheader("User-Agent", USER_AGENT)
    http.putheader("Host", host)
    if type:
        http.putheader("Content-Type", type)
    http.putheader("Content-Length", str(len(size)))
    http.endheaders()

    # write body
    http.send(data)

    # get response
    errcode, errmsg, headers = http.getreply()

    if errcode != 200:
        raise Error(errcode, errmsg, headers)

    file = http.getfile()
    return file.read()

if _ _name_ _ == "_ _main_ _":

    post("www.spam.egg", "/bacon.htm", "a piece of data", "text/plain")

7.13. poplib 模塊

poplib 模塊(如 Example 7-28 所示) 提供了一個 Post Office Protocol ( POP3 協議) 客戶端實現. 這個協議用來從郵件服務器 "pop" (拷貝) 信息到你的我的電腦.

7.13.0.1. Example 7-28. 使用 poplib 模塊

File: poplib-example-1.py

import poplib
import string, random
import StringIO, rfc822

SERVER = "pop.spam.egg"

USER  = "mulder"
PASSWORD = "trustno1"

# connect to server
server = poplib.POP3(SERVER)

# login
server.user(USER)
server.pass_(PASSWORD)

# list items on server
resp, items, octets = server.list()

# download a random message
id, size = string.split(random.choice(items))
resp, text, octets = server.retr(id)

text = string.join(text, "/n")
file = StringIO.StringIO(text)

message = rfc822.Message(file)

for k, v in message.items():
    print k, "=", v

print message.fp.read()

subject = ANN: (the eff-bot guide to) The Standard Python Library
message-id = <199910120808.KAA09206@spam.egg>
received = (from fredrik@spam.egg)
 by spam.egg (8.8.7/8.8.5) id KAA09206
 for mulder; Tue, 12 Oct 1999 10:08:47 +0200
from = Fredrik Lundh <fredrik@spam.egg>
date = Tue, 12 Oct 1999 10:08:47 +0200
to = mulder@spam.egg

...

7.14. imaplib 模塊

imaplib 模塊提供了一個 Internet Message Access Protocol ( IMAP, Internet 消息訪問協議) 的客戶端實現. 這個協議容許你訪問郵件服務器的郵件目錄, 就好像是在本機訪問同樣. 如 Example 7-29 所示.

7.14.0.1. Example 7-29. 使用 imaplib 模塊

File: imaplib-example-1.py

import imaplib
import string, random
import StringIO, rfc822

SERVER = "imap.spam.egg"

USER  = "mulder"
PASSWORD = "trustno1"

# connect to server
server = imaplib.IMAP4(SERVER)

# login
server.login(USER, PASSWORD)
server.select()

# list items on server
resp, items = server.search(None, "ALL")
items = string.split(items[0])

# fetch a random item
id = random.choice(items)
resp, data = server.fetch(id, "(RFC822)")
text = data[0][1]

file = StringIO.StringIO(text)

message = rfc822.Message(file)

for k, v in message.items():
    print k, "=", v

print message.fp.read()

server.logout()

subject = ANN: (the eff-bot guide to) The Standard Python Library
message-id = <199910120816.KAA12177@larch.spam.egg>
to = mulder@spam.egg
date = Tue, 12 Oct 1999 10:16:19 +0200 (MET DST)
from = <effbot@spam.egg>
received = (effbot@spam.egg) by imap.algonet.se (8.8.8+Sun/8.6.12)
id KAA12177 for effbot@spam.egg; Tue, 12 Oct 1999 10:16:19 +0200
(MET DST)

body text for test 5

7.15. smtplib 模塊

smtplib 模塊提供了一個 Simple Mail Transfer Protocol ( SMTP , 簡單郵件傳輸協議) 客戶端實現. 該協議用於經過 Unix 郵件服務器發送郵件, 如 Example 7-30 所示.

讀取郵件請使用 poplib 或 imaplib 模塊.

7.15.0.1. Example 7-30. 使用 smtplib 模塊

File: smtplib-example-1.py

import smtplib
import string, sys

HOST = "localhost"

FROM = "effbot@spam.egg"
TO = "fredrik@spam.egg"

SUBJECT = "for your information!"

BODY = "next week: how to fling an otter"

body = string.join((
    "From: %s" % FROM,
    "To: %s" % TO,
    "Subject: %s" % SUBJECT,
    "",
    BODY), "/r/n")

print body

server = smtplib.SMTP(HOST)
server.sendmail(FROM, [TO], body)
server.quit()

From: effbot@spam.egg
To: fredrik@spam.egg
Subject: for your information!

next week: how to fling an otter

7.16. telnetlib 模塊

telnetlib 模塊提供了一個 telnet 客戶端實現.

Example 7-31 鏈接到一臺 Unix 計算機, 登錄, 而後請求一個目錄的列表.

7.16.0.1. Example 7-31. 使用 telnetlib 模塊登錄到遠程服務器

File: telnetlib-example-1.py

import telnetlib
import sys

HOST = "spam.egg"

USER = "mulder"
PASSWORD = "trustno1"

telnet = telnetlib.Telnet(HOST)

telnet.read_until("login: ")
telnet.write(USER + "/n")

telnet.read_until("Password: ")
telnet.write(PASSWORD + "/n")

telnet.write("ls librarybook/n")
telnet.write("exit/n")

print telnet.read_all()

[spam.egg mulder]$ ls
README                                 os-path-isabs-example-1.py
SimpleAsyncHTTP.py                     os-path-isdir-example-1.py
aifc-example-1.py                      os-path-isfile-example-1.py
anydbm-example-1.py                    os-path-islink-example-1.py
array-example-1.py                     os-path-ismount-example-1.py
...

7.17. nntplib 模塊

nntplib 模塊提供了一個網絡新聞傳輸協議( Network News Transfer Protocol, NNTP )客戶端的實現.

7.17.1. 列出消息

重新聞服務器上讀取消息以前, 你必須鏈接這個服務器並選擇一個新聞組. Example 7-32 中的腳本會從服務器下載一個完成的消息列表, 而後根據列表作簡單的統計.

7.17.1.1. Example 7-32. 使用 nntplib 模塊列出消息

File: nntplib-example-1.py

import nntplib
import string

SERVER = "news.spam.egg"
GROUP  = "comp.lang.python" 
AUTHOR = "fredrik@pythonware.com" # eff-bots human alias

# connect to server
server = nntplib.NNTP(SERVER)

# choose a newsgroup
resp, count, first, last, name = server.group(GROUP)
print "count", "=>", count
print "range", "=>", first, last

# list all items on the server
resp, items = server.xover(first, last)

# extract some statistics
authors = {}
subjects = {}
for id, subject, author, date, message_id, references, size, lines in items:
    authors[author] = None
    if subject[:4] == "Re: ":
        subject = subject[4:]
    subjects[subject] = None
    if string.find(author, AUTHOR) >= 0:
        print id, subject
    
print "authors", "=>", len(authors)
print "subjects", "=>", len(subjects)

count => 607
range => 57179 57971
57474 Three decades of Python!
...
57477 More Python books coming...
authors => 257
subjects => 200

7.17.2. 下載消息

下載消息是很簡單的, 只須要調用 article方法, 如 Example 7-33 所示.

7.17.2.1. Example 7-33. 使用 nntplib 模塊下載消息

File: nntplib-example-2.py

import nntplib
import string

SERVER = "news.spam.egg"
GROUP  = "comp.lang.python" 
KEYWORD = "tkinter"

# connect to server
server = nntplib.NNTP(SERVER)

resp, count, first, last, name = server.group(GROUP)
resp, items = server.xover(first, last)
for id, subject, author, date, message_id, references, size, lines in items:
    if string.find(string.lower(subject), KEYWORD) >= 0:
        resp, id, message_id, text = server.article(id)
        print author
        print subject
        print len(text), "lines in article"

"Fredrik Lundh" <fredrik@pythonware.com>
Re: Programming Tkinter (In Python)
110 lines in article
...

Example 7-34 展現瞭如何進一步處理這些消息, 你能夠把它封裝到一個 Message 對象中(使用 rfc822 模塊).

7.17.2.2. Example 7-34. 使用 nntplib 和 rfc822 模塊處理消息

File: nntplib-example-3.py

import nntplib
import string, random
import StringIO, rfc822

SERVER = "news.spam.egg"
GROUP  = "comp.lang.python"

# connect to server
server = nntplib.NNTP(SERVER)

resp, count, first, last, name = server.group(GROUP)
for i in range(10):
    try:
        id = random.randint(int(first), int(last))
        resp, id, message_id, text = server.article(str(id))
    except (nntplib.error_temp, nntplib.error_perm):
        pass # no such message (maybe it was deleted?)
    else:
        break # found a message!
else:
    raise SystemExit

text = string.join(text, "/n")
file = StringIO.StringIO(text)

message = rfc822.Message(file)

for k, v in message.items():
    print k, "=", v

print message.fp.read()

mime-version = 1.0
content-type = text/plain; charset="iso-8859-1"
message-id = <008501bf1417$1cf90b70$f29b12c2@sausage.spam.egg>
lines = 22
...
from = "Fredrik Lundh" <fredrik@pythonware.com>
nntp-posting-host = parrot.python.org
subject = ANN: (the eff-bot guide to) The Standard Python Library
...
</F>

到這一步後, 你可使用 htmllib , uu , 以及 base64 繼續處理這些消息.


7.18. SocketServer 模塊

SocketServer 爲各類基於 socket 的服務器提供了一個框架. 該模塊提供了大量的類, 你能夠用它們來建立不一樣的服務器.

Example 7-35 使用該模塊實現了一個 Internet 時間協議服務器. 你能夠用前邊的 timeclient 腳本鏈接它.

7.18.0.1. Example 7-35. 使用 SocketServer 模塊

File: socketserver-example-1.py

import SocketServer
import time

# user-accessible port
PORT = 8037

# reference time
TIME1970 = 2208988800L

class TimeRequestHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        print "connection from", self.client_address
        t = int(time.time()) + TIME1970
        b = chr(t>>24&255) + chr(t>>16&255) + chr(t>>8&255) + chr(t&255)
        self.wfile.write(b)

server = SocketServer.TCPServer(("", PORT), TimeRequestHandler)
print "listening on port", PORT
server.serve_forever()

connection from ('127.0.0.1', 1488)
connection from ('127.0.0.1', 1489)
...

7.19. BaseHTTPServer 模塊

這是一個創建在 SocketServer 框架上的基本框架, 用於 HTTP 服務器.

Example 7-36 在每次從新載入頁面時會生成一條隨機信息. path 變量包含當前 URL , 你可使用它爲不一樣的 URL 生成不一樣的內容 (訪問除根目錄的其餘任何 path 該腳本都會返回一個錯誤頁面).

7.19.0.1. Example 7-36. 使用 BaseHTTPServer 模塊

File: basehttpserver-example-1.py

import BaseHTTPServer
import cgi, random, sys

MESSAGES = [
    "That's as maybe, it's still a frog.",
    "Albatross! Albatross! Albatross!",
    "It's Wolfgang Amadeus Mozart.",
    "A pink form from Reading.",
    "Hello people, and welcome to 'It's a Tree.'"
    "I simply stare at the brick and it goes to sleep.",
]

class Handler(BaseHTTPServer.BaseHTTPRequestHandler):

    def do_GET(self):
        if self.path != "/":
            self.send_error(404, "File not found")
            return
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        try:
            # redirect stdout to client
            stdout = sys.stdout
            sys.stdout = self.wfile
            self.makepage()
        finally:
            sys.stdout = stdout # restore
        
    def makepage(self):
        # generate a random message
        tagline = random.choice(MESSAGES)
        print "<html>"
        print "<body>"
        print "<p>Today's quote: "
        print "<i>%s</i>" % cgi.escape(tagline)
        print "</body>"
        print "</html>"

PORT = 8000

httpd = BaseHTTPServer.HTTPServer(("", PORT), Handler)
print "serving at port", PORT
httpd.serve_forever()

更有擴展性的 HTTP 框架請參閱 SimpleHTTPServer 和 CGIHTTPServer 模塊.


7.20. SimpleHTTPServer 模塊

SimpleHTTPServer 模塊是一個簡單的 HTTP 服務器, 它提供了標準的 GET 和 HEAD 請求處理器. 客戶端請求的路徑名稱會被翻譯爲一個相對文件名 (相對於服務器啓動時的當前路徑). Example 7-37 展現了該模塊的使用.

7.20.0.1. Example 7-37. 使用 SimpleHTTPServer 模塊

File: simplehttpserver-example-1.py

import SimpleHTTPServer
import SocketServer

# minimal web server.  serves files relative to the
# current directory.

PORT = 8000

Handler = SimpleHTTPServer.SimpleHTTPRequestHandler

httpd = SocketServer.TCPServer(("", PORT), Handler)

print "serving at port", PORT
httpd.serve_forever()

serving at port 8000
localhost - - [11/Oct/1999 15:07:44] code 403, message Directory listing not sup
ported
localhost - - [11/Oct/1999 15:07:44] "GET / HTTP/1.1" 403 -
localhost - - [11/Oct/1999 15:07:56] "GET /samples/sample.htm HTTP/1.1" 200 -

這個服務器會忽略驅動器符號和相對路徑名(例如 `..`). 但它並無任何訪問驗證處理, 因此請當心使用.

Example 7-38 實現了個迷你的 web 代理. 發送給代理的 HTTP 請求必須包含目標服務器的完整 URI . 代理服務器使用 urllib 來獲取目標服務器的數據.

7.20.0.2. Example 7-38. 使用 SimpleHTTPServer 模塊實現代理

File: simplehttpserver-example-2.py

# a truly minimal HTTP proxy

import SocketServer
import SimpleHTTPServer
import urllib

PORT = 1234

class Proxy(SimpleHTTPServer.SimpleHTTPRequestHandler):
    def do_GET(self):
        self.copyfile(urllib.urlopen(self.path), self.wfile)

httpd = SocketServer.ForkingTCPServer(('', PORT), Proxy)
print "serving at port", PORT
httpd.serve_forever()

7.21. CGIHTTPServer 模塊

CGIHTTPServer 模塊是一個能夠經過公共網關接口( common gateway interface , CGI )調用外部腳本的 HTTP 服務器. 如 Example 7-39 所示.

7.21.0.1. Example 7-39. 使用 CGIHTTPServer 模塊

File: cgihttpserver-example-1.py

import CGIHTTPServer
import BaseHTTPServer

class Handler(CGIHTTPServer.CGIHTTPRequestHandler):
    cgi_directories = ["/cgi"]

PORT = 8000

httpd = BaseHTTPServer.HTTPServer(("", PORT), Handler)
print "serving at port", PORT
httpd.serve_forever()

7.22. cgi 模塊

cgi 模塊爲 CGI 腳本提供了函數和類支持. 它還能夠處理 CGI 表單數據.

Example 7-40 展現了一個簡單的 CGI 腳本, 它返回給定目錄下的文件列表 (相對於腳本中指定的根目錄)

7.22.0.1. Example 7-40. 使用 cgi 模塊

File: cgi-example-1.py

import cgi
import os, urllib

ROOT = "samples"

# header
print "text/html"
print

query = os.environ.get("QUERY_STRING")
if not query:
    query = "."

script = os.environ.get("SCRIPT_NAME", "")
if not script:
    script = "cgi-example-1.py"

print "<html>"
print "<head>"
print "<title>file listing</title>"
print "</head>"
print "</html>"

print "<body>"

try:
    files = os.listdir(os.path.join(ROOT, query))
except os.error:
    files = []

for file in files:
    link = cgi.escape(file)
    if os.path.isdir(os.path.join(ROOT, query, file)):
        href = script + "?" + os.path.join(query, file)
        print "<p><a href= '%s'>%s</a>" % (href, cgi.escape(link))
    else:
        print "<p>%s" % link

print "</body>"
print "</html>"

text/html

<html>
<head>
<title>file listing</title>
</head>
</html>
<body>
<p>sample.gif
<p>sample.gz
<p>sample.netrc
...
<p>sample.txt
<p>sample.xml
<p>sample~
<p><a href='cgi-example-1.py?web'>web</a>
</body>
</html>

7.23. webbrowser 模塊

(2.0 中新增) webbrowser 模塊提供了一個到系統標準 web 瀏覽器的接口. 它提供了一個 open 函數, 接受文件名或 URL 做爲參數, 而後在瀏覽器中打開它. 若是你又一次調用 open 函數, 那麼它會嘗試在相同的窗口打開新頁面. 如 Example 7-41 所示.

7.23.0.1. Example 7-41. 使用 webbrowser 模塊

File: webbrowser-example-1.py

import webbrowser
import time

webbrowser.open("http://www.pythonware.com")

# wait a while, and then go to another page
time.sleep(5)
webbrowser.open(
    "http://www.pythonware.com/people/fredrik/librarybook.htm"
    )

在 Unix 下, 該模塊支持 lynx , Netscape , Mosaic , Konquerer , 和 Grail . 在 Windows 和 Macintosh 下, 它會調用標準瀏覽器 (在註冊表或是 Internet 選項面板中定義).


8. 國際化


8.1. locale 模塊

locale 模塊提供了 C 本地化( localization )函數的接口, 如 Example 8-1 所示. 同時提供相關函數, 實現基於當前 locale 設置的數字, 字符串轉換. (而 int ,float , 以及 string 模塊中的相關轉換函數不受 locale 設置的影響.)

====Example 8-1. 使用 locale 模塊格式化數據=====[eg-8-1]

File: locale-example-1.py

import locale

print "locale", "=>", locale.setlocale(locale.LC_ALL, "")

# integer formatting
value = 4711
print locale.format("%d", value, 1), "==",
print locale.atoi(locale.format("%d", value, 1))

# floating point
value = 47.11
print locale.format("%f", value, 1), "==",
print locale.atof(locale.format("%f", value, 1))

info = locale.localeconv()
print info["int_curr_symbol"]

locale => Swedish_Sweden.1252
4,711 == 4711
47,110000 == 47.11
SEK

Example 8-2 展現瞭如何使用 locale 模塊得到當前平臺 locale 設置.

8.1.0.1. Example 8-2. 使用 locale 模塊得到當前平臺 locale 設置

File: locale-example-2.py

import locale

language, encoding = locale.getdefaultlocale()

print "language", language
print "encoding", encoding

language sv_SE
encoding cp1252

8.2. unicodedata 模塊

( 2.0 中新增) unicodedata 模塊包含了 Unicode 字符的屬性, 例如字符類別, 分解數據, 以及數值. 如 Example 8-3 所示.

8.2.0.1. Example 8-3. 使用 unicodedata 模塊

File: unicodedata-example-1.py

import unicodedata

for char in [u"A", u"-", u"1", u"/N{LATIN CAPITAL LETTER O WITH DIAERESIS}"]:
    print repr(char),
    print unicodedata.category(char),
    print repr(unicodedata.decomposition(char)),

    print unicodedata.decimal(char, None),
    print unicodedata.numeric(char, None)

u'A' Lu '' None None
u'-' Pd '' None None
u'1' Nd '' 1 1.0
u'/303/226' Lu '004F 0308' None None

在 Python 2.0 中缺乏 CJK 象形文字和韓語音節的屬性. 這影響到了 0x3400-0x4DB5 , 0x4E00-0x9FA5 , 以及 0xAC00-D7A3 中的字符, 不過每一個區間內的第一個字符屬性是正確的, 咱們能夠把字符映射到起始 實現正常操做:

def remap(char):
    # fix for broken unicode property database in Python 2.0
    c = ord(char)
    if 0x3400 <= c <= 0x4DB5:
        return unichr(0x3400)
    if 0x4E00 <= c <= 0x9FA5:
        return unichr(0x4E00)
    if 0xAC00 <= c <= 0xD7A3:
        return unichr(0xAC00)
    return char

Python 2.1 修復了這個 bug .


8.3. ucnhash 模塊

(僅適用於 2.0 ) ucnhash 模塊爲一些 Unicode 字符代碼提供了特定的命名. 你能夠直接使用 /N{} 轉義符將 Unicode 字符名稱映射到字符代碼上. 如 Example 8-4 所示.

8.3.0.1. Example 8-4. 使用 ucnhash 模塊

File: ucnhash-example-1.py

# Python imports this module automatically, when it sees
# the first /N{} escape
# import ucnhash

print repr(u"/N{FROWN}")
print repr(u"/N{SMILE}")
print repr(u"/N{SKULL AND CROSSBONES}")

u'/u2322'
u'/u2323'
u'/u2620'

9. 多媒體相關模塊

"Wot? No quote?"
- Guido van Rossum

9.1. 概覽

Python 提供了一些用於處理圖片和音頻文件的模塊.

另請參閱 Pythonware Image Library ( PIL , http://www.pythonware.com/products/pil/ ), 以及 PythonWare Sound Toolkit (PST ,http://www.pythonware.com/products/pst/ ).

譯註: 別參閱 PST 了, 廢了, 用 pymedia 代替吧.


9.2. imghdr 模塊

imghdr 模塊可識別不一樣格式的圖片文件. 當前版本能夠識別 bmp , gif , jpeg , pbm , pgm , png , ppm , rast (Sun raster), rgb (SGI), tiff , 以及 xbm 圖像. 如Example 9-1 所示.

9.2.0.1. Example 9-1. 使用 imghdr 模塊

File: imghdr-example-1.py

import imghdr

result = imghdr.what("samples/sample.jpg")

if result:
    print "file format:", result
else:
    print "cannot identify file"

file format: jpeg
# 使用 PIL 
import Image

im = Image.open("samples/sample.jpg")
print im.format, im.mode, im.size

9.3. sndhdr 模塊

sndhdr 模塊, 可來識別不一樣的音頻文件格式, 並提取文件內容相關信息. 如 Example 9-2 所示.

執行成功後, what 函數將返回一個由文件類型, 採樣頻率, 聲道數, 音軌數和每一個採樣點位數組成的元組. 具體含義請參考 help(sndhdr) .

9.3.0.1. Example 9-2. 使用 sndhdr 模塊

File: sndhdr-example-1.py

import sndhdr

result = sndhdr.what("samples/sample.wav")

if result:
    print "file format:", result
else:
    print "cannot identify file"

file format: ('wav', 44100, 1, -1, 16)

9.4. whatsound 模塊

(已廢棄) whatsound 是 sndhdr 模塊的一個別名. 如 Example 9-3 所示.

9.4.0.1. Example 9-3. 使用 whatsound 模塊

File: whatsound-example-1.py

import whatsound # same as sndhdr

result = whatsound.what("samples/sample.wav")

if result:
    print "file format:", result
else:
    print "cannot identify file"

file format: ('wav', 44100, 1, -1, 16)

9.5. aifc 模塊

aifc 模塊用於讀寫 AIFF 和 AIFC 音頻文件(在 SGI 和 Macintosh 的計算機上使用). 如 Example 9-4 所示.

9.5.0.1. Example 9-4. 使用 aifc 模塊

File: SimpleAsyncHTTP.py

import asyncore
import string, socket
import StringIO
import mimetools, urlparse

class AsyncHTTP(asyncore.dispatcher_with_send):
    # HTTP requestor

    def _ _init_ _(self, uri, consumer):
        asyncore.dispatcher_with_send._ _init_ _(self)

        self.uri = uri
        self.consumer = consumer

        # turn the uri into a valid request
        scheme, host, path, params, query, fragment = urlparse.urlparse(uri)
        assert scheme == "http", "only supports HTTP requests"
        try:
            host, port = string.split(host, ":", 1)
            port = int(port)
        except (TypeError, ValueError):
            port = 80 # default port
        if not path:
            path = "/"
        if params:
            path = path + ";" + params
        if query:
            path = path + "?" + query

        self.request = "GET %s HTTP/1.0/r/nHost: %s/r/n/r/n" % (path, host)

        self.host = host
        self.port = port

        self.status = None
        self.header = None

        self.data = ""

        # get things going!
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((host, port))

    def handle_connect(self):
        # connection succeeded
        self.send(self.request)

    def handle_expt(self):
        # connection failed; notify consumer (status is None)
        self.close()
        try:
            http_header = self.consumer.http_header
        except AttributeError:
            pass
        else:
            http_header(self)

    def handle_read(self):
        data = self.recv(2048)
        if not self.header:
            self.data = self.data + data
            try:
                i = string.index(self.data, "/r/n/r/n")
            except ValueError:
                return # continue
            else:
                # parse header
                fp = StringIO.StringIO(self.data[:i+4])
                # status line is "HTTP/version status message"
                status = fp.readline()
                self.status = string.split(status, " ", 2)
                # followed by a rfc822-style message header
                self.header = mimetools.Message(fp)
                # followed by a newline, and the payload (if any)
                data = self.data[i+4:]
                self.data = ""
                # notify consumer (status is non-zero)
                try:
                    http_header = self.consumer.http_header
                except AttributeError:
                    pass
                else:
                    http_header(self)
                if not self.connected:
                    return # channel was closed by consumer

        self.consumer.feed(data)

    def handle_close(self):
        self.consumer.close()
        self.close()

9.6. sunau 模塊

sunau 模塊用於讀寫 Sun AU 音頻文件. 如 Example 9-5 所示.

9.6.0.1. Example 9-5. 使用 sunau 模塊

File: sunau-example-1.py

import sunau

w = sunau.open("samples/sample.au", "r")

if w.getnchannels() == 1:
    print "mono,",
else:
    print "stereo,",

print w.getsampwidth()*8, "bits,",
print w.getframerate(), "Hz sampling rate"

mono, 16 bits, 8012 Hz sampling rate

9.7. sunaudio 模塊

sunaudio 模塊用於識別 Sun AU 音頻文件, 並提取其基本信息. sunau 模塊爲 Sun AU 文件提供了更完成的支持. 如 Example 9-6 所示

9.7.0.1. Example 9-6. 使用 sunaudio 模塊

File: sunaudio-example-1.py

import sunaudio

file = "samples/sample.au"

print sunaudio.gethdr(open(file, "rb"))

(6761, 1, 8012, 1, 'sample.au')

9.8. wave 模塊

wave 模塊用於讀寫 Microsoft WAV 音頻文件, 如 Example 9-7 所示.

9.8.0.1. Example 9-7. 使用 wave 模塊

File: wave-example-1.py

import wave

w = wave.open("samples/sample.wav", "r")

if w.getnchannels() == 1:
    print "mono,",
else:
    print "stereo,",

print w.getsampwidth()*8, "bits,",
print w.getframerate(), "Hz sampling rate"

mono, 16 bits, 44100 Hz sampling rate

9.9. audiodev 模塊

(只用於 Unix) audiodev 爲 Sun 和 SGI 計算機提供了音頻播放支持. 如 Example 9-8 所示.

9.9.0.1. Example 9-8. 使用 audiodev 模塊

File: audiodev-example-1.py

import audiodev
import aifc

sound = aifc.open("samples/sample.aiff", "r")

player = audiodev.AudioDev()

player.setoutrate(sound.getframerate())
player.setsampwidth(sound.getsampwidth())
player.setnchannels(sound.getnchannels())

bytes_per_frame = sound.getsampwidth() * sound.getnchannels()
bytes_per_second = sound.getframerate() * bytes_per_frame

while 1:
    data = sound.readframes(bytes_per_second)
    if not data:
        break
    player.writeframes(data)

player.wait()

9.10. winsound 模塊

(只用於 Windows ) winsound 模塊容許你在 Winodws 平臺上播放 Wave 文件. 如 Example 9-9 所示.

9.10.0.1. Example 9-9. 使用 winsound 模塊

File: winsound-example-1.py

import winsound

file = "samples/sample.wav"

winsound.PlaySound(
    file,
    winsound.SND_FILENAME|winsound.SND_NOWAIT,
    )

flag 變量說明:

  • SND_FILENAME - sound 是一個 wav 文件名
  • SND_ALIAS - sound 是一個註冊表中指定的別名
  • SND_LOOP - 重複播放直到下一次 PlaySound ; 必須指定 SND_ASYNC
  • SND_MEMORY - sound 是一個 wav 文件的內存映像
  • SND_PURGE - 中止指定 sound 的全部實例
  • SND_ASYNC - 異步播放聲音, 聲音開始播放後函數當即返回
  • SND_NODEFAULT - 找不到 sound 時不播放默認的 beep 聲音
  • SND_NOSTOP - 不打斷當前播放中的任何 sound
  • SND_NOWAIT - sound 驅動忙時當即返回

10. 數據儲存

"Unlike mainstream component programming, scripts usually do not introduce new components but simply 'wire' existing ones. Scripts can be seen as introducing behavior but no new state ... Of course, there is nothing to stop a 'scripting' language from introducing persistent state — it then simply turns into a normal programming language."
- Clemens Szyperski, in Component Software

10.1. 概覽

Python 提供了多種類似數據庫管理( database manager )的驅動, 它們的模型都基於 Unix 的 dbm 庫. 這些數據庫和普通的字典對象相似, 但這裏須要注意的是它只能接受字符串做爲鍵和值. ( shelve 模塊能夠處理任何類型的值)


10.2. anydbm 模塊

anydbm 模塊爲簡單數據庫驅動提供了統一標準的接口.

當第一次被導入的時候, anydbm 模塊會自動尋找一個合適的數據庫驅動, 按照 dbhash , gdbm , dbm , 或 dumbdbm 的順序嘗試. 若是沒有找到任何模塊, 它將引起一個 ImportError 異常.

open 函數用於打開或建立一個數據庫(使用導入時找到的數據庫驅動), 如 Example 10-1 所示.

10.2.0.1. Example 10-1. 使用 anydbm 模塊

File: anydbm-example-1.py

import anydbm

db = anydbm.open("database", "c")
db["1"] = "one"
db["2"] = "two"
db["3"] = "three"
db.close()

db = anydbm.open("database", "r")
for key in db.keys():
    print repr(key), repr(db[key])

'2' 'two'
'3' 'three'
'1' 'one'

10.3. whichdb 模塊

whichdb 模塊能夠判斷給定數據庫文件的格式, 如 Example 10-2 所示.

10.3.0.1. Example 10-2. 使用 whichdb 模塊

File: whichdb-example-1.py

import whichdb

filename = "database"

result = whichdb.whichdb(filename)

if result:
    print "file created by", result
    handler = _ _import_ _(result)
    db = handler.open(filename, "r")
    print db.keys()
else:
    # cannot identify data base
    if result is None:
        print "cannot read database file", filename
    else:
        print "cannot identify database file", filename
    db = None

這個例子中使用了 _ _import_ _ 函數來導入對應模塊(還記得咱們在第一章的例子麼?).


10.4. shelve 模塊

shelve 模塊使用數據庫驅動實現了字典對象的持久保存. shelve 對象使用字符串做爲鍵, 但值能夠是任意類型, 全部能夠被 pickle 模塊處理的對象均可以做爲它的值. 如 Example 10-3 所示.

10.4.0.1. Example 10-3. 使用 shelve 模塊

File: shelve-example-1.py

import shelve

db = shelve.open("database", "c")
db["one"] = 1
db["two"] = 2
db["three"] = 3
db.close()

db = shelve.open("database", "r")
for key in db.keys():
    print repr(key), repr(db[key])

'one' 1
'three' 3
'two' 2

Example 10-4 展現瞭如何使用 shelve 處理給定的數據庫驅動.

10.4.0.2. Example 10-4. 使用 shelve 模塊處理給定數據庫

File: shelve-example-3.py

import shelve
import gdbm

def gdbm_shelve(filename, flag="c"):
    return shelve.Shelf(gdbm.open(filename, flag))

db = gdbm_shelve("dbfile")

10.5. dbhash 模塊

(可選) dbhash 模塊爲 bsddb 數據庫驅動提供了一個 dbm 兼容的接口. 如 Example 10-5 所示.

10.5.0.1. Example 10-5. 使用 dbhash 模塊

File: dbhash-example-1.py

import dbhash

db = dbhash.open("dbhash", "c")
db["one"] = "the foot"
db["two"] = "the shoulder"
db["three"] = "the other foot"
db["four"] = "the bridge of the nose"
db["five"] = "the naughty bits"
db["six"] = "just above the elbow"
db["seven"] = "two inches to the right of a very naughty bit indeed"
db["eight"] = "the kneecap"
db.close()

db = dbhash.open("dbhash", "r")
for key in db.keys():
    print repr(key), repr(db[key])

10.6. dbm 模塊

(可選) dbm 模塊提供了一個到 dbm 數據庫驅動的接口(在許多 Unix 平臺上均可用). 如 Example 10-6 所示.

10.6.0.1. Example 10-6. 使用 dbm 模塊

File: dbm-example-1.py

import dbm

db = dbm.open("dbm", "c")
db["first"] = "bruce"
db["second"] = "bruce"
db["third"] = "bruce"
db["fourth"] = "bruce"
db["fifth"] = "michael"
db["fifth"] = "bruce" # overwrite
db.close()

db = dbm.open("dbm", "r")
for key in db.keys():
    print repr(key), repr(db[key])

'first' 'bruce'
'second' 'bruce'
'fourth' 'bruce'
'third' 'bruce'
'fifth' 'bruce'

10.7. dumbdbm 模塊

dumbdbm 模塊是一個簡單的數據庫實現, 與 dbm 一類類似, 但使用純 Python 實現. 它使用兩個文件: 一個二進制文件 (.dat) 用於儲存數據, 一個文本文件 (.dir) 用於數據描述.

10.7.0.1. Example 10-7. 使用 dumbdbm 模塊

File: dumbdbm-example-1.py

import dumbdbm

db = dumbdbm.open("dumbdbm", "c")
db["first"] = "fear"
db["second"] = "surprise"
db["third"] = "ruthless efficiency"
db["fourth"] = "an almost fanatical devotion to the Pope"
db["fifth"] = "nice red uniforms"
db.close()

db = dumbdbm.open("dumbdbm", "r")
for key in db.keys():
    print repr(key), repr(db[key])

'first' 'fear'
'third' 'ruthless efficiency'
'fifth' 'nice red uniforms'
'second' 'surprise'
'fourth' 'an almost fanatical devotion to the Pope'

10.8. gdbm 模塊

(可選) gdbm 模塊提供了到 GNU dbm 數據驅動的接口, 如 Example 10-8 所示.

10.8.0.1. Example 10-8. 使用 gdbm 模塊

File: gdbm-example-1.py

import gdbm

db = gdbm.open("gdbm", "c")
db["1"] = "call"
db["2"] = "the"
db["3"] = "next"
db["4"] = "defendant"
db.close()


db = gdbm.open("gdbm", "r")

keys = db.keys()
keys.sort()
for key in keys:
    print db[key],

call the next defendant

11. 工具和實用程序

標準庫中有一些模塊既可用做模塊又能夠做爲命令行實用程序.


11.1. dis 模塊

dis 模塊是 Python 的反彙編器. 它能夠把字節碼轉換爲更容易讓人看懂的格式.

你能夠從命令行調用反彙編器. 它會編譯給定的腳本並把反彙編後的字節代碼輸出到終端上:

$ dis.py hello.py

          0 SET_LINENO              0

          3 SET_LINENO              1
          6 LOAD_CONST              0 ('hello again, and welcome to the show')
          9 PRINT_ITEM
         10 PRINT_NEWLINE
         11 LOAD_CONST              1 (None)
         14 RETURN_VALUE

固然 dis 也能夠做爲模塊使用. dis 函數接受一個類, 方法, 函數, 或者 code 對象 做爲單個參數. 如 Example 11-1 所示.

11.1.0.1. Example 11-1. 使用 dis 模塊

File: dis-example-1.py

import dis

def procedure():
    print 'hello'

dis.dis(procedure)

          0 SET_LINENO          3

          3 SET_LINENO          4
          6 LOAD_CONST          1 ('hello')
          9 PRINT_ITEM
         10 PRINT_NEWLINE
         11 LOAD_CONST          0 (None)
         14 RETURN_VALUE

11.2. pdb 模塊

pdb 模塊是標準 Python 調試器( debugger ). 它基於 bdb 調試器框架.

你能夠從命令行調用調試器 (鍵入 n  進入下一行代碼, 鍵入 help 得到可用命令列表):

$ pdb.py hello.py
> hello.py(0)?()
(Pdb) n
> hello.py()
(Pdb) n
hello again, and welcome to the show
--Return--
> hello.py(1)?()->None
(Pdb)

Example 11-2 展現瞭如何從程序中啓動調試器.

11.2.0.1. Example 11-2. 使用 pdb 模塊

File: pdb-example-1.py

import pdb

def test(n):
    j = 0
    for i in range(n):
        j = j + i
    return n

db = pdb.Pdb()
db.runcall(test, 1)

> pdb-example-1.py(3)test()
-> def test(n):
(Pdb) s
> pdb-example-1.py(4)test()
-> j = 0
(Pdb) s
> pdb-example-1.py(5)test()
-> for i in range(n):
...

11.3. bdb 模塊

bdb 模塊爲提供了一個調試器框架. 你可使用它來建立自定義的調試器, 如 Example 11-3 所示.

你須要作的只是繼承 Bdb 類, 覆蓋它的 user 方法(在每次調試器中止的時候被調用). 使用各類各樣的 set 方法能夠控制調試器.

11.3.0.1. Example 11-3. 使用 bdb 模塊

File: bdb-example-1.py

import bdb
import time

def spam(n):
    j = 0
    for i in range(n):
        j = j + i
    return n

def egg(n):
    spam(n)
    spam(n)
    spam(n)
    spam(n)

def test(n):
    egg(n)

class myDebugger(bdb.Bdb):

    run = 0

    def user_call(self, frame, args):
        name = frame.f_code.co_name or "<unknown>"
        print "call", name, args
        self.set_continue() # continue

    def user_line(self, frame):
        if self.run:
            self.run = 0
            self.set_trace() # start tracing
        else:
            # arrived at breakpoint
            name = frame.f_code.co_name or "<unknown>"
            filename = self.canonic(frame.f_code.co_filename)
            print "break at", filename, frame.f_lineno, "in", name
        print "continue..."
        self.set_continue() # continue to next breakpoint

    def user_return(self, frame, value):
        name = frame.f_code.co_name or "<unknown>"
        print "return from", name, value
        print "continue..."
        self.set_continue() # continue

    def user_exception(self, frame, exception):
        name = frame.f_code.co_name or "<unknown>"
        print "exception in", name, exception
        print "continue..."
        self.set_continue() # continue

db = myDebugger()
db.run = 1
db.set_break("bdb-example-1.py", 7)
db.runcall(test, 1)

continue...
call egg None
call spam None
break at C:/ematter/librarybook/bdb-example-1.py 7 in spam
continue...
call spam None
break at C:/ematter/librarybook/bdb-example-1.py 7 in spam
continue...
call spam None
break at C:/ematter/librarybook/bdb-example-1.py 7 in spam
continue...
call spam None
break at C:/ematter/librarybook/bdb-example-1.py 7 in spam
continue...

11.4. profile 模塊

profile 模塊是標準 Python 分析器.

和反彙編器, 調試器相同, 你能夠從命令行調用分析器:

$ profile.py hello.py

hello again, and welcome to the show

         3 function calls in 0.785 CPU seconds

  Ordered by: standard name

  ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       1  0.001    0.001    0.002    0.002 <string>:1(?)
       1  0.001    0.001    0.001    0.001 hello.py:1(?)
       1  0.783    0.783    0.785    0.785 profile:0(execfile('hello.py'))
       0  0.000             0.000          profile:0(profiler)

如 Example 11-4 所示, 咱們還能夠從程序中調用 profile 來對程序性能作分析.

11.4.0.1. Example 11-4. U使用 profile 模塊

File: profile-example-1.py

import profile

def func1():
    for i in range(1000):
        pass

def func2():
    for i in range(1000):
        func1()

profile.run("func2()")

        1003 function calls in 2.380 CPU seconds

  Ordered by: standard name

  ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       1  0.000    0.000    2.040    2.040 <string>:1(?)
    1000  1.950    0.002    1.950    0.002 profile-example-1.py:3(func1)
       1  0.090    0.090    2.040    2.040 profile-example-1.py:7(func2)
       1  0.340    0.340    2.380    2.380 profile:0(func2())
       0  0.000             0.000          profile:0(profiler)

你可使用 pstats 模塊來修改結果報告的形式.


11.5. pstats 模塊

pstats 模塊用於分析 Python 分析器收集的數據. 如 Example 11-5 所示.

11.5.0.1. Example 11-5. 使用 pstats 模塊

File: pstats-example-1.py

import pstats
import profile

def func1():
    for i in range(1000):
        pass

def func2():
    for i in range(1000):
        func1()

p = profile.Profile()
p.run("func2()")

s = pstats.Stats(p)
s.sort_stats("time", "name").print_stats()

         1003 function calls in 1.574 CPU seconds

   Ordered by: internal time, function name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1000    1.522    0.002    1.522    0.002 pstats-example-1.py:4(func1)
        1    0.051    0.051    1.573    1.573 pstats-example-1.py:8(func2)
        1    0.001    0.001    1.574    1.574 profile:0(func2())
        1    0.000    0.000    1.573    1.573 <string>:1(?)
        0    0.000             0.000          profile:0(profiler)

11.6. tabnanny 模塊

(2.0 新增) tabnanny 模塊用於檢查 Python 源文件中的含糊的縮進. 當文件混合了 tab 和空格兩種縮進時候, nanny (保姆)會當即給出提示.

在下邊使用的 badtabs.py 文件中, if 語句後的第一行使用 4 個空格和 1 個 tab . 第二行只使用了空格.

$ tabnanny.py -v samples/badtabs.py
';samples/badtabs.py': *** Line 3: trouble in tab city! ***
offending line:         print "world"

indent not equal e.g. at tab sizes 1, 2, 3, 5, 6, 7, 9

由於 Python 解釋器把 tab 做爲 8 個空格來處理, 因此這個腳本能夠正常運行. 在全部符合代碼標準(一個 tab 爲 8 個空格)的編輯器中它也會正常顯示. 固然, 這些都騙不過 nanny .

Example 11-6 展現瞭如何在你本身的程序中使用 tabnanny .

11.6.0.1. Example 11-6. 使用 tabnanny 模塊

File: tabnanny-example-1.py

import tabnanny

FILE = "samples/badtabs.py"

file = open(FILE)
for line in file.readlines():
    print repr(line)

# let tabnanny look at it
tabnanny.check(FILE)

'if 1:/012'
'    /011print "hello"/012'
'        print "world"/012'
samples/badtabs.py 3 '        print "world"'/012'

將 sys.stdout 重定向到一個 StringIO 對象就能夠捕獲輸出.


12. 其餘模塊


12.1. 概覽

本章介紹了一些平臺相關的模塊. 重點放在了適用於整個平臺家族的模塊上. (好比 Unix , Windows 家族)


12.2. fcntl 模塊

(只用於 Unix) fcntl 模塊爲 Unix上的 ioctl 和 fcntl 函數提供了一個接口. 它們用於文件句柄和 I/O 設備句柄的 "out of band" 操做, 包括讀取擴展屬性, 控制阻塞. 更改終端行爲等等. (out of band management: 指使用分離的渠道進行設備管理. 這使系統管理員能在機器關機的時候對服務器, 網絡進行監視和管理. 出處: http://en.wikipedia.org/wiki/Out-of-band_management )

關於如何在平臺上使用這些函數, 請查閱對應的 Unix man 手冊.

該模塊同時提供了 Unix 文件鎖定機制的接口. Example 12-1 展現瞭如何使用 flock 函數, 更新文件時爲文件設置一個 advisory lock .

輸出結果是由同時運行 3 個副本獲得的. 像這樣(都在一句命令行裏):

python fcntl-example-1.py& python fcntl-example-1.py& python fcntl-example-1.py&

若是你註釋掉對 flock 的調用, 那麼 counter 文件不會正確地更新.

12.2.0.1. Example 12-1. Using the fcntl Module

File: fcntl-example-1.py

import fcntl, FCNTL
import os, time

FILE = "counter.txt"

if not os.path.exists(FILE):
    # create the counter file if it doesn't exist
	# 建立 counter 文件
    file = open(FILE, "w")
    file.write("0")
    file.close()

for i in range(20):
    # increment the counter
    file = open(FILE, "r+")
    fcntl.flock(file.fileno(), FCNTL.LOCK_EX)
    counter = int(file.readline()) + 1
    file.seek(0)
    file.write(str(counter))
    file.close() # unlocks the file
    print os.getpid(), "=>", counter
    time.sleep(0.1)

30940 => 1
30942 => 2
30941 => 3
30940 => 4
30941 => 5
30942 => 6

12.3. pwd 模塊

(只用於 Unix) pwd 提供了一個到 Unix 密碼/password "數據庫"( /etc/passwd 以及相關文件 )的接口. 這個數據庫(通常是一個純文本文件)包含本地機器用戶帳戶的信息. 如 Example 12-2 所示.

12.3.0.1. Example 12-2. 使用 pwd 模塊

File: pwd-example-1.py

import pwd
import os

print pwd.getpwuid(os.getgid())
print pwd.getpwnam("root")

('effbot', 'dsWjk8', 4711, 4711, 'eff-bot', '/home/effbot', '/bin/bosh')
('root', 'hs2giiw', 0, 0, 'root', '/root', '/bin/bash')

getpwall 函數返回一個包含全部可用用戶數據庫入口的列表. 你可使用它搜索一個用戶.

當須要查詢不少名稱的時候, 你可使用 getpwall 來預加載一個字典, 如 Example 12-3 所示.

12.3.0.2. Example 12-3. 使用 pwd 模塊

File: pwd-example-2.py

import pwd
import os

# preload password dictionary
_pwd = {}
for info in pwd.getpwall():
    _pwd[info[0]] = _pwd[info[2]] = info

def userinfo(uid):
    # name or uid integer
    return _pwd[uid]

print userinfo(os.getuid())
print userinfo("root")

('effbot', 'dsWjk8', 4711, 4711, 'eff-bot', '/home/effbot', '/bin/bosh')
('root', 'hs2giiw', 0, 0, 'root', '/root', '/bin/bash')

12.4. grp 模塊

(只用於 Unix) grp 模塊提供了一個到 Unix 用戶組/group ( /etc/group )數據庫的接口. getgrgid 函數返回給定用戶組 id 的相關數據(參見 Example 12-4 ),getgrnam 返回給定用戶組名稱的相關數據.

12.4.0.1. Example 12-4. 使用 grp 模塊

File: grp-example-1.py

import grp
import os

print grp.getgrgid(os.getgid())
print grp.getgrnam("wheel")

('effbot', '', 4711, ['effbot'])
('wheel', '', 10, ['root', 'effbot', 'gorbot', 'timbot'])

getgrall 函數返回包含全部可用用戶組數據庫入口的列表.

若是須要執行不少用戶組查詢, 你可使用 getgrall 來把當前全部的用戶組複製到一個字典裏, 這能夠節省一些時間. Example 12-5 中的 groupinfo 函數返回一個用戶組 id ( int )或是 一個用戶組名稱( str )的信息.

12.4.0.2. Example 12-5. 使用 grp 模塊緩存用戶組信息

File: grp-example-2.py

import grp
import os

# preload password dictionary
_grp = {}
for info in grp.getgrall():
    _grp[info[0]] = _grp[info[2]] = info

def groupinfo(gid):
    # name or gid integer
    return _grp[gid]

print groupinfo(os.getgid())
print groupinfo("wheel")

('effbot', '', 4711, ['effbot'])
('wheel', '', 10, ['root', 'effbot', 'gorbot', 'timbot'])

12.5. nis 模塊

(ֻ���� Unix , ��ѡ) nis ģ���ṩ�� NIS ( Network Information Services , ������Ϣ���� ,��ҳ) ����Ľӿ�, �� Example 12-6 ��ʾ. �����ڴӿ��õ� NIS ��ݿ��л�����.

12.5.0.1. Example 12-6. ʹ�� nis ģ��

File: nis-example-1.py

import nis
import string

print nis.cat("ypservers")
print string.split(nis.match("bacon", "hosts.byname"))

{'bacon.spam.egg': 'bacon.spam.egg'}
['194.18.155.250', 'bacon.spam.egg', 'bacon', 'spam-010']

12.6. curses 模塊

(ֻ���� Unix ��ѡ) curses ģ���ṩ�˶��ı��ַ��ն˴��ڵĿ���, ��ʹ����һ�ֶ�����ն˵ķ���. �� Example 12-7 ��ʾ.

12.6.0.1. Example 12-7. ʹ�� curses ģ��

File: curses-example-1.py

import curses

text = [
    "a very simple curses demo",
    "",
    "(press any key to exit)"
]

# connect to the screen
# ��ӵ���Ļ
screen = curses.initscr()

# setup keyboard
# ���ü���
curses.noecho() # no keyboard echo
curses.cbreak() # don't wait for newline

# screen size
# ��Ļ�ߴ�
rows, columns = screen.getmaxyx()

# draw a border around the screen
# ��һ��߿�
screen.border()

# display centered text
# ��ʾ����
y = (rows - len(text)) / 2

for line in text:
    screen.addstr(y, (columns-len(line))/2, line)
    y = y + 1

screen.getch()

curses.endwin()

12.7. termios 模塊

(只用於 Unix , 可選) termios 爲 Unix 的終端控制設備提供了一個接口. 它可用於控制終端通信端口的大多方面.

Example 12-8 中, 該模塊臨時關閉了鍵盤迴顯(由第三個標誌域的 ECHO 標誌控制).

12.7.0.1. Example 12-8. 使用 termios 模塊

File: termios-example-1.py

import termios, TERMIOS
import sys

fileno = sys.stdin.fileno()

attr = termios.tcgetattr(fileno)
orig = attr[:]

print "attr =>", attr[:4] # flags

# disable echo flag
attr[3] = attr[3] & ~TERMIOS.ECHO

try:
    termios.tcsetattr(fileno, TERMIOS.TCSADRAIN, attr)
    message = raw_input("enter secret message: ")
    print
finally:
    # restore terminal settings
    termios.tcsetattr(fileno, TERMIOS.TCSADRAIN, orig)

print "secret =>", repr(message)

attr => [1280, 5, 189, 35387]
enter secret message:
secret => 'and now for something completely different'

12.8. tty 模塊

(只用於 Unix) tty 模塊包含一些用於處理 tty 設備的工具函數. Example 12-9 將終端窗口切換爲 "raw" 模式.

12.8.0.1. Example 12-9. 使用 tty 模塊

File: tty-example-1.py

import tty
import os, sys

fileno = sys.stdin.fileno()

tty.setraw(fileno)
print raw_input("raw input: ")

tty.setcbreak(fileno)
print raw_input("cbreak input: ")

os.system("stty sane") # ...

raw input: this is raw input
cbreak input: this is cbreak input

12.9. resource 模塊

(只用於 Unix , 可選) resource 模塊用於查詢或修改當前系統資源限制設置. Example 12-10 展現瞭如何執行查詢操做, Example 12-11 展現瞭如何執行修改操做.

12.9.0.1. Example 12-10. 使用 resource 模塊查詢當前設置

File: resource-example-1.py

import resource

print "usage stats", "=>", resource.getrusage(resource.RUSAGE_SELF)
print "max cpu", "=>", resource.getrlimit(resource.RLIMIT_CPU)
print "max data", "=>", resource.getrlimit(resource.RLIMIT_DATA)
print "max processes", "=>", resource.getrlimit(resource.RLIMIT_NPROC)
print "page size", "=>", resource.getpagesize()

usage stats => (0.03, 0.02, 0, 0, 0, 0, 75, 168, 0, 0, 0, 0, 0, 0, 0, 0)
max cpu => (2147483647, 2147483647)
max data => (2147483647, 2147483647)
max processes => (256, 256)
page size => 4096

12.9.0.2. Example 12-11. 使用 resource 模塊限制資源

File: resource-example-2.py

import resource

resource.setrlimit(resource.RLIMIT_CPU, (0, 1))

# pretend we're busy
for i in range(1000):
    for j in range(1000):
        for k in range(1000):
            pass

CPU time limit exceeded

12.10. syslog 模塊

(只用於 Unix 可選) syslog 模塊用於向系統日誌設備發送信息( syslogd ). 這些信息如何處理依不一樣的系統而定, 一般會被記錄在一個 log 文件中, 例如/var/log/messages , /var/adm/syslog , 或者其餘相似處理. (若是你找不到這個文件, 請聯繫你的系統管理員). Example 12-12 展現了該模塊的使用.

12.10.0.1. Example 12-12. 使用 syslog 模塊

File: syslog-example-1.py

import syslog
import sys

syslog.openlog(sys.argv[0])

syslog.syslog(syslog.LOG_NOTICE, "a log notice")
syslog.syslog(syslog.LOG_NOTICE, "another log notice: %s" % "watch out!")

syslog.closelog()

12.11. msvcrt 模塊

(只用於 Windows/DOS ) msvcrt 模塊用於訪問 Microsoft Visual C/C++ Runtime Library (MSVCRT) 中函數的方法.

Example 12-13 展現了 getch 函數, 它用於從命令行讀取一次按鍵操做.

12.11.0.1. Example 12-13. 使用 msvcrt 模塊得到按鍵值

File: msvcrt-example-1.py

import msvcrt

print "press 'escape' to quit..."

while 1:
    char = msvcrt.getch()
    if char == chr(27):
        break
    print char,
    if char == chr(13):
        print

press 'escape' to quit...
h e l l o

kbhit 函數在按鍵時返回(這樣的捕獲操做不會讓 getch 阻塞), 如 Example 12-14 所示.

12.11.0.2. Example 12-14. 使用 msvcrt 模塊接受鍵盤輸入

File: msvcrt-example-2.py

import msvcrt
import time

print "press SPACE to enter the serial number"

while not msvcrt.kbhit() or msvcrt.getch() != " ":
    # do something else
    print ".",
    time.sleep(0.1)

print

# clear the keyboard buffer
# 清除鍵盤緩衝區
while msvcrt.kbhit():
    msvcrt.getch()

serial = raw_input("enter your serial number: ")

print "serial number is", serial

press SPACE to enter the serial number
. . . . . . . . . . . . . . . . . . . . . . . .
enter your serial number: 10
serial number is 10
譯註: 某翻譯在這裏評註道: 我能在 cmd 下運行. 用別的 IDLE 要否則卡住, 要否則接受不了鍵盤輸入. 緣由未知. 這是由於 IDLE 啓動兩個 python 線程, 使用 socket 發送數據, 得到程序返回的.

locking 函數實現了 Windows 下的跨進程文件鎖定, 如 Example 12-15 所示.

12.11.0.3. Example 12-15. 使用 msvcrt 模塊鎖定文件

File: msvcrt-example-3.py

import msvcrt
import os

LK_UNLCK = 0 # unlock the file region 解鎖區域
LK_LOCK = 1 # lock the file region 鎖定文件區域
LK_NBLCK = 2 # non-blocking lock 非阻塞文件鎖
LK_RLCK = 3 # lock for writing 爲寫入文件提供鎖定
LK_NBRLCK = 4 # non-blocking lock for writing 爲寫入文件提供的非阻塞鎖定

FILE = "counter.txt"

if not os.path.exists(FILE):
    file = open(FILE, "w")
    file.write("0")
    file.close()

for i in range(20):
    file = open(FILE, "r+")
    # look from current position (0) to end of file
    msvcrt.locking(file.fileno(), LK_LOCK, os.path.getsize(FILE))
    counter = int(file.readline()) + 1
    file.seek(0)
    file.write(str(counter))
    file.close() # unlocks the file
    print os.getpid(), "=>", counter
    time.sleep(0.1)

208 => 21
208 => 22
208 => 23
208 => 24
208 => 25
208 => 26

12.12. nt 模塊

(非直接使用模塊, 只用於 Windows ) nt 模塊是 os 模塊在 Windows 平臺下調用的執行模塊. 幾乎沒有任何緣由直接使用這個模塊, 請使用 os 模塊替代.Example 12-16 展現了它的使用.

12.12.0.1. Example 12-16. 使用 nt 模塊

File: nt-example-1.py

import nt

# in real life, use os.listdir and os.stat instead!
for file in nt.listdir("."):
    print file, nt.stat(file)[6]

aifc-example-1.py 314
anydbm-example-1.py 259
array-example-1.py 48

12.13. _winreg 模塊

(只用於 Windows , 2.0 中新增) _winreg 模塊提供了訪問 Windows 註冊表數據庫的一個基本接口. Example 12-17 展現了它的使用.

12.13.0.1. Example 12-17. 使用 _winreg 模塊

File: winreg-example-1.py

import _winreg

explorer = _winreg.OpenKey(
    _winreg.HKEY_CURRENT_USER,
    "Software//Microsoft//Windows/CurrentVersion//Explorer"
    )

# list values owned by this registry key 
# 列出該註冊表鍵下的全部值
try:
    i = 0
    while 1:
      name, value, type= _winreg.EnumValue(explorer, i)
      print repr(name),
      i += 1
except WindowsError:
    print

value, type = _winreg.QueryValueEx(explorer, "Logon User Name")

print
print "user is", repr(value)


'Logon User Name' 'CleanShutdown' 'ShellState' 'Shutdown Setting'
'Reason Setting' 'FaultCount' 'FaultTime' 'IconUnderline'...

user is u'Effbot'

12.14. posix 模塊

(非直接使用模塊, 只用於 Unix/POSIX ) posix 模塊是 os 模塊在 Unix 及其餘 POSIX 系統下使用的實現模塊. 通常只須要經過 os 模塊訪問它便可. 如 Example 12-18 所示.

12.14.0.1. Example 12-18. 使用 posix 模塊

File: posix-example-1.py

import posix

for file in posix.listdir("."):
    print file, posix.stat(file)[6]

aifc-example-1.py 314
anydbm-example-1.py 259
array-example-1.py 48

13. 執行支持模塊

就是其餘模塊中用到的模塊.


13.1. dospath 模塊

dospath 模塊(參見 Example 13-1 )提供了 DOS 平臺下的 os.path 功能. 你可使用它在其餘平臺處理 DOS 路徑.

13.1.0.1. Example 13-1. 使用 dospath 模塊

File: dospath-example-1.py

import dospath 

file = "/my/little/pony"

print "isabs", "=>", dospath.isabs(file)
print "dirname", "=>", dospath.dirname(file)
print "basename", "=>", dospath.basename(file)
print "normpath", "=>", dospath.normpath(file)
print "split", "=>", dospath.split(file)
print "join", "=>", dospath.join(file, "zorba")

isabs => 1
dirname => /my/little
basename => pony
normpath => /my/little/pony
split => ('/my/little', 'pony')
join => /my/little/pony/zorba

注意 Python 的 DOS 支持可使用斜槓和反斜槓做爲目錄分隔符.


13.2. macpath 模塊

macpath 模塊( 參見 Example 13-2 )提供了 Macintosh 平臺下的 os.path 功能. 你也可使用它在其餘平臺處理 Macintosh 路徑.

13.2.0.1. Example 13-2. 使用 macpath 模塊

File: macpath-example-1.py

import macpath

file = "my:little:pony"

print "isabs", "=>", macpath.isabs(file)
print "dirname", "=>", macpath.dirname(file)
print "basename", "=>", macpath.basename(file)
print "normpath", "=>", macpath.normpath(file)
print "split", "=>", macpath.split(file)
print "join", "=>", macpath.join(file, "zorba")

isabs => 1
dirname => my:little
basename => pony
normpath => my:little:pony
split => ('my:little', 'pony')
join => my:little:pony:zorba

13.3. ntpath 模塊

ntpath 模塊( 參見 Example 13-3 )提供了 Windows 平臺下的 os.path 功能. 你也可使用它在其餘平臺處理 Windows 路徑.

13.3.0.1. Example 13-3. 使用 ntpath 模塊

File: ntpath-example-1.py

import ntpath

file = "/my/little/pony"

print "isabs", "=>", ntpath.isabs(file)
print "dirname", "=>", ntpath.dirname(file)
print "basename", "=>", ntpath.basename(file)
print "normpath", "=>", ntpath.normpath(file)
print "split", "=>", ntpath.split(file)
print "join", "=>", ntpath.join(file, "zorba")

isabs => 1
dirname => /my/little
basename => pony

normpath => /my/little/pony
split => ('/my/little', 'pony')
join => /my/little/pony/zorba

注意該模塊能夠同時使用斜槓和反斜槓做爲目錄分隔符.


13.4. posixpath 模塊

posixpath 模塊( 參見 Example 13-4 )提供了 Unix 和其餘 POSIX 兼容平臺下的 os.path 功能. 你也可使用它在其餘平臺處理 POSIX 路徑. 另外, 它也能夠處理 URL .

13.4.0.1. Example 13-4. 使用 posixpath 模塊

File: posixpath-example-1.py

import posixpath

file = "/my/little/pony"

print "isabs", "=>", posixpath.isabs(file)
print "dirname", "=>", posixpath.dirname(file)
print "basename", "=>", posixpath.basename(file)
print "normpath", "=>", posixpath.normpath(file)
print "split", "=>", posixpath.split(file)
print "join", "=>", posixpath.join(file, "zorba")

isabs => 1
dirname => /my/little
basename => pony
normpath => /my/little/pony
split => ('/my/little', 'pony')
join => /my/little/pony/zorba

13.5. strop 模塊

(已廢棄) strop 爲 string 模塊中的大多函數提供了底層 C 語言實現. string 模塊會自動調用它, 因此通常你不須要直接使用它.

不過在導入 Python 模塊以前處理路徑的時候你可能會用到它. 如 Example 13-5 所示.

13.5.0.1. Example 13-5. 使用 strop 模塊

File: strop-example-1.py

import strop
import sys

# assuming we have an executable named ".../executable", add a
# directory named ".../executable-extra" to the path

if strop.lower(sys.executable)[-4:] == ".exe":
    extra = sys.executable[:-4] # windows
else:
    extra = sys.executable

sys.path.insert(0, extra + "-extra")

import mymodule

在 Python 2.0 及之後版本中, 你應該使用字符串方法代替 strop , 例如在上邊的代碼中. 使用 "sys.executable.lower()" 替換 "strop.lower(sys.executable)" .


13.6. imp 模塊

imp 模塊包含的函數能夠用於實現自定義的 import 行爲. Example 13-6 重載了 import 語句, 實現了對模塊來源的記錄功能.

13.6.0.1. Example 13-6. 使用 imp 模塊

File: imp-example-1.py

import imp
import sys

def my_import(name, globals=None, locals=None, fromlist=None):
    try:
        module = sys.modules[name] # already imported?
    except KeyError:
        file, pathname, description = imp.find_module(name)
        print "import", name, "from", pathname, description
        module = imp.load_module(name, file, pathname, description)
    return module

import _ _builtin_ _
_ _builtin_ _._ _import_ _ = my_import

import xmllib

import xmllib from /python/lib/xmllib.py ('.py', 'r', 1)
import re from /python/lib/re.py ('.py', 'r', 1)

import sre from /python/lib/sre.py ('.py', 'r', 1)
import sre_compile from /python/lib/sre_compile.py ('.py', 'r', 1)
import _sre from /python/_sre.pyd ('.pyd', 'rb', 3)

注意這裏的導入功能不支持包. 具體實現請參閱 knee 模塊的源代碼.


13.7. new 模塊

new 模塊是一個底層的模塊, 你可使用它來建立不一樣的內建對象, 例如類對象, 函數對象, 以及其餘由 Python 運行時系統建立的類型. Example 13-7 展現了該模塊的使用.

若是你使用的是 1.5.2 版本 , 那麼你有可能須要從新編譯 Python 來使用這個模塊, 在默認狀況下並非全部平臺都有這個模塊. 在 2.0 及之後版本中, 不須要這麼作.

13.7.0.1. Example 13-7. 使用 new 模塊

File: new-example-1.py

import new

class Sample:

    a = "default"

    def _ _init_ _(self):
        self.a = "initialised"

    def _ _repr_ _(self):
        return self.a

#
# create instances

a = Sample()
print "normal", "=>", a

b = new.instance(Sample, {})
print "new.instance", "=>", b

b._ _init_ _()
print "after _ _init_ _", "=>", b

c = new.instance(Sample, {"a": "assigned"})
print "new.instance w. dictionary", "=>", c

normal => initialised
new.instance => default
after _ _init_ _ => initialised
new.instance w. dictionary => assigned

13.8. pre 模塊

(已廢棄) pre 模塊是 1.5.2 中 re 模塊調用的實現功能模塊. 在當前版本中已廢棄. Example 13-8 展現了它的使用.

13.8.0.1. Example 13-8. 使用 pre 模塊

File: pre-example-1.py

import pre

p = pre.compile("[Python]+")

print p.findall("Python is not that bad")

['Python', 'not', 'th', 't']

13.9. sre 模塊

(功能實現模塊, 已聲明不支持) sre 模塊是 re 模塊的底層實現. 通常不必直接使用它, 並且之後版本將不會支持它. Example 13-9 展現了它的使用.

13.9.0.1. Example 13-9. 使用 sre 模塊

File: sre-example-1.py

import sre

text = "The Bookshop Sketch"

# a single character
m = sre.match(".", text)
if m: print repr("."), "=>", repr(m.group(0))

# and so on, for all 're' examples...

'.' => 'T'

13.10. py_compile 模塊

py_compile 模塊用於將 Python 模塊編譯爲字節代碼. 它和 Python 的 import 語句行爲相似, 不過它接受文件名而不是模塊名做爲參數. 使用方法如 Example 13-10 所示.

13.10.0.1. Example 13-10. 使用 py_compile 模塊

File: py-compile-example-1.py

import py_compile

# explicitly compile this module
py_compile.compile("py-compile-example-1.py")

compileall 模塊能夠把一個目錄樹下的全部 Python 文件編譯爲字節代碼.


13.11. compileall 模塊

compileall 模塊用於將給定目錄下(以及 Python path )的全部 Python 腳本編譯爲字節代碼. 它也能夠做爲可執行腳本使用(在 Unix 系統下, Python 安裝時會自動調用執行它). 用法參見 Example 13-11 .

13.11.0.1. Example 13-11. 使用 compileall 模塊編譯目錄中的全部腳本

File: compileall-example-1.py

import compileall

print "This may take a while!"

compileall.compile_dir(".", force=1)

This may take a while!
Listing . ...
Compiling ./SimpleAsyncHTTP.py ...
Compiling ./aifc-example-1.py ...
Compiling ./anydbm-example-1.py ...
...

13.12. ihooks 模塊

ihooks 模塊爲替換導入提供了一個框架. 這容許多個導入機制共存. 使用方法參見 Example 13-12 .

13.12.0.1. Example 13-12. 使用 ihooks 模塊

File: ihooks-example-1.py

import ihooks, imp, os

def import_from(filename):
    "Import module from a named file"

    loader = ihooks.BasicModuleLoader()
    path, file = os.path.split(filename)
    name, ext  = os.path.splitext(file)
    m = loader.find_module_in_dir(name, path)
    if not m:
        raise ImportError, name
    m = loader.load_module(name, m)
    return m

colorsys = import_from("/python/lib/colorsys.py")

print colorsys

<module 'colorsys' from '/python/lib/colorsys.py'>

13.13. linecache 模塊

linecache 模塊用於從模塊源文件中讀取代碼. 它會緩存最近訪問的模塊 (整個源文件). 如 Example 13-13 .

13.13.0.1. Example 13-13. 使用 linecache 模塊

File: linecache-example-1.py

import linecache

print linecache.getline("linecache-example-1.py", 5)

print linecache.getline("linecache-example-1.py", 5)

traceback 模塊使用這個模塊實現了對導入操做的跟蹤.


13.14. macurl2path 模塊

(功能實現模塊) macurl2path 模塊用於 URL 和 Macintosh 文件名 的相互映射. 通常沒有必要直接使用它, 請使用 urllib 中的機制. 它的用法參見 Example 13-14 .

13.14.0.1. Example 13-14. 使用 macurl2path 模塊

File: macurl2path-example-1.py

import macurl2path

file = ":my:little:pony"

print macurl2path.pathname2url(file)
print macurl2path.url2pathname(macurl2path.pathname2url(file))

my/little/pony
:my:little:pony

13.15. nturl2path 模塊

(功能實現模塊) nturl2path 模塊用於 URL 和 Windows 文件名的 相互映射. 用法參見 Example 13-15 .

13.15.0.1. Example 13-15. 使用 nturl2path 模塊

File: nturl2path-example-1.py

import nturl2path

file = r"c:/my/little/pony"

print nturl2path.pathname2url(file)
print nturl2path.url2pathname(nturl2path.pathname2url(file))

///C|/my/little/pony
C:/my/little/pony

一樣地, 請經過 urllib 模塊來訪問這些函數, 如 Example 13-16 所示.

13.15.0.2. Example 13-16. 經過 urllib 調用 nturl2path 模塊

File: nturl2path-example-2.py

import urllib

file = r"c:/my/little/pony"

print urllib.pathname2url(file)
print urllib.url2pathname(urllib.pathname2url(file))

///C|/my/little/pony
C:/my/little/pony

13.16. tokenize 模塊

tokenize 模塊將一段 Python 源文件分割成不一樣的 token . 你能夠在代碼高亮工具中使用它.

在 Example 13-17 中, 咱們分別打印出這些 token .

13.16.0.1. Example 13-17. 使用 tokenize 模塊

File: tokenize-example-1.py

import tokenize

file = open("tokenize-example-1.py")

def handle_token(type, token, (srow, scol), (erow, ecol), line):
    print "%d,%d-%d,%d:/t%s/t%s" % /
        (srow, scol, erow, ecol, tokenize.tok_name[type], repr(token))

tokenize.tokenize(
    file.readline,
    handle_token
    )

1,0-1,6:     NAME    'import'
1,7-1,15:    NAME    'tokenize'
1,15-1,16:   NEWLINE '/012'
2,0-2,1:     NL      '/012'
3,0-3,4:     NAME    'file'
3,5-3,6:     OP      '='
3,7-3,11:    NAME    'open'
3,11-3,12:   OP      '('
3,12-3,35:   STRING  '"tokenize-example-1.py"'
3,35-3,36:   OP      ')'
3,36-3,37:   NEWLINE '/012'
...

注意這裏的 tokenize 函數接受兩個可調用對象做爲參數: 前一個用於獲取新的代碼行, 第二個用於在得到每一個 token 時調用.


13.17. keyword 模塊

keyword 模塊(參見 Example 13-18 )有一個包含當前 Python 版本所使用的關鍵字的列表. 它還提供了一個字典, 以關鍵字做爲 key , 以一個描述性函數做爲 value , 它可用於檢查 給定單詞是不是 Python 關鍵字.

13.17.0.1. Example 13-18. 使用 keyword 模塊

File: keyword-example-1.py

import keyword

name = raw_input("Enter module name: ")

if keyword.iskeyword(name):
    print name, "is a reserved word."
    print "here's a complete list of reserved words:"
    print keyword.kwlist

Enter module name: assert
assert is a reserved word.
here's a complete list of reserved words:
['and', 'assert', 'break', 'class', 'continue', 'def', 'del',
'elif', 'else', 'except', 'exec', 'finally', 'for', 'from',
'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or',
'pass', 'print', 'raise', 'return', 'try', 'while']

13.18. parser 模塊

(可選) parser 模塊提供了一個到 Python 內建語法分析器和編譯器的接口.

Example 13-19 將一個簡單的表達式編譯爲一個抽象語法樹( abstract syntax tree , AST ), 而後將 AST 轉換爲一個嵌套列表, 轉儲樹 ( 其中每一個節點包含一個語法符號或者是一個 token )中的內容, 將全部數字加上 1 , 最後將列表轉回一個代碼對象. 至少我認爲它是這麼作的.

13.18.0.1. Example 13-19. 使用 parser 模塊

File: parser-example-1.py

import parser
import symbol, token

def dump_and_modify(node):
    name = symbol.sym_name.get(node[0])
    if name is None:
        name = token.tok_name.get(node[0])
    print name,
    for i in range(1, len(node)):
        item = node[i]
        if type(item) is type([]):
            dump_and_modify(item)
        else:
            print repr(item)
            if name == "NUMBER":
                # increment all numbers!
                node[i] = repr(int(item)+1)

ast = parser.expr("1 + 3")

list = ast.tolist()

dump_and_modify(list)

ast = parser.sequence2ast(list)

print eval(parser.compileast(ast))

eval_input testlist test and_test not_test comparison
expr xor_expr and_expr shift_expr arith_expr term factor
power atom NUMBER '1'
PLUS '+'
term factor power atom NUMBER '3'
NEWLINE ''
ENDMARKER ''
6

13.19. symbol 模塊

symbol 模塊包含 Python 語法中的非終止符號. 可能只有你涉及 parser 模塊的時候用到它. 用法參見 Example 13-20 .

13.19.0.1. Example 13-20. 使用 symbol 模塊

File: symbol-example-1.py

import symbol

print "print", symbol.print_stmt
print "return", symbol.return_stmt

print 268
return 274

13.20. token 模塊

token 模塊包含標準 Python tokenizer 所使用的 token 標記. 如 Example 13-21 所示.

13.20.0.1. Example 13-21. 使用 token 模塊

File: token-example-1.py

import token

print "NUMBER", token.NUMBER
print "PLUS", token.STAR
print "STRING", token.STRING

NUMBER 2
PLUS 16
STRING 3

14. 其餘模塊


14.1. 概覽

本章描述的是一些並不怎麼常見的模塊. 一些是很實用的, 另些是已經廢棄的模塊.


14.2. pyclbr 模塊

pyclbr 模塊包含一個基本的 Python 類解析器, 如 Example 14-1 所示.

版本 1.5.2 中, 改模塊只包含一個 readmodule 函數, 解析給定模塊, 返回一個模塊全部頂層類組成的列表.

14.2.0.1. Example 14-1. 使用 pyclbr 模塊

File: pyclbr-example-1.py

import pyclbr

mod = pyclbr.readmodule("cgi")

for k, v in mod.items():
    print k, v

MiniFieldStorage <pyclbr.Class instance at 7873b0>
InterpFormContentDict <pyclbr.Class instance at 79bd00>
FieldStorage <pyclbr.Class instance at 790e20>
SvFormContentDict <pyclbr.Class instance at 79b5e0>
StringIO <pyclbr.Class instance at 77dd90>
FormContent <pyclbr.Class instance at 79bd60>
FormContentDict <pyclbr.Class instance at 79a9c0>

2.0 及之後版本中, 添加了另個接口 readmodule_ex , 它還會讀取全局函數. 如 Example 14-2 所示.

14.2.0.2. Example 14-2. 使用 pyclbr 模塊讀取類和函數

File: pyclbr-example-3.py

import pyclbr

# 2.0 and later
mod = pyclbr.readmodule_ex("cgi")

for k, v in mod.items():
    print k, 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. 後記

相關文章
相關標籤/搜索