<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 23,comments - 66,trackbacks - 0
    Python入門

    時間:2004/03/28    來源:不詳

      第一章 介紹

      腳本語言是類似DOS批處理、UNIX shell程序的語言。腳本語言不需要每次編譯再執行,并且在執行中可以很容易地訪問正在運行的程序,甚至可以動態地修改正在運行的程序,適用于快速地開發以及完成一些簡單的任務。在使用腳本語言時常常需要增的新的功能,但有時因為腳本語言本來就已經很慢、很大、很復雜了而不能實現;或者,所需的功能涉及只能用C語言提供的系統調用或其他函數——通常所要解決的問題沒有重要到必須用C語言重寫的程度;或者,解決問題需要諸如可變長度字符串等數據類型(如文件名的有序列表),這樣的數據類型在腳本語言中十分容易而C語言則需要很多工作才能實現;或者,編程者不熟悉C語言:這些情況下還是可以使用腳本語言的。在這樣的情況下,Python可能正好適合你的需要。Python使用簡單,但它是一個真正的程序語言,而且比shell提供了更多結構和對大型程序的支持。另一方面,它比C提供更多的錯誤檢查,它是一個非常高級的語言,內置了各種高級數據結構,如靈活的數組和字典,這些數據結構要用C高效實現的話可能要花費你幾天的時間。由于Python具有更一般的數據結構,它比Awk甚至Perl適用的范圍都廣,而許多東西在Python內至少和在這些語言內一樣容易。
     
      Python允許你把程序分解為模塊,模塊可以在其他Python程序中重用。它帶有一大批標準模塊可以作為你自己的程序的基礎——或作為學習Python編程的例子。系統還提供了關于文件輸入輸出、系統調用、插座(sockets)的東西,甚至提供了窗口系統(STDWIN)的通用接口。
     
      Python是一個解釋性語言,因為不需要編譯和連接所以能節省大量的程序開發時間。解釋程序可以交互使用,這樣可以可以很容易地試驗語言的各種特色,寫只用一次的程序,或在從底向上程序開發中測試函數。它也是一個方便的計算器。
     
      Python允許你寫出非常嚴謹而且可讀的程序。用Python寫的程序通常都比相應的C程序要短,因為如下幾個理由:
     
      高級的數據結構允許你用一個語句表達復雜的操作; 復合語句是靠縮進而不是用表示開始和結束的括號; 不需要變量聲明或參量聲明。

      Python是可擴充的:如果你會用C語言編程就很容易為解釋程序增加新的內置函數或模塊,這樣可以以最快速度執行關鍵操作,或把Python程序和只能以二進制碼提供的庫(如不同廠商提供的圖形庫)連接起來。當你變得確實很在行時你可以把Python解釋器與用C寫的應用相連接,把它作為該應用的擴展或命令語言。

      Python的命名是由BBC的“Monty Python's Flying Circus”節目而得,與蟒蛇沒有什么關系。

      第二章 解釋程序的使用

      在命令行鍵入
       python
      或在Windows環境下雙擊相應的圖標可以進入Python的解釋程序。如果要運行儲存在文件中的Python程序,可以用
       python 文件名
      的形式。 

      進入解釋程序的環境后,解釋程序稱為處于交互狀態。在這種狀態下系統用 主提示提示輸入下一個命令,這一般是三個大于號(>>>),如果需要續行系統用 次提示提示輸入,缺省為三個小數點(...)。在主提示下鍵入文件尾符號(在UNIX中為Control-D,在DOS或Windows中為Control-Z)可以正常退出解釋程序。

      Python解釋程序的有些版本支持命令行編輯和命令歷史,使用用Emacs或vi的鍵組合。 

      第三章 基本使用

      下面我們用例子來介紹Python的基本用法。在例子中,用戶輸入和系統輸出靠有沒有提示(>>>和...)來分別。如果要試這些例子的話,需要鍵入提示后的所有命令,例子中沒有提示的行是系統的輸出。注意只有次提示的行意味著需要鍵入一個空行,這用于結束多行命令。
     
      3.1 用Python作計算器使用
      啟動解釋程序,等待主提示>>>出現。解釋程序可以作為計算器使用。鍵入一個表達式,解釋程序就可以輸出結果。表達式的寫法很直觀:+,-,*,/, %, **等算符的作用和其它大多數語言(如Pascal或C)沒什么差別,括號可以用來組合。例如:
     
      >>> 2+2
      4

      >>> # 這是一個注釋
      ... 2+2
      4
      >>> 2+2 # 和代碼在同一行的注釋
      4
      >>> (50-5*6)/4
      5
      >>> # 整數除法得下面的整數
      ... 7/3
      2
      >>> 7/-3
      -3
      >>>
      和C中一樣,等于號用來給變量賦值,賦值的結果不顯示:
      >>> width = 20
      >>> height = 5*9
      >>> width * height
      900
      >>>
      可以同時給幾個變量賦同一個值:
      >>> x = y = z = 0 # 把 x, y 和 z賦零

      >>> x
      0
      >>> y
      0
      >>> z
      0
      >>>
      Python完全支持浮點數,混合類型的運算會把整數先轉換成浮點數:
      >>> 4 * 2.5 / 3.3
      3.0303030303
      >>> 7.0 / 2
      3.5
      >>>
      Python也提供了復數,方法是用j和J作為虛數單位,如1+1j,3.14e-10j,等等。
      3.2. 字符串
      Python除處理數字外還可以處理字符串,字符串用單撇號或雙撇號包裹:
      >>> 'spam eggs'
      'spam eggs'
      >>> 'doesn\'t'
      "doesn't"
      >>> "doesn't"
      "doesn't"
      >>> '"Yes," he said.'
      '"Yes," he said.'
      >>> "\"Yes,\" he said."
      '"Yes," he said.'
      >>> '"Isn\'t," she said.'
      '"Isn\'t," she said.'
      >>>

      字符串輸出格式與輸入的樣子相同,都是用撇號包裹,撇號和其它特殊字符用用反斜杠轉義。如果字符串中有單撇號而沒有雙撇號則用雙撇號包裹,否則應該用單撇號包裹。后面要介紹的print語句可以不帶撇號或轉義輸出字符串。
      字符串可以用+號連接起來,用*號重復:
      >>> word = 'Help' + 'A'
      >>> word
      'HelpA'
      >>> '<' + word*5 + '>'
      '<HelpAHelpAHelpAHelpAHelpA>'
      >>>

      字符串可以象在C中那樣用下標索引,字符串的第一個字符下標為0。
      Python沒有單獨的字符數據類型,一個字符就是長度為一的字符串。象在Icon語言中那樣,可以用片段(slice)記號來指定子串,片段即用冒號隔開的兩個下標。

      >>> word[4]
      'A'
      >>> word[0:2]
      'He'
      >>> word[2:4]
      'lp'
      >>>

      片段有很好的缺省值:第一下標省略時缺省為零,第二下標省略時缺省為字符串的長度。

      >>> word[:2]  # 前兩個字符
      'He'
      >>> word[2:]  # 除前兩個字符串外的部分
      'lpA'
      >>>
      注意s[:i] + s[i:] 等于 s 是片段運算的一個有用的恒等式。
      >>> word[:2] + word[2:]
      'HelpA'
      >>> word[:3] + word[3:]
      'HelpA'
      >>>

      不合理的片段下標可以很好地得到解釋:過大的下標被換成字符串長度,上界小于下界時返回空串。

      >>> word[1:100]
      'elpA'
      >>> word[10:]
      ''
      >>> word[2:1]
      ''
      >>>

      下標允許為負數,這時從右向左數。例如:

      >>> word[-1]   # 最后一個字符
      'A'
      >>> word[-2]   # 倒數第二個字符
      'p'
      >>> word[-2:]  # 最后兩個字符
      'pA'
      >>> word[:-2]  # 除最后兩個字符外的部分
      'Hel'
      >>>

      但要注意的是 -0 實際還是 0,所以它不會從右向左數!

      >>> word[-0]   # (因為 -0 等于 0)
      'H'
      >>>

      超出范圍的片段下標被截斷,但在非片段的情況下不要這樣:
      >>> word[-100:]
      'HelpA'
      >>> word[-10]  # 錯誤
      Traceback (innermost last):
      File "<stdin>", line 1
      IndexError: string index out of range
      >>>

      記住片段意義的最好方法是把下標看成是字符 之間的點,第一個字符的左邊界號碼為0。有n個字符的字符串的最后一個字符的右邊界下標為n,例如:
     
      +---+---+---+---+---+
      | H | e | l | p | A |
      +---+---+---+---+---+
      0  1  2  3  4  5
      -5 -4 -3 -2 -1

      第一行數字給出字符串中下標0到5的位置,第二行給出相應的負下標。從i到j的片段由在邊界i和j之間的字符組成。
     
      對于非負下標,如果下標都在界內,則片段的長度為下標的差。例如,word[1:3] 的長度為 2。
      內置函數len()返回字符串的長度:
      >>> s = 'supercalifragilisticexpialidocious'
      >>> len(s)
      34
      >>>
      多行的長字符串也可以用行尾反斜杠續行,續行的行首空白不被忽略,如
      hello = "This is a rather long string containing\n\
      several lines of text just as you would do in C.\n\
      Note that whitespace at the beginning of the line is\
      significant.\n"
      print hello
      結果為
     
        This is a rather long string containing
        several lines of text just as you would do in C.
          Note that whitespace at the beginning of the line is significant.

    對于特別長的字符串(比如包含說明的幾段文字),如果用上面的方式每行都用\n\結尾是很麻煩的,特別是這樣無法用象Emacs這樣的功能強大的編輯器重新編排。對這種情況,可以使用三重撇號,例如
        hello = """
     
          This string is bounded by triple double quotes (3 times ").
        Unescaped newlines in the string are retained, though \
        it is still possible\nto use all normal escape sequences.
     
          Whitespace at the beginning of a line is
        significant. If you need to include three opening quotes
        you have to escape at least one of them, e.g. \""".
     
          This string ends in a newline.
        """
      三重撇號字符串也可以用三個單撇號,沒有任何語義差別。
      多行的字符串常量可以直接連接起來,字符串常量之間用空格分隔則在編譯時可以自動連接起來,這樣可以把一個長字符串連接起來而不需要犧牲縮進對齊或性能,不象用加號連接需要運算,也不象字符串串內的換行其行首空格需要保持。

      3.3 列表
      Python中有幾種復合數據類型,用來把其它值組合到一起。其中最靈活的是列表,可以寫成在方括號之間用逗號隔開的若干值(項)。列表的項不必取同一類型。

      >>> a = ['spam', 'eggs', 100, 1234]
      >>> a
      ['spam', 'eggs', 100, 1234]
      >>>
      象字符串下標那樣,列表下標從0開始,列表可以取片段,可以連接,等等:
      >>> a[0]
      'spam'
      >>> a[3]
      1234
      >>> a[-2]
      100
      >>> a[1:-1]
      ['eggs', 100]
      >>> a[:2] + ['bacon', 2*2]
      ['spam', 'eggs', 'bacon', 4]
      >>> 3*a[:3] + ['Boe!']
      ['spam', 'eggs', 100, 'spam', 'eggs', 100, 'spam', 'eggs', 100, 'Boe!']
      >>>
      與字符串不同的是列表是可變的,可以修改列表的每個元素:
      >>> a
      ['spam', 'eggs', 100, 1234]
      >>> a[2] = a[2] + 23
      >>> a
      ['spam', 'eggs', 123, 1234]
      >>>
      也可以給一個片段重新賦值,這甚至可以改變表的大小:
      >>> # 替換若干項:
      ... a[0:2] = [1, 12]
      >>> a
      [1, 12, 123, 1234]
      >>> # 去掉若干項:
      ... a[0:2] = []
      >>> a
      [123, 1234]
      >>> # 插入若干項:
      ... a[1:1] = ['bletch', 'xyzzy']
      >>> a
      [123, 'bletch', 'xyzzy', 1234]
      >>> a[:0] = a   # 在開頭插入自身
      >>> a
      [123, 'bletch', 'xyzzy', 1234, 123, 'bletch', 'xyzzy', 1234]
      >>>
      內置函數也使用于列表:
      >>> len(a)
      8
      >>>
      可以建立嵌套列表(表的元素也是列表),如:
      >>> q = [2, 3]
      >>> p = [1, q, 4]
      >>> len(p)
      3
      >>> p[1]
      [2, 3]
      >>> p[1][0]
      2
      >>> p[1].append('xtra')   # 列表方法
      >>> p
      [1, [2, 3, 'xtra'], 4]
      >>> q
      [2, 3, 'xtra']
      >>>
      注意這個例子中p[1]和q實際是同一個對象!也就是說它們只不過是同一個東西的兩個名字(引用)而已。

      3.4 編程初步
      Python當然不是只能用來把兩個數加到一起,它可以完成很復雜的工作。例如,我們可以寫出Fibonacci序列的開始幾個:

      >>> # Fibonacci 序列:
      ... # 兩個元素的和定義下一個
      ... a, b = 0, 1
      >>> while b < 10:
      ...    print b
      ...    a, b = b, a+b
      ...
      1
      1
      2
      3
      5
      8
      >>>

      這個例子介紹了幾個新特色。
      第一行包含一個多重賦值: 變量a和b同時得到新值0和1。在最后一行又用了多重賦值,我們可以看出賦值時先把右邊都算出后再進行賦值。
      while循環當循環條件(這里即: b < 10)成立時不斷執行。在Python中和C中一樣,非零整數值為真值,零為假值。條件也可以是字符串或列表或任何序列,長度為非零的為真,空序列為假。例子中所用的是一個簡單比較。標準的比較算符和C一樣:
      <, >, ==, <=, >= 和 !=。
      循環體是縮進的:縮進是Python用來組合語句的方式。Python目前還不能提供智能自動縮進,所以你需要自己為每個縮進行鍵入制表符或空格。實際使用中你可以用文本編輯程序為Python 準備復雜的輸入,多數文本編輯程序都有自動縮進的功能。在交互輸入復合語句時必修附加一個空行以指示復合語句的完成(因為解釋程序無法猜到哪是語句的最后一行)。print語句顯示后面的表達式值。這和直接寫出表達式不同,它可以顯示多個表達式和字符串,而且可以用于程序文件中。顯示時字符串沒有撇號,各項目之間插入一個空格,所以你可以以精美的格式顯示,如:

      >>> i = 256*256
      >>> print 'The value of i is', i
      The value of i is 65536
      >>>
      在尾部寫一個逗號可以避免最后換行:
      >>> a, b = 0, 1
      >>> while b < 1000:
      ...   print b,
      ...   a, b = b, a+b
      ...
      1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
      >>>

      注意如果前一行沒有結束的話系統在顯示提示之前先換行。
      Python還提供了和C語言一樣的printf格式的輸出方式,這是用%實現的,左邊是格式,如:
     
        >>> print 'The value of 1/7 is approximately %5.3f.' % 0.142857
        The value of 1/7 is approximately 0.143.
        >>>

      如果有多個需要輸出的項百分號右邊的項可以是一個序組,如
        >>> print "Name: %-10s Age: %3d" % ("White", 31)
        Name: White   Age: 31
     

      第四章 流程控制

      前面我們已經見到了如何由用while結構控制流程運行。這一章我們介紹更多的控制結構。Python具有和其它語言類似的控制結構但略有差別。
      4.1 If 語句
      If 語句可能是最基本的程序分支語句了。例如:
      >>> if x < 0:
      ...   x = 0
      ...   print 'Negative changed to zero'
      ... elif x == 0:
      ...   print 'Zero'
      ... elif x == 1:
      ...   print 'Single'
      ... else:
      ...   print 'More'
      ...
      可以有零到多個elif部分,else部分可選。關鍵字elif是else if的縮寫,這樣可以縮短語句行長度。其它語言中switch 或 case 語句可以用if...elif...elif...語句組來實現。
      4.2 for 語句
      Python中的for語句與你可能熟悉的C或者Pascal中的相應語句略有不同。不象Pascal 那樣總是對數字序列進行循環,也不是象C中那樣完全由程序員自由地控制循環條件和循環體,Python的for循環是對任意種類的序列(如列表或字符串)按出現次序遍歷每一項。
      例如:
      >>> # 計算字符串長:
      ... a = ['cat', 'window', 'defenestrate']
      >>> for x in a:
      ...   print x, len(x)
      ...
      cat 3
      window 6
      defenestrate 12
      >>>
      盡量不要在循環體內修改用來控制循環的序列(當然,只有可變的序列類型如列表才有可能被修改),這樣程序可能會出問題。如果需要這樣,比如說要復制某些項,可以用序列的副本來控制循環。片段記號讓你很容易生成副本:
      >>> for x in a[:]: # 生成整個列表的片段副本
      ...  if len(x) > 6: a.insert(0, x)
      ...
      >>> a
      ['defenestrate', 'cat', 'window', 'defenestrate']
      >>>
      結果是把列表中長度超過6個字符的字符串插入到列表開頭。
      4.3 range() 函數
      如果確實需要對一列數字進行循環的話,可以使用內置函數range()。它生成包含數字序列的列表,如:
      >>> range(10)
      [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
      >>>
      注意給出的終點永遠不出現在生成的列表中,range(10)生成一個十個數的列表,恰好是長度為10的序列的合法下標的各個值。也可以指定不同的起始點,或者指定不同的間隔(甚至負數):
      >>> range(5, 10)
      [5, 6, 7, 8, 9]
      >>> range(0, 10, 3)
      [0, 3, 6, 9]
      >>> range(-10, -100, -30)
      [-10, -40, -70]
      >>>
      為了對序列的下標進行循環,如下聯合使用range() 和 len():
      >>> a = ['Mary', 'had', 'a', 'little', 'lamb']
      >>> for i in range(len(a)):
      ...   print i, a[i]
      ...
      0 Mary
      1 had
      2 a
      3 little
      4 lamb
      >>>
     
      4.4 break語句,continue語句和循環中的else子句
      如同C語言一樣,break語句跳出其所處的最內層的for 或 while循環,continue語句繼續下一循環步。
      循環語句還可以帶一個 else 子句,當循環正常結束時執行其內容,但如果循環是用break 語句跳出的則不執行其內容。下例說明了這種用法,此例求素數:
      >>> for n in range(2, 10):
      ...   for x in range(2, n):
      ...     if n % x == 0:
      ...      print n, 'equals', x, '*', n/x
      ...      break
      ...   else:
      ...     print n, 'is a prime number'
      ...
      2 is a prime number
      3 is a prime number
      4 equals 2 * 2
      5 is a prime number
      6 equals 2 * 3
      7 is a prime number
      8 equals 2 * 4
      9 equals 3 * 3
      >>>
     
      4.5 pass 語句
      pass語句不執行任何操作,當語法要求一個語句而程序不需要執行操作時就用此語句。
      例如:
      >>> while 1:
      ...    pass # 等待鍵盤中斷
      ...

      4.6 函數定義
      我們可以定義一個函數用來計算某一界限以下的所有Fibonacci序列值:
      >>> def fib(n):  # 寫出 n 以下的所有Fibonacci序列值
      ...   a, b = 0, 1
      ...   while b < n:
      ...      print b,
      ...      a, b = b, a+b
      ...
      >>> # 調用剛剛定義的函數:
      ... fib(2000)

      1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
      >>>
      其中關鍵字 def 開始一個函數定義,其后應該是函數名,括號內的形參表,以冒號結束。構成函數體的各語句從下一行開始,用一個制表符縮進。函數的第一個語句可以是一個字符串,如果是的話,這個字符串就是函數的文檔字符串,簡稱為docstring。有一些工具可以利用文檔字符串自動生成可打印的文檔,或者讓用戶交互地瀏覽代碼,所以在自己編程時加入文檔字符串是一個好習慣,應該養成這樣的習慣。
      函數在執行時對局部變量引入一個新的符號表。函數中的變量賦值都存入局部符號表;引用變量時變量名先從局部符號表中查找,然后在全局符號表中查找,最后從內置的名字中查找。因此,在函數中不能直接對全局變量賦值(除非用了global語句來說明),但可以引用全局變量的值。
      函數調用的實參被引入函數的局部符號表,即函數的參數是按值調用的。函數再調用其它函數時為該函數生成一個新的符號表。但是嚴格地說,函數的調用是按引用調用的,因為如果參數是一個可變類型如列表的話在函數中改變形參的內容將導致實參的內容被改變(不改變的是實參名字的綁定關系)。
      函數定義把函數名放入當前符號表。函數名的值類型為用戶自定義函數,這個值可以賦給另一個名字,從而這個名字也代表相同的函數。這可以作為一般的改名方法:
     
      >>> fib
      <function object at 10042ed0>
      >>> f = fib
      >>> f(100)
      1 1 2 3 5 8 13 21 34 55 89
      >>>

      你可能會說 fib 不是函數而是過程。Python和C一樣,過程只是不返回值的函數。實際上,嚴格地說,過程也返回一個值,只不過是一個很沒意思的值。這個值叫做 None(這是一個內置的名字)。解釋程序交互運行時如果只需要顯示這個值的話就會忽略不顯示。如果希望顯示的話可以用 print 語句:
     
      >>> print fib(0)
      None
      >>>

      也可以寫一個函數返回Fibonacci 序列的數值列表而不是顯示這些值:

      >>> def fib2(n): # 返回直到n的Fibonacci 序列值
      ...   result = []
      ...   a, b = 0, 1
      ...   while b < n:
      ...      result.append(b)  # 解釋見下面
      ...      a, b = b, a+b
      ...   return result
      ...
      >>> f100 = fib2(100)  # 調用
      >>> f100        # 輸出結果
      [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

      >>>
     
      這個例子也演示了新的Python特色:return語句從函數中退出并返回一個值。不帶返回值的return可以從過程中間退出,運行到過程的末尾也可以退出,這兩種情況下返回None。
      語句result.append(b)調用列表對象result的一個方法。方法是“屬于”一個對象的函數,引用格式為obj.methodname,其中obj是某個對象(也允許是一個表達式), methodname 是由該對象的類型定義的一個方法的名字。不同的不同的方法。不同類型的方法可以使用相同的名字而不致引起誤解。(可以定義自己的對象類型和方法,使用類,本文后面會討論這個話題)。例子中的append()方法時列表對象的方法,它在列表末尾增加一個新元素。在本例中這等價于“result = result + ”,只是更有效。
      4.7 函數參數
      可以定義使用可變個數參數的函數。這樣的定義方法有三種,可以聯合使用。
      4.7.1 參數缺省值
      可以為一個參數或幾個參數指定缺省值。這樣定義的函數在調用時實參個數可以比定義時少。例如:

    def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
      while 1:
        ok = raw_input(prompt)
        if ok in ('y', 'ye', 'yes'): return 1
        if ok in ('n', 'no', 'nop', 'nope'): return 0
        retries = retries - 1
        if retries < 0: raise IOError, 'refusenik user'
        print complaint
      這個函數在調用時既可以這樣調用:ask_ok('Do you really want to quit?'),或者可以這樣調用:ask_ok('OK to overwrite the file?', 2)。缺省值是在函數定義時的定義作用域中計算的,所以例如:
      i = 5
      def f(arg = i): print arg
      i = 6
      f()
      將顯示5。
      注意:缺省值只計算一次。當缺省值是可變對象如列表或字典時這一點是要注意的。例如,以下函數會在以后的調用中累加它的值:
      def f(a, l = []):
        l.append(a)
        return l
      print f(1)
      print f(2)
      print f(3)
      This will print

      [1]
      [1, 2]
      [1, 2, 3]
      如果你不希望缺省值在連續的調用中被保留,可以象下面這樣改寫函數:
      def f(a, l = None):
        if l is None:
           l = []
        l.append(a)
        return l
      4.7.2 關鍵字參數
      函數調用時也可以象“關鍵字 = 值”這樣指定實參,其中關鍵字是定義時使用的形參的名字。例如:

      def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
        print "-- This parrot wouldn't", action,
        print "if you put", voltage, "Volts through it."
        print "-- Lovely plumage, the", type
        print "-- It's", state, "!"
      可以用如下幾種方式調用:
      parrot(1000)   # 缺省值
      parrot(action = 'VOOOOOM', voltage = 1000000) # 關鍵字,缺省值,次序可變
      parrot('a thousand', state = 'pushing up the daisies') # 位置參數,缺省值,
      關鍵字
      parrot('a million', 'bereft of life', 'jump') # 位置參數,缺省值
      但以下幾種調用方式是錯誤的:
     
      parrot()           # 非缺省的參數沒有提供
      parrot(voltage=5.0, 'dead') # 關鍵字參數后面又出現了非關鍵字參數
      parrot(110, voltage=220)   # 參數值重復提供
      parrot(actor='John Cleese') # 未知關鍵字
      一般說來,實參表中位置參數在前,關鍵字參數在后,關鍵字名字必須是形參名字。形參有沒有缺省值都可以用關鍵字參數的形式調用。每一形參至多只能對應一個實參,因此,已經由位置參數傳入值的形參就不能在同一調用中再作為關鍵字參數。
      如果形參表中有一個形為**name的形參,在調用時這個形參可以接收一個字典,字典中包含所有不與任何形參匹配的關鍵字參數。形參表中還可以使用一個特殊的如*name的形參,它將接受所有不能匹配的位置參數組成的一個序表。*name只能在**name之前出現。例如,如果定義了下面的函數:
    def cheeseshop(kind, *arguments, **keywords):
      print "-- Do you have any", kind, '?'
      print "-- I'm sorry, we're all out of", kind
      for arg in arguments: print arg
      print '-'*40
      for kw in keywords.keys(): print kw, ':', keywords[kw]
      就可以象下面這樣調用:
    cheeseshop('Limburger', "It's very runny, sir.",
          "It's really very, VERY runny, sir.",
          client='John Cleese',
          shopkeeper='Michael Palin',
          sketch='Cheese Shop Sketch')
      結果顯示:
      -- Do you have any Limburger ?
      -- I'm sorry, we're all out of Limburger
      It's very runny, sir.
      It's really very, VERY runny, sir.
      ----------------------------------------
      client : John Cleese
      shopkeeper : Michael Palin
      sketch : Cheese Shop Sketch
      4.7.3 任意個數參數
      在所有有名的形參的后面可以有兩個特殊的形參,一個以*args的形式命名,一個以**kw 的形式命名。有了*args形式的形參后函數在調用時就可以在正常的能匹配的實參表后面輸入任意個數的參數,這些參數組成一個序表賦給args形參,不能匹配的關鍵字參數組成一個字典賦給kw形參。在任意個數形參之前可以有0到多個正常的參數。例如:
      def fprintf(file, format, *args):
         file.write(format % args)
      4.7.4 Lambda形式
      因為許多人的要求,Python中加入了一些在函數編程語言和Lisp中常見的功能。可以用lambda 關鍵字來定義小的無名函數。這是一個返回其兩個參數的和的函數:“lambda a, b: a+b” 。Lambda形式可以用于任何需要函數對象的地方。從句法上講lambda形式局限于一個表達式。從語義上講,這只是正常的函數定義的句法甜食。像嵌套函數定義一樣,lambda形式不能訪問包含其定義的作用域中的變量,但審慎地使用缺省參數之可以繞過這個限制。例如:
    def make_incrementor(n):
      return lambda x, incr=n: x+incr
      4.7.5 文檔字符串
      關于文檔字符串的內容與格式正在形成一些慣例。第一行應該為簡短的對象目的概括說明。為了簡明起見,這一行不應該提及對象的名字或類型,因為這些可以通過其他途徑得知(當然如果對象名字就是一個描述函數操作的動詞則當然可以提及其名字)。著以行應該用大些字母開始,以句點結尾。如果文檔字符串中有多行,第二行應該是空行,把概括說明與其它說明分開。以下的行可以是一段或幾段,描述對象的調用方法,它的副作用,等等。
      Python的掃描程序不會從多行字符串中去掉縮進空白,所以處理文檔的工具需要自己處理縮進。只要遵循如下的慣例就可以有利于縮進空白的處理。在第一行之后的第一個非空白的行決定整個文檔字符串的縮進數量(我們不用第一行,因為它經常是直接跟在表示字符串開始的引號后面)。文檔字符串中除第一行以外的各行都要刪除等價于此行的縮進量的空白。對制表符將擴展為空格后再刪除。

      [b]第五章 Python數據結構


      本章更詳細地討論一些已經講過的數據類型的使用,并引入一些新的類型。
      5.1 列表
      列表數據類型還有其它一些方法。下面是列表對象的所有方法:
      insert(i, x) ---- 在指定位置插入一項。第一自變量是要在哪一個元素前面插入,用下標表示。例如,a.insert(0, x)在列表前面插入,a.insert(len(a), x)等價于a.append(x) 。
      append(x) ---- 等價于a.insert(len(a), x)
      index(x) ---- 在列表中查找值x然后返回第一個值為x的元素的下標。沒有找到時出錯。
      remove(x) ---- 從列表中刪去第一個值為x的元素,找不到時出錯。
      sort() ---- 對列表元素在原位排序。注意這個方法改變列表,而不是返回排序后的列表。
      reverse() ---- 把列表元素反序。改變列表。
      count(x) ---- 返回x在列表中出現的次數。
      下例使用了所有的列表方法:
      >>> a = [66.6, 333, 333, 1, 1234.5]
      >>> print a.count(333), a.count(66.6), a.count('x')
      2 1 0
      >>> a.insert(2, -1)
      >>> a.append(333)
      >>> a
      [66.6, 333, -1, 333, 1, 1234.5, 333]
      >>> a.index(333)
      1
      >>> a.remove(333)
      >>> a
      [66.6, -1, 333, 1, 1234.5, 333]
      >>> a.reverse()
      >>> a
      [333, 1234.5, 1, 333, -1, 66.6]
      >>> a.sort()
      >>> a
      [-1, 1, 66.6, 333, 333, 1234.5]
      5.1.1 函數程序設計工具
      Python中有一些函數程序設計風格的東西,例如前面我們看到的lambda形式。關于列表有三個非常有用的內置函數:filter(), map()和reduce()。
      “filter(函數, 序列)”返回一個序列(盡可能與原來同類型),序列元素是原序列中由指定的函數篩選出來的那些,篩選規則是“函數(序列元素)=true”。filter()可以用來取出滿足條件的子集。例如,為了計算一些素數:
      >>> def f(x): return x % 2 != 0 and x % 3 != 0
      ...
      >>> filter(f, range(2, 25))
      [5, 7, 11, 13, 17, 19, 23]
      “map(函數,序列)”對指定序列的每一項調用指定的函數,結果為返回值組成的列表。
      map() 可以對序列進行隱式循環。例如,要計算三次方,可用:
      >>> def cube(x): return x*x*x
      ...
      >>> map(cube, range(1, 11))
      [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
      可以有多個序列作為自變量,這時指定的函數必須也有相同個數的自變量,函數從每個序列分別取出對應元素作為自變量進行調用(如果某個序列比其它的短則取出的值是None)。如果指定的函數是None,map()把它當成一個返回自己的自變量的恒同函數。在函數用None的情況下指定多個序列可以把多個序列搭配起來,比如“map(None, list1, list2)”可以把兩個列表組合為一個成對值的列表。見下例:
      >>> seq = range(8)
      >>> def square(x): return x*x
      ...
      >>> map(None, seq, map(square, seq))
      [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25), (6, 36), (7, 49)]
      “reduce(函數, 序列)”用來進行類似累加這樣的操作,這里的函數是一個兩個子變量的函數,reduce()先對序列的前兩項調用函數得到一個結果,然后對結果和序列下一項調用函數得到一個新結果,如此進行到序列尾部。例如,要計算1到10的和:
      >>> def add(x,y): return x+y
      ...
      >>> reduce(add, range(1, 11))
      55
      如果序列中只有一個值則返回這個值,序列為空時會產生例外。可以指定第三個自變量作為初始值。有初始值時對空序列函數將返回初始值,否則函數先對初始值和序列第一項作用,然后對結果和序列下一項作用,如此進行到序列尾。例如:
      >>> def sum(seq):
      ...   def add(x,y): return x+y
      ...   return reduce(add, seq, 0)
      ...
      >>> sum(range(1, 11))
      55
      >>> sum([])
      0
      5.2 del語句
      上面我們看到,列表的remove()方法可以從列表中刪去某個取值的項,我們還可以用del 語句來刪除指定下標的項。也可以用del語句從列表中刪除一個片斷(前面我們是用給片斷賦空列表的辦法刪除片斷的)。例如:
      >>> a
      [-1, 1, 66.6, 333, 333, 1234.5]
      >>> del a[0]
      >>> a
      [1, 66.6, 333, 333, 1234.5]
      >>> del a[2:4]
      >>> a
      [1, 66.6, 1234.5]
      del也可以用來刪除整個變量,例如:
      >>> del a
      變量刪除以后再引用該變量就會出錯(除非又給它賦值了)。后面我們還會看到del的其它一些應用。
      5.3 序表和序列
      我們看到列表和字符串有許多共同點,例如,下標和片斷運算。它們都屬于序列數據類型。因為Python是一個正在不斷發展的語言,以后還可能會加入其它的序列數據類型。現在還有一種標準的序列數據類型,稱為序表(tuple)。
      序表由一系列值用逗號分隔而成,例如:
      >>> t = 12345, 54321, 'hello!'
      >>> t[0]
      12345
      >>> t
      (12345, 54321, 'hello!')
      >>> # 序表允許嵌套:
      ... u = t, (1, 2, 3, 4, 5)
      >>> u
      ((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
      輸出的序表總是用括號包圍,這樣可以保證嵌套序表得以正確解釋。輸入時可以有括號也可以沒有括號,當經常是必須有括號(如果序表是一個大表達式的一部分)。
      序表有許多用處,例如,(x,y)坐標對,數據庫中的職工紀錄,等等。序表與字符串一樣是不可變的:不允許對序表的某一項賦值。
      生成序表時對0項或1項的序表有特殊的規定:空序表用一對空括號表示;只有一項的序表用一個之后面跟一個抖好表示(指把這個值放在括號內是不夠的)。這樣寫不夠美觀,但很有效。例如:
      >>> empty = ()
      >>> singleton = 'hello',  # <-- note trailing comma
      >>> len(empty)
      0
      >>> len(singleton)
      1
      >>> singleton
      ('hello',)
      語句t = 12345, 54321, 'hello!'是序表打包的一個實例:12345, 54321和'hello!'這些值被打包進了一個序表中。相反的操作也是允許的,例如:
      >>> x, y, z = t
      這叫做序表解包。序表解包要求等號左邊的變量個數等于序表的長度。注意多重賦值只是序表打包和序表解包的聯合使用。有時也對列表進行類似操作,即列表解包。只要把各變量寫成一個列表就可以進行解包:
      >>> a = ['spam', 'eggs', 100, 1234]
      >>> [a1, a2, a3, a4] = a
      5.4 字典
      Python內置的另一個有用的數據類型是字典。字典在其它語言中有時被稱為“關聯記憶” 或“關聯數組”。字典不象序列,它不是用在一個范圍之內的數字下標來索引,而是用鍵值來索引,鍵值可以是任何不可變類型。字符串和數值總可以作鍵值。如果序表只包含字符串、數值或序表則序表也可以作鍵值使用。列表不能用作鍵值,因為列表可以用其append()方法就地改變值。
      最好把字典看成是一系列未排序的“鍵值:值”的集合,在同一字典內鍵值是互不相同的。一對空大括號產生一個空字典:{}。在大括號內加入用逗號分開的“鍵值:值”對可以在字典內加入初始的鍵值和值對,字典在輸出時也是這樣顯示的。對字典的主要操作是以某個鍵值保存一個值,以及給定鍵值后查找對應的值。也可以用del刪除某個鍵值:值對。如果用一個已有定義的鍵值保存某個值則原來的植被遺忘。用不存在的鍵值去查找會出錯。
      字典對象的keys()方法返回字典中所有鍵值組成的列表,次序是隨機的。需要排序時只要對返回的鍵值列表使用sort()方法。為了檢查某個鍵值是否在字典中,使用字典的has_key() 方法。
      下面是字典使用的一個簡單例子:
      >>> tel = {'jack': 4098, 'sape': 4139}
      >>> tel['guido'] = 4127
      >>> tel
      {'sape': 4139, 'guido': 4127, 'jack': 4098}
      >>> tel['jack']
      4098
      >>> del tel['sape']
      >>> tel['irv'] = 4127
      >>> tel
      {'guido': 4127, 'irv': 4127, 'jack': 4098}
      >>> tel.keys()
      ['guido', 'irv', 'jack']
      >>> tel.has_key('guido')
      1

      5.5 條件的進一步討論
      在while語句和if語句中使用的條件除了可以使用比較之外還可以包含其它的運算符。比較運算符“in”和“not in”可以檢查一個值是否在一個序列中。運算符“is”和“is not ”比較兩個對象是否恰好是同一個對象,這只對象列表這樣的可變對象有意義。所有比較運算優先級相同,而比較運算的優先級比所有數值運算優先級低。
      比較允許連寫,例如,a < b == c檢查是否a小于等于b而且b等于c。
      比較可以用邏輯運算符and和or連接起來,比較的結果(或其它任何邏輯表達式)可以用not 取反。邏輯運算符又比所有比較運算符低,在邏輯運算符中,not優先級最高,or的優先級最低,所以“A and not B or C”應解釋為“(A and (not B)) or C”。當然,可以用括號來表示所需的組合條件。
      邏輯運算符and和or稱為“短路”運算符:運算符兩側的表達式是先計算左邊的,如果左邊的結果已知則整體結果已知就不再計算右邊的表達式。例如,如果A和C為真而B為假則“A and B and C”不會計算表達式C。一般地,當短路運算符的運算結果不是用作邏輯值的時候返回的是最后求值的那個表達式的值。
      可以把比較或其它邏輯表達式的結果賦給一個變量。例如:
      >>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
      >>> non_null = string1 or string2 or string3
      >>> non_null
      'Trondheim'
      注意Python和C不同,表達式中不能進行賦值。

      5.6 序列與其它類型的比較
      序列對象可以和其它同序列類型的對象比較。比較使用字典序:先比較最前面兩項,如果這兩項不同則結果可以確定;如果這兩項相同,就比較下面的兩項,如此下去,直到有一個序列到頭為止。如果某兩項本身也是同類型的序列,則進行遞歸的字典序比較。如果兩個序列的所有各項都相等,則這兩個序列相等。如果一個序列是另一個序列的一個初始子序列,短的一個是較小的一個。字符串的字典序比較按各個字符的ASCII次序進行。下面是一些序列比較的實例:
      (1, 2, 3)       < (1, 2, 4)
      [1, 2, 3]       < [1, 2, 4]
      'ABC' < 'C' < 'Pascal' < 'Python'
      (1, 2, 3, 4)      < (1, 2, 4)
      (1, 2)         < (1, 2, -1)
      (1, 2, 3)       = (1.0, 2.0, 3.0)
      (1, 2, ('aa', 'ab'))  < (1, 2, ('abc', 'a'), 4)
      注意不同類型的對象比較目前也是合法的。結果是確定的但卻沒有什么意義:不同類型是按類型的名字排序的。所以,列表(list)總是小于字符串(string),字符串總是小于序表(tuple),等等。但是程序中不能依賴這樣的比較規則,語言實現可能會改變。不同的數值類型可以按數值來比較,所以0等于0.0,等等。


      第六章 模塊

      如果退出Python解釋程序然后再進入,原有的定義(函數和變量)就丟失了。所以,如果需要寫長一點的程序,最好用一個文本編輯程序為解釋程序準備輸入,然后以程序文件作為輸入來運行Python解釋程序,這稱為準備腳本(script)。當你的程序變長時,最好把它拆分成幾個文件以利于維護。你還可能想在幾個程序中都使用某個很方便的函數,但又不想把函數定義賦值到每一個程序中。
      為了支持這些,Python有一種辦法可以把定義放在一個文件中然后就可以在一個腳本中或交互運行中調用。這樣的文件叫做一個模塊;模塊中的定義可以導入其它模塊或主模塊(主模塊指在解釋程序頂級執行的腳本或交互執行的程序所能訪問的變量集合)。模塊是包含了Python定義和語句的文件。文件名由模塊名加上后綴“.py”構成。在模塊內,模塊的名字(作為一個字符串)可以由全局變量__name__的值獲知。例如,在Python的搜索路徑中用你習慣使用的文本編輯器(Python 1.5.2包含了一個用Tkinter編寫的IDLE集成開發環境,MS Windows下有一個PythonWin界面也可以進行Python程序編輯)生成一個名為“fibo.py ”的文件,包含如下內容:

    # Fibonacci numbers module
     
    def fib(n):  # 輸出小于n的Fibonacci序列
      a, b = 0, 1
      while b < n:
        print b,
        a, b = b, a+b
     
    def fib2(n): # 返回小于n的Fibonacci序列
      result = []
      a, b = 0, 1
      while b < n:
        result.append(b)
        a, b = b, a+b
      return result
      然后進入Python解釋程序(在IDLE或PythonWin中可以直接進入解釋程序窗口),用如下命令可以導入模塊:
      >>> import fibo
      這不會把模塊fibo中的函數的名字直接引入當前的符號表,這只是把模塊名fibo引入。
      可以用模塊名來訪問其中的函數:
      >>> fibo.fib(1000)
      1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
      >>> fibo.fib2(100)
      [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
      >>> fibo.__name__
      'fibo'
      如果經常使用某個函數可以給它賦一個局部名字:
      >>> fib = fibo.fib
      >>> fib(500)
      1 1 2 3 5 8 13 21 34 55 89 144 233 377
      6.1 模塊的進一步介紹
      模塊除了可以包含函數定義之外也可以包含可執行語句。這些可執行語句用來初始化模塊,它們只在模塊第一次被導入時執行。
      每個模塊有自己私有的符號表,這個私有符號表對于模塊中的所有函數而言卻是它們的全局符號表。因此,模塊作者可以在模塊中使用全局變量而不需擔心與模塊用戶的全局變量沖突。另一方面,如果你有把握的話也可以用訪問模塊中函數的格式,即modname.itemname的方法來修改模塊中的全局變量。
      模塊可以導入其它模塊。我們通常把所有的導入語句放在模塊(或腳本)的開始位置,
      這不是規定要求的。導入的模塊名放入模塊的全局符號表中。
      導入還有另一種用法,可以把模塊中的名字直接導入使用者的符號表。例如:
      >>> from fibo import fib, fib2
      >>> fib(500)
      1 1 2 3 5 8 13 21 34 55 89 144 233 377
      這不會把模塊名導入使用者的符號表中(例如,上面例子中fibo就沒有定義)。
      還有一種辦法可以導入一個模塊中定義的所有名字:
      >>> from fibo import *
      >>> fib(500)
      1 1 2 3 5 8 13 21 34 55 89 144 233 377
      這可以把模塊中除了以下劃線結尾的所有名字導入。

      6.1.1 模塊搜索路徑
      在導入名為spam的模塊時,解釋程序先在當前目錄中尋找名為“spam.py”的文件,然后從環境變量PYTHONPATH所定義的目錄列表中尋找。PYTHONPATH的用法和可執行文件的搜索路徑PATH用法相同,都是一個目錄列表。當PYTHONPATH未設置的時候,或者文件仍找不到,則搜索繼續在安裝時設定的缺省路徑搜索,在Unix中,這通常是“.:/usr/local/lib/python” 。
      實際上,模塊是按變量sys.path指定的路徑搜索的,此變量在解釋程序啟動時初始化為包含輸入腳本的目錄(或當前路徑),PYTHONPATH和安裝缺省路徑。這樣,用戶可以通過修改sys.path 來修改和替換模塊搜索路徑。參見后面關于標準模塊的一節。
      6.1.2 “編譯”的Python文件
      為了提高調用許多標準模塊的小程序的啟動時間,一個重要的措施是,如果在找到“spam.py ”的目錄中存在一個名為“spam.pyc”的文件,就認為此文件包含了模塊spam的一個所謂“ 字節編譯”版本。用于生成“spam.pyc”的“spam.py”的修改時間被記入了“spam.pyc”中,如果記錄的修改時間與現在文件的時間不相符的話就忽略編譯文件。
      一般不需要自己生成“spam.pyc”這樣的編譯文件。每當“spam.py”成功編譯后解釋程序就嘗試寫編譯版本“spam.pyc”,如果不可寫也不會出錯;如果因為某種原因此文件沒有寫完則生成的“spam.pyc”被識別為不完整的而被忽略。編譯文件“spam.pyc”的格式是不依賴于平臺的,所以不同結構的機器可以共享Python模塊目錄。
      下面是對專家的一些竅門:
      如果Python解釋程序是以-O標志啟動的,將生成優化的編譯代碼,保存在“.pyo”文件中。目前優化不是很多,現在只是去掉assert語句和SET_LINENO指令。使用了-O標志時,所有字節碼都是優化的,“.pyc”文件被忽略,“.py”文件被編譯為優化的字節碼。
      給Python解釋程序兩個優化標志(-OO)產生的優化代碼有時會導致程序運行不正常。目前雙重優化只從字節碼中刪除了__doc__字符串,使得“.pyo”文件較小。有些程序可能是依賴于文檔字符串的,所以只有在確知不會有問題時才可以使用這樣的優化。
      從“.pyc”或“.pyo”讀入的程序并不能比從“.py”讀入的運行更快,它們只是調入速度更快一些。如果一個程序是用在命令行指定腳本文件名的方式運行的,腳本的字節碼不會寫入“.pyc ”或“.pyo”文件。所以如果把程序的主要代碼都移入一個模塊,腳本中只剩下導入該模塊的引導程序則可以略微縮短腳本的啟動時間。
      可以有叫做“spam.pyc”(當用了-O標志時為“spam.pyo”)的文件而沒有對應的源文件“spam.py”。這可以用來分發一個比較難反編譯的Python代碼庫。
      模塊compileall可以把一個目錄中所有模塊編譯為“.pyc”文件(指定了-O選項時編譯為“.pyo”文件)。
      6.2 標準模塊
      Python帶有一個標準模塊庫,在另一個文檔《Python庫參考》中進行了描述。一些模塊直接編入了解釋程序中,這些模塊不是語言的核心,為了運行效率或者為了提供對于系統調用這樣的系統底層功能而編入了解釋程序中。提供那些模塊是編譯時的選擇,例如,amoeba模塊只在提供amoeba底層指令的系統中才能提供。
      有一個模塊值得特別重視:sys模塊,每一個Python解釋程序中都編譯入了這個模塊。變量sys.ps1和sys.ps2定義了交互運行時的初始提示和續行提示。
      >>> import sys
      >>> sys.ps1
      '>>> '
      >>> sys.ps2
      '... '
      >>> sys.ps1 = 'C> '
      C> print 'Yuck!'
      Yuck!
      C>
      這兩個變量只在解釋程序以交互方式運行時才有定義。
      變量sys.path是一個字符串列表,由它確定解釋程序的模塊搜索路徑。它被初始化為環境變量PYTHONPATH所指定的缺省路徑,環境變量沒有定義時初始化為安裝時的缺省路徑。可以用標準的列表操作修改這個搜索路徑,例如:
      >>> import sys
      >>> sys.path.append('/ufs/guido/lib/python')
      6.3 dir()函數
      內置函數dir()用于列出一個模塊所定義的名字,它返回一個字符串列表:
      >>> import fibo, sys
      >>> dir(fibo)
      ['__name__', 'fib', 'fib2']
      >>> dir(sys)
      ['__name__', 'argv', 'builtin_module_names', 'copyright', 'exit',
      'maxint', 'modules', 'path', 'ps1', 'ps2', 'setprofile', 'settrace',
      'stderr', 'stdin', 'stdout', 'version']
      沒有自變量時,dir()列出當前定義的名字。
      >>> a = [1, 2, 3, 4, 5]
      >>> import fibo, sys
      >>> fib = fibo.fib
      >>> dir()
      ['__name__', 'a', 'fib', 'fibo', 'sys']
      注意dir()列出了所有各類名字:變量名、模塊名、函數名,等等。dir()不會列出內置函數、變量的名字。要想列出內置名字的話需要使用標準模塊__builtin__:
      >>> import __builtin__
      >>> dir(__builtin__)
      ['AccessError', 'AttributeError', 'ConflictError', 'EOFError', 'IOError',
      'ImportError', 'IndexError', 'KeyError', 'KeyboardInterrupt',
      'MemoryError', 'NameError', 'None', 'OverflowError', 'RuntimeError',
      'SyntaxError', 'SystemError', 'SystemExit', 'TypeError', 'ValueError',
      'ZeroDivisionError', '__name__', 'abs', 'apply', 'chr', 'cmp', 'coerce',
      'compile', 'dir', 'divmod', 'eval', 'execfile', 'filter', 'float',
      'getattr', 'hasattr', 'hash', 'hex', 'id', 'input', 'int', 'len', 'long',
      'map', 'max', 'min', 'oct', 'open', 'ord', 'pow', 'range', 'raw_input',
      'reduce', 'reload', 'repr', 'round', 'setattr', 'str', 'type', 'xrange']
      6.4 包
      Python中可以用“包”來組織Python的模塊名字空間,名字引用時可以用“帶點的模塊名。例如,模塊名A.B代表包“A”內名為“B”的子模塊。正如使用模塊可以使不同模塊的作者不用顧慮彼此的全局變量名會沖突,使用帶點的模塊名可以使多模塊包如NumPy和PIL的作者不需要擔心彼此的模塊名會沖突。
      假設你有一系列處理聲音文件和聲音數據的模塊(稱為一個“包”)。有許多種不同的聲音文件格式(通常用擴展名來識別,如“wav”,“.aiff”,“.au”),所以你可能需要制作并維護一組不斷增加的模塊來處理不同文件格式的轉換。你還可能需要對聲音數據進行許多不同的操作(如混音、回響、均衡、產生模擬立體聲效果),所以你還需要不斷增加模塊來執行這些操作。一下是你的程序包的可能的結構(用一個分層文件系統表示):
     
    Sound/             頂層包
       __init__.py        初始化音響包
       Formats/         用于文件格式轉換的子程序包
           __init__.py
           wavread.py
           wavwrite.py
           aiffread.py
           aiffwrite.py
           auread.py
           auwrite.py
           ...
       Effects/         用于音響效果的子程序包
           __init__.py
           echo.py
           surround.py
           reverse.py
           ...
       Filters/         用于濾波的子程序包
           __init__.py
           equalizer.py
           vocoder.py
           karaoke.py
           ...
     
      包目錄中的“__init__.py”文件是必須得,用來指示Python把這個目錄看成包,這可以防止有相同名字如“string”的子目錄掩蓋住在搜索路徑后面一些出現的模塊定義。在最簡單的情況下,“__init__.py”可以是一個空文件,它也可以包含初始化包所需的代碼,和設置“__all__”變量,這些后面會加以討論。
      包的用戶可以從包中導入單獨的模塊,如:
         import Sound.Effects.echo
      這可以把子模塊Sound.Effects.echo導入。要引用它也必須用全名,例如:
      Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4)
      導入子模塊的另一種辦法是:
      from Sound.Effects import echo
      這同樣也導入子模塊echo,但調用時不需寫包前綴,所以可以用如:
      echo.echofilter(input, output, delay=0.7, atten=4)
      另外一種寫法是直接導入所需的函數或變量:
      from Sound.Effects.echo import echofilter
      這一次同樣是調入了子模塊echo,但是使其函數echofilter直接可用:
      echofilter(input, output, delay=0.7, atten=4)
      注意使用“from 包 import 項”這樣的格式時,導入的項可以是包的一個子模塊(或子包),也可以是包內定義的其它名字如函數、類、變量。導入語句首先查找包內是否定義了所需的項,如果沒有則假設它是一個模塊然后調入。如果找不到,結果引起ImportError。
      相反的,當使用“import item.subitem.subsubitem”這樣的格式時,除最后一個外其它各項都應該是包,最后一項可以是包也可以是模塊,不允許是前面一項內部定義的類、函數或變量。

      6.4.1 從包中導入*
      現在,如果用戶寫“from Sound.Effects import *”會發生什么情況?理想情況下我們希望這應該掃描文件系統,找到所有包內的子模塊并把它們都導入進來。不幸的是這種操作在Mac和Windows平臺上不能準確實現,這兩種操作系統對文件名的大小寫沒有準確信息。在這些平臺上,不知道名為“ECHO.PY”的文件會作為模塊echo、Echo還是ECHO被導入。(例如,Windows 95在顯示文件名時總是討厭地把第一個字母大寫)。DOS的8+3文件名限制更是對長模塊名造成了有趣的困難。
      這個問題的唯一解決辦法是由模塊作者顯式地提供包的索引。引入*的import語句遵循如下規定:如果包的“__init__.py”文件定義了一個名為“__all__”的列表,這個列表就作為從包內導入*時要導入的所有模塊的名字表。因此當包的新版本發布時需要包的作者確保這個列表是最新的。包的作者如果認為不需要導入*的話也可以不支持這種用法。例如,文件Sounds/Effects/__init__.py 可以包含如下代碼:
     
      __all__ = ["echo", "surround", "reverse"]
      這意味著from Sound.Effects import *將從Sound包中導入指定的三個子包。 
      如果沒有定義__all__,則from Sound.Effects import *語句不會導入Sound.Effects包中的所有子模塊;此語句只能保證Sound.Effects被導入(可能是執行其初始化代碼“__init__.py ”)并導入包中直接定義的名字。這包括由“__init__.py”定義的任何名字和顯式導入的子模塊名。這也包括模塊中已經在前面用import顯式地導入的子模塊,例如:
     
      import Sound.Effects.echo
      import Sound.Effects.surround
      from Sound.Effects import *
     
      在這個例子中,echo和surround模塊被導入當前名字空間,因為它們在執行from...import 語句時已定義(在定義了__all__的情況下這一點也是成立的)。
      注意用戶應盡量避免使用從模塊或包中導入*的做法,因為這樣經常導致可讀性差的代碼。盡管如此,在交互運行時可以用導入*的辦法節省敲鍵次數,而且有些模塊在設計時就考慮到了這個問題,它們只輸出遵循某種約定的名字。注意,from 包 import 特定子模塊的用法并沒有錯,實際上這還是我們推薦的用法,除非程序還需要用到來自其它包的同名的子模塊。
      6.4.2 包內部引用
      子模塊常常需要彼此引用。例如,模塊surround可能要用到模塊echo。事實上,這樣的引用十分常見,所以import語句首先從子模塊的所在包中尋找要導入的子模塊才在標準模塊搜索路徑查找。所以,模塊surround只要寫import echo或from echo import echofilter。如果在包含本模塊的包中沒有找到要導入的模塊,import語句將去尋找指定名字的頂級模塊。
      當包組織成子包時(比如例中的Sound包),沒有一種簡單的辦法可以引用兄弟包中的子模塊――必須使用子模塊的全名。例如,如果模塊Sound.Filters.vocoder要引用Sound.Effects 包中的echo模塊,它可以用Sound.Effects import echo。

      第七章 輸入輸出

      有幾種辦法可以從程序輸出;數據可以用可讀的形式顯示,或保存到文件中以備日后使用。本章討論一些輸入輸出的辦法。
      7.1 輸出格式控制
      到現在為止我們已經看到了兩種輸出值的方法:表達式語句和print語句。(第三種方法是使用文件對象的write()方法,標準輸出文件可以用sys.stdout引用。參見庫參考手冊)。
      我們常常需要控制輸出格式,而不僅僅是顯示空格分開的值。有兩種辦法控制輸出格式:一種辦法是自己進行字符串處理,用字符串的片斷和合并操作可以產生任何可以想象的格式。標準模塊string包含了諸如把字符串填充到指定的列寬這樣的有用操作,后面會有提及。
      另一種辦法是使用%運算符,此運算符以一個字符串為左運算元,它按C的sprintf()函數格式把右運算元轉換為字符串,返回轉換結果。
      問題是:如何把值轉換為字符串?
      幸運的是,Python有一種辦法可以把任何值轉換為字符串:使用repr()函數,或把值寫在兩個反向引號(``)之間。例如:
      >>> x = 10 * 3.14
      >>> y = 200*200
      >>> s = 'The value of x is ' + `x` + ', and y is ' + `y` + '...'
      >>> print s
      The value of x is 31.4, and y is 40000...
      >>> # 反向引號也適用于非數值型
      ... p = [x, y]
      >>> ps = repr(p)
      >>> ps
      '[31.4, 40000]'
      >>> # 轉換字符串對字符串加字符串引號和反斜杠
      ... hello = 'hello, world\n'
      >>> hellos = `hello`
      >>> print hellos
      'hello, world\012'
      >>> # 反向引號內可以是一個序表
      ... `x, y, ('spam', 'eggs')`
      "(31.4, 40000, ('spam', 'eggs'))"
      下面是兩種寫出平方、立方表的方法:
      >>> import string
      >>> for x in range(1, 11):
      ...   print string.rjust(`x`, 2), string.rjust(`x*x`, 3),
      ...   # 前一行的結尾逗號表示不換行
      ...   print string.rjust(`x*x*x`, 4)
      ...
      1  1  1
      2  4  8
      3  9  27
      4 16  64
      5 25 125
      6 36 216
      7 49 343
      8 64 512
      9 81 729
      10 100 1000
      >>> for x in range(1,11):
      ...   print'%2d %3d %4d' % (x, x*x, x*x*x)
      ...
      1  1  1
      2  4  8
      3  9  27
      4 16  64
      5 25 125
      6 36 216
      7 49 343
      8 64 512
      9 81 729
      10 100 1000
      注意print輸出的各項之間額外加了一個空格,這是print的規定。 
      此例顯示了函數string.rjust()的用法,此函數可以把一個字符串放進指定寬度右對齊,左邊用空格填充。類似函數還有string.ljust()和string.center()。這些函數不向外輸出,只是返回轉換后的字符串。如果輸入字符串太長也不會被截斷而是被原樣返回。這樣的處理可能會使你的列對齊失效,但這可能比截斷要好一些,截斷的結果是我們看到一個錯誤的值。(如果你確實需要截斷的話總可以再加一層片斷,如string.ljust(x,n)[0:n])。
      還有一個函數string.zfill(),可以在數值左邊填零。此函數可以處理帶有加減號的情況:
      >>> string.zfill('12', 5)
      '00012'
      >>> string.zfill('-3.14', 7)
      '-003.14'
      >>> string.zfill('3.14159265359', 5)
      '3.14159265359'
      %操作符的用法如下例:
      >>> import math
      >>> print 'The value of PI is approximately %5.3f.' % math.pi
      The value of PI is approximately 3.142.
      如果有多個值可以用一個序表給出,這時格式字符串中要有多個格式,如:
      >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
      >>> for name, phone in table.items():
      ...   print'%-10s ==> %10d' % (name, phone)
      ...
      Jack    ==>    4098
      Dcab    ==>  8637678
      Sjoerd   ==>    4127
      大多數格式與C用法相同,要求要輸出的值的類型符合格式的需要。但是,如果你沒有引發例外錯誤的話也不會產生內核堆列。Python的%s格式要寬得多:如果相應的輸出項不是字符串對象,就先用str()內置函數把它變成字符串。在格式指定中寬度指定為*號表示后面的輸出項中多出一個指定寬度的整數。C格式%n和%p未被支持。
      如果你有一個長格式串不想把它分開,可以在指定格式的地方指定名字,這樣就不需要按次序去把格式和名字對應起來,這種格式為“%(變量名)格式”,例如:
      >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
      >>> print 'Jack: %(Jack)d; Sjoerd: %(Sjoerd)d; Dcab: %(Dcab)d' % table
      Jack: 4098; Sjoerd: 4127; Dcab: 8637678
      這里輸出項總是一個字典,字典的各項值是要輸出的值,字典的鍵值是各值的名字。這種輸出格式經常與內置函數var()配合使用,var()返回包含所有局部變量的字典。
      7.2 讀寫文件
      open()打開一個文件對象,經常使用兩個參數:“open(文件名,模式)”。例如:
      >>> f=open('/tmp/workfile', 'w')
      >>> print f
      <open file '/tmp/workfile', mode 'w' at 80a0960>
      第一自變量是一個包含了文件名的字符串,第二自變量是文件打開方式的字符串。模式‘r ’表示讀取,‘w’表示只寫(已有的同名文件被清除),‘a’表示打開文件在尾部添加, ‘r+’表示打開文件既可以讀也可以寫。打開方式參數可選,缺省為‘r’模式。
      在Windows和Macintosh中在模式中加入‘b’表示以二進制格式打開文件,如‘rb’、‘wb ’、‘r+b’。Windows對文本文件和二進制文件有不同的處理,文本文件中的換行字符在讀寫時有變化。這種對文件數據的幕后的修改不影響ASCII文本文件,但是會破壞二進制數據如JPEG 或“.EXE”文件的數據。讀寫這樣的文件一定要使用二進制格式。(Macintosh中文本模式的精確描述依賴于使用的C庫)。
      7.2.1 文件對象的方法
      本節后面的例子假設已經建立了一個名為f的文件對象。
      為了讀取文件內容,調用f.read(size),可以讀入一定字節數的數據返回為一個字符串。size 是一個可選數值參數,省略size或size取負值時讀入整個文件并返回為一個字符串;如果文件比你的機器內存大一倍,那是你的問題。指定了正的size的時候之多讀入并返回size字節。如果讀到了文件尾,f.read()返回一個空串("")。如:
     
      >>> f.read()
      'This is the entire file.\012'
      >>> f.read()
      ''
     
      f.readline()從文件中讀入一行,返回的字符串中將包括結尾的一個換行符(\n),如果文件的最后一行沒有換行符則由該行讀入的字符串也沒有結尾的換行符。這樣,由readline() 返回的結果不會有歧義,讀入結果為空串時一定是到了文件尾,讀入一個'\n'時為空行。
      >>> f.readline()
      'This is the first line of the file.\012'
      >>> f.readline()
      'Second line of the file\012'
      >>> f.readline()
      ''
      f.readlines()反復調用f.readline(),返回一個包含文件所有行的列表。
      >>> f.readlines()
      ['This is the first line of the file.\012', 'Second line of the file\012']f.write(string)把string的內容寫入到文件中,返回None。

      >>> f.write('This is a test\n')
     
      f.tell()返回文件對象的當前讀寫為止,按從文件開始的字節數算。為了改變讀寫位置,使用“f.seek(位移,從哪里)”。讀寫位置按一個參考點加上位移來計算,參考點用“從那里”參數指定,取0時從文件頭開始算,取1時按當前位置算,取2時從文件尾算。缺省值是0 ,從文件開始算。
      >>> f=open('/tmp/workfile', 'r+')
      >>> f.write('0123456789abcdef')
      >>> f.seek(5)   # 從文件頭前進5個字節,到達第6個字符
      >>> f.read(1)
      '5'
      >>> f.seek(-3, 2) # 轉到結尾前3個字符
      >>> f.read(1)
      'd'
      用外一個文件后調用f.close()關閉文件,釋放打開文件所占用的系統資源。文件關閉后再使用此文件對象就無效了。
      >>> f.close()
      >>> f.read()
      Traceback (innermost last):
        File "<stdin>", line 1, in ?
      ValueError: I/O operation on closed file
    文件對象還有其它一些不太常用的方法,例如isatty()和truncate(),參見庫參考手冊。 
      7.2.2 pickle模塊
      字符串可以很容易地從文件讀入或向文件寫出。讀入數值要麻煩一些,因為read()方法總是返回字符串,要把讀入的字符串傳給象string.atoi()這樣的函數,把象‘123’這樣的字符串轉換為對應的整數值123。但是,當你想保存更復雜的數據類型如列表、字典或類實例時,讀寫就要復雜得多。
      Python的設計使程序員可以不必反復編寫調試保存復雜數據類型的代碼,它提供了一個叫做pickle的標準模塊。這個令人驚異的模塊可以把幾乎任何Python對象轉換為字符串表示,這個過程叫做腌制,從對象的字符串表示恢復對象叫做恢復。在腌制和反腌制之間,對象的字符串表示可以保存在文件或數據中,甚至于通過網絡連接傳送到遠程計算機上。
      如果你有一個對象x,有一個可寫的文件對象f,最簡單的腌制對象的辦法是下面一行代碼:
      pickle.dump(x, f)
      為了恢復對象,如果剛才的文件已打開用于讀取,文件對象名仍為f,則:
      x = pickle.load(f)
      (腌制和恢復還有其它用法,可以腌制多個對象,可以不把數據寫入文件,詳見庫參考手冊)。
      pickle是保存Python對象并被其它程序或同一程序以后再運行時調用的標準辦法,這種做法的專用術語叫做“持久對象”。因為pickle使用廣泛,許多Python擴展模塊的作者都留意使新增加的數據類型如矩陣可以正確地腌制和恢復。

      第八章 錯誤與例外

      到現在為止我們只是提到了錯誤信息而沒有詳細討論,如果你運行了前面的例子可能已經看到了一些錯誤信息。至少有兩種不同錯誤:句法錯和例外錯(exceptions)。
      8.1 句法錯
      句法錯也稱為語法分析錯,是你在學習Python的時候最可能犯的錯誤。
    >>> while 1 print 'Hello world'
     File "<stdin>", line 1
      while 1 print 'Hello world'
            ^
    SyntaxError: invalid syntax
      語法分析器重復出錯行,并用一個小‘箭頭’指向行內最早發現錯誤的位置。錯誤是由箭頭前面的記號引起的(至少是在這里檢測到的)。在本例中,錯誤在關鍵字print處檢測到,因為它前面應該有一個冒號(“:”)。錯誤信息中顯示了文件名和行號這樣如果錯誤發生在一個腳本文件中你就知道到哪里去找。
      8.2 例外
      即使語句或表達式句法沒有問題,在試圖運行的時候也可能發生錯誤。運行時檢測到的錯誤叫做例外,這種錯誤不一定必然是致命的:你很快就會學到如何在Python程序中處理例外。然而,多數例外不能被程序處理,這是會產生錯誤信息,如:
      >>> 10 * (1/0)
      Traceback (innermost last):
        File "<stdin>", line 1
      ZeroDivisionError: integer division or modulo
      >>> 4 + spam*3
      Traceback (innermost last):
        File "<stdin>", line 1
      NameError: spam
      >>> '2' + 2
      Traceback (innermost last):
        File "<stdin>", line 1
      TypeError: illegal argument type for built-in operation
      錯誤信息的最后一行顯示發生的情況。例外有不同的類型,類型作為錯誤信息的一部分顯示:上例中錯誤的類型有ZeroDivisionError、NameError和TypeError。作為例外類型顯示的字符串是發生的例外的內置名。這對于所有內置例外成立,但對用戶自定義例外不一定成立(用戶最好能遵守這樣的約定)。標準例外名是內置的標識符(不是保留關鍵字)。
      此行的其余部分是錯誤的細節,其解釋依賴于例外類型。錯誤信息前面的部分以堆棧反跟蹤的形式顯示了發生錯誤的上下文環境。一般這包含了列出源代碼行的一個列出源程序行的堆棧反跟蹤;然而,它不會顯示從標準輸入讀進的行。
      庫參考手冊列出了內置例外和其含義。 
      8.3 例外處理
      可以編程序來處理選定的例外。請看下面的例子,顯示一些浮點數的倒數:
      >>> numbers = [0.3333, 2.5, 0, 10]
      >>> for x in numbers:
      ...   print x,
      ...   try:
      ...     print 1.0 / x
      ...   except ZeroDivisionError:
      ...     print '*** has no inverse ***'
      ...
      0.3333 3.00030003
      2.5 0.4
      0 *** has no inverse ***
      10 0.1
      try語句是這樣工作的:
      首先,運行try子句(在try和except之間的語句)。
      如果沒有發生例外,跳過except子句,try語句運行完畢。
      如果在try子句中發生了例外錯誤而且例外錯誤匹配except后指定的例外名,則跳過try子句剩下的部分,執行except子句,然后繼續執行try語句后面的程序。
      如果在try子句中發生了例外錯誤但是例外錯誤不匹配except后指定的例外名,則此例外被傳給外層的try語句。如果沒有找到匹配的處理程序則此例外稱作是未處理例外,程序停止運行,顯示錯誤信息。
      try語句可以有多個except子句,為不同的例外指定不同處理。至多只執行一個錯誤處理程序。錯誤處理程序只處理相應的try子句中發生的例外,如果同try語句中其它的錯誤處理程序中發生例外錯誤處理程序不會反應。一個except子句可以列出多個例外,寫在括號里用逗號分開,例如:
      ... except (RuntimeError, TypeError, NameError):
      ...   pass
      最后一個except子句可以省略例外名,作為一個通配項。這種方法要謹慎使用,因為這可能會導致程序實際已出錯卻發現不了。
      try ... except語句有一個可選的else子句,如有的話要放在所有except子句之后。else 的意思是沒有發生例外,我們可以把try子句中沒有發生例外時要做的事情放在這個子句里。例如:
    for arg in sys.argv[1:]:
      try:
        f = open(arg, 'r')
      except IOError:
        print '不能打開', arg
      else:
        print arg, '有', len(f.readlines()), '行'
        f.close()
      例外發生時可能伴有一個值,叫做例外的參數。參數是否存在及其類型依賴于例外的類型。對于有參數的例外,except在自居可以在例外名(或表)后指定一個變量用來接受例外的參數值,如:
      >>> try:
      ...   spam()
      ... except NameError, x:
      ...   print 'name', x, 'undefined'
      ...
      name spam undefined
      有參數的例外未處理時會在錯誤信息的最后細節部分列出其參數值。 
      例外處理程序不僅處理直接產生于try子句中的例外,也可以處理try子句中調用的函數(甚至是間接調用的函數)中的例外。如:
      >>> def this_fails():
      ...   x = 1/0
      ...
      >>> try:
      ...   this_fails()
      ... except ZeroDivisionError, detail:
      ...   print 'Handling run-time error:', detail
      ...
      Handling run-time error: integer division or modulo
      8.4 產生例外
      raise語句允許程序員強行產生指定的例外。例如:
      >>> raise NameError, 'HiThere'
      Traceback (innermost last):
      File "<stdin>", line 1
      NameError: HiThere
      raise語句的第一個參數指定要產生的例外的名字。可選的第二參數指定例外的參數。 

      8.5 用戶自定義例外
      程序中可以定義自己的例外,只要把一個字符串賦給一個變量即可。例如:
      >>> my_exc = 'my_exc'
      >>> try:
      ...   raise my_exc, 2*2
      ... except my_exc, val:
      ...   print 'My exception occurred, value:', val
      ...
      My exception occurred, value: 4
      >>> raise my_exc, 1
      Traceback (innermost last):
      File "<stdin>", line 1
      my_exc: 1
      許多標準模塊用這種方法報告自己定義的函數中發生的錯誤。 
      8.6 定義清理動作
      try語句還有另一個finally可選子句,可以用來規定不論出錯與否都要執行的動作。
      例如:
      >>> try:
      ...   raise KeyboardInterrupt
      ... finally:
      ...   print 'Goodbye, world!'
      ...
      Goodbye, world!
      Traceback (innermost last):
      File "<stdin>", line 2
      KeyboardInterrupt
      finally子句不論try子句中是否發生例外都會執行。例外發生時,先執行finally子句然后重新提出該例外。當try語句用break或return語句退出時也將執行finally子句。
      要注意的是,try語句有了except子句就不能有finally子句,有了finally子句就不能有except 子句,不能同時使用except子句和finally子句。需要的話可以嵌套。

      第九章 類

      Python是一個真正面向對象的語言,它只增加了很少的新語法就實現了類。它的類機制是C++ 和Modula-3的類機制的混合。Python的類并不嚴格限制用戶對定義的修改,它依賴于用戶自覺不去修改定義。然而Python對類最重要的功能都保持了完全的威力。類繼承機制允許多個基類的繼承,導出類可以重載基類的任何方法,方法可以調用基類的同名方法。對象可以包含任意多的私有數據。
      用C++術語說,所有類成員(包括數據成員)是公用的,所有成員函數是虛擬(virtual)的。沒有特別的構建函數或銷毀函數(destructor)。如同在Modula-3中一樣,從對象的方法中要引用對象成員沒有簡捷的辦法:方法函數的必須以對象作為第一個參數,而在調用時則自動提供。象在Smalltalk中一樣,類本身也是對象,實際上這里對象的含義比較寬:在Python 中所有的數據類型都是對象。象在C++或Modula-3中一樣,內置類型不能作為基類由用戶進行擴展。并且,象C++但不象Modula-3,多數有特殊語法的內置函數(如算術算符、下標等)可以作為類成員重定義。
      9.1 關于術語
      Python的對象概念比較廣泛,對象不一定非得是類的實例,因為如同C++和Modula-3而不同于Smalltalk,Python的數據類型不都是類,比如基本內置類型整數、列表等不是類,甚至較古怪的類型如文件也不是類。然而,Python所有的數據類型都或多或少地帶有一些類似對象的語法。對象是有單獨身份的,同一對象可以有多個名字與其聯系,這在其他語言中叫做別名。這樣做的好處乍一看并不明顯,而且對于非可變類型(數字、字符串、序表(tuple))等沒有什么差別。但是別名句法對于包含可變對象如列表、字典及涉及程序外部物件如文件、窗口的程序有影響,這可以有利于程序編制,因為別名有些類似指針:比如,傳遞一個對象變得容易,因為這只是傳遞了一個指針;如果一個函數修改了作為參數傳遞來的對象,修改結果可以傳遞回調用處。這樣就不必象Pascal那樣使用兩種參數傳遞機制。
      9.2 Python作用域與名字空間
      在引入類之前,我們必須講一講Python的作用域規則。類定義很好地利用了名字空間,需要了解Python如何處理作用域和名字空間才能充分理解類的使用。另外,作用域規則也是一個高級Python程序員必須掌握的知識。
      先給出一些定義。
      名字空間是從名字到對象的映射。多數名字空間目前是用Python字典類型實現的,不過這一點一般是注意不到的,而且將來可能會改變。下面是名字空間的一些實例:Python中內置的名字(如abs()等函數,以及內置的例外名);模塊中的全局名;函數調用中的局部變量名。在某種意義上一個對象的所有屬性也構成了一個名字空間。關于名字空間最重要的事要知道不同名字空間的名字沒有任何聯系;例如,兩個不同模塊可能都定義了一個叫“maximize ”的函數而不會引起混亂,因為模塊的用戶必須在函數名之前加上模塊名作為修飾。
      另外,在Python中可以把任何一個在句點之后的名字稱為屬性,例如,在表達式z.real中,real是一個對象z的屬性。嚴格地說,對模塊中的名字的引用是屬性引用:在表達式modname.funcname 中,modname是一個模塊對象,funcname是它的一個屬性。在這種情況下在模塊屬性與模塊定義的全局名字之間存在一個直接的映射:它們使用相同的名字空間!
      屬性可以是只讀的也可以是可寫的。在屬性可寫的時候,可以對屬性賦值。模塊屬性是可寫的:你可以寫“modname.the_answer = 42”。可寫屬性也可以用del語句閃出,如“del modname.the_answer”。
      名字空間與不同時刻創建,有不同的生存周期。包含Python內置名字的名字空間當Python 解釋程序開始時被創建,而且不會被刪除。模塊的全局名字空間當模塊定義被讀入時創建,一般情況下模塊名字空間也一直存在到解釋程序退出。由解釋程序的最頂層調用執行的語句,不論是從一個腳本文件讀入的還是交互輸入的,都屬于一個叫做__main__的模塊,所以也存在于自己的全局名字空間之中。(內置名字實際上也存在于一個模塊中,這個模塊叫做__builtin__ )。
      函數的局部名字空間當函數被調用時創建,當函數返回或者產生了一個不能在函數內部處理的例外時被刪除。(實際上,說是忘記了這個名字空間更符合實際發生的情況。)當然,遞歸調用在每次遞歸中有自己的局部名字空間。
      一個作用域是Python程序中的一個文本區域,其中某個名字空間可以直接訪問。“直接訪問” 這里指的是使用不加修飾的名字就直接找到名字空間中的對象。
      雖然作用域是靜態定義的,在使用時作用域是動態的。在任何運行時刻,總是恰好有三個作用域在使用中(即恰好有三個名字空間是直接可訪問的):最內層的作用域,最先被搜索,包含局部名字;中層的作用域,其次被搜索,包含當前模塊的全局名字;最外層的作用域最后被搜索,包含內置名字。
      一般情況下,局部作用域引用當前函數的局部名字,其中局部是源程序文本意義上來看的。在函數外部,局部作用域與全局作用域使用相同的名字空間:模塊的名字空間。類定義在局部作用域中又增加了另一個名字空間。
      一定要注意作用域是按照源程序中的文本位置確定的:模塊中定義的函數的全局作用域是模塊的名字空間,不管這個函數是從哪里調用或者以什么名字調用的。另一方面,對名字的搜索卻是在程序運行中動態進行的,不過,Python語言的定義也在演變,將來可能發展到靜態名字解析,在“編譯”時,所以不要依賴于動態名字解析!(實際上,局部名字已經是靜態確定的了)。
      Python的一個特別之處是賦值總是進入最內層作用域。關于刪除也是這樣:“del x”從局部作用域對應的名字空間中刪除x的名字綁定(注意在Python中可以多個名字對應一個對象,所以刪除一個名字只是刪除了這個名字與其對象間的聯系而不一定刪除這個對象。實際上,所有引入新名字的操作都使用局部作用域:特別的,import語句和函數定義把模塊名或函數名綁定入局部作用域。(可以使用global語句指明某些變量是屬于全局名字空間的)。
      9.3 初識類
      類引入了一些新語法,三種新對象類型,以及一些新的語義。
      9.3.1 類定義語法
      類定義的最簡單形式如下:
      class 類名:
      <語句-1>
      .
      .
      .
      <語句-N>
      如同函數定義(def語句)一樣,類定義必須先執行才能生效。(甚至可以把類定義放在if 語句的一個分支中或函數中)。在實際使用時,類定義中的語句通常是函數定義,其它語句也是允許的,有時是有用的――我們后面會再提到這一點。類內的函數定義通常具有一種特別形式的自變量表,專用于方法的調用約定――這一點也會在后面詳細討論。
      進入類定義后,產生了一個新的名字空間,被用作局部作用域――于是,所有對局部變量的賦值進入這個新名字空間。特別地,函數定義把函數名與新函數綁定在這個名字空間。
      當函數定義正常結束(從結尾退出)時,就生成了一個類對象。這基本上是將類定義生成的名字空間包裹而成的一個對象;我們在下一節會學到類對象的更多知識。原始的局部作用域(在進入類定義之前起作用的那個)被恢復,類對象在這里被綁定到了類對象定義頭部所指定的名字。
      9.3.2 類對象
      類對象支持兩種操作:屬性引用和實例化。屬性引用的格式和Python中其它的屬性引用格式相同,即obj.name。有效的屬性名包括生成類對象時的類名字空間中所有的名字。
      所以,如果象下面這樣定義類:
    class MyClass:
      "A simple example class"
      i = 12345
      def f(x):
        return 'hello world'
      則MyClass.i和MyClass.f都是有效的屬性引用,分別返回一個整數和一個函數對象。也可以對類屬性賦值,所以你可以對MyClass.i賦值而改變該屬性的值。
      __doc__也是一個有效的屬性,它是只讀的,返回類的文檔字符串:“A simple example class”。
      類實例化使用函數記號。只要把這個類對象看成是一個沒有自變量的函數,返回一個類實例。例如(假設使用上面的類):
      x = MyClass()
      可以生成該類的一個新實例并把實例對象賦給局部變量x。 
      9.3.3 實例對象
      我們如何使用實例對象呢?類實例只懂得屬性引用這一種操作。有兩類有效的屬性。

      第一類屬性叫做數據屬性。數據屬性相當于Smalltalk中的“實例變量”,和C++中的“數據成員”。數據成員不需要聲明,也不需要在類定義中已經存在,象局部變量一樣,只要一賦值它就產生了。例如,如果x是上面的MyClass類的一個實例,則下面的例子將顯示值16而不會留下任何痕跡:
    x.counter = 1
    while x.counter < 10:
      x.counter = x.counter * 2
    print x.counter
    del x.counter
      類實例能理解的第二類屬性引用是方法。方法是“屬于”一個對象的函數。(在Python中,方法并不是只用于類實例的:其它對象類型也可以有方法,例如,列表對象也有append、insert 、remove、sort等方法。不過,在這里除非特別說明我們用方法來特指類實例對象的方法)。
      類對象的有效方法名依賴于它的類。按照定義,類的所有類型為函數對象屬性定義了其實例的對應方法。所以在我們的例子y,x.f是一個有效的方法引用,因為MyClass是一個函數;x.i 不是方法引用,因為MyClass.i不是。但是x.f和MyClass.f不是同一個東西――x.f是一個方法對象而不是一個函數對象。
      9.3.4 方法對象
      方法一般是直接調用的,例如:
      x.f()
      在我們的例子中,這將返回字符串‘hello world’。然而,也可以不直接調用方法:x.f 是一個方法對象,可以把它保存起來再調用。例如:
    xf = x.f
    while 1:
      print xf()
    會不停地顯示“hello world”。 
      調用方法時到底發生了什么呢?你可能已經注意到x.f()調用沒有自變量,而函數f在調用時有一個自變量。那個自變量是怎么回事?Python如果調用一個需要自變量的函數時忽略自變量肯定會產生例外錯誤――即使那個自變量不需要用到……
      實際上,你可以猜出答案:方法與函數的區別在于對象作為方法的第一個自變量自動傳遞給方法。在我們的例子中,調用x.f()等價于調用MyClass.f(x)。一般地,用n個自變量的去調用方法等價于把方法所屬對象插入到第一個自變量前面以后調用對應函數。如果你還不理解方法是如何工作的,看一看方法的實現可能會有所幫助。在引用非數據屬性的實例屬性時,將搜索它的類。如果該屬性名是一個有效的函數對象,就生成一個方法對象,把實例對象(的指針)和函數對象包裝到一起:這就是方法對象。當方法對象用一個自變量表調用時,它再被打開包裝,由實例對象和原自變量表組合起來形成新自變量表,用這個新自變量表調用函數。
      9.4 一些說明
      在名字相同時數據屬性會覆蓋方法屬性;為了避免偶然的名字沖突,這在大型程序中會造成難以查找的錯誤,最好按某種命名慣例來區分方法名和數據名,例如,所有方法名用大寫字母開頭,所有數據屬性名前用一個唯一的字符串開頭(或者只是一個下劃線),或方法名用動詞而數據名用名詞。
      數據屬性可以被方法引用也可以被普通用戶(“客戶”)引用。換句話說,類不能用來構造抽象數據類型。實際上,Python中沒有任何辦法可以強制進行數據隱藏——這些都是基于慣例。(另一方面,Python的實現是用C寫的,它可以完全隱藏實現細節,必要時可以控制對象存取;用C寫的Python擴展模塊也有同樣特性)。
      客戶要自己小心使用數據屬性——客戶可能會因為隨意更改類對象的數據屬性而破壞由類方法維護的類數據的一致性。注意客戶只要注意避免名字沖突可以任意為實例對象增加新數據屬性而不需影響到方法的有效性——這里,有效的命名慣例可以省去許多麻煩。
      從方法內要訪問本對象的數據屬性(或其它方法)沒有一個簡寫的辦法。我認為這事實上增加了程序的可讀性:在方法定義中不會混淆局部變量和實例變量。
      習慣上,方法的第一自變量叫做self。這只不過是一個習慣用法:名字self在Python中沒有任何特殊意義。但是,因為用戶都使用此慣例,所以違背此慣例可能使其它Python程序員不容易讀你的程序,可以想象某些類瀏覽程序會依賴于此慣例)。
      作為類屬性的任何函數對象都為該類的實例定義一個方法。函數的定義不一定必須在類定義內部:只要在類內把一個函數對象賦給一個局部變量就可以了。例如:
    # Function defined outside the class
    def f1(self, x, y):
      return min(x, x+y)
     
    class C:
      f = f1
      def g(self):
        return 'hello world'
      h = g
      現在f、g和h都是類C的屬性且指向函數對象,所以它們都是C的實例的方法——其中h與g 完全等價。注意我們應該避免這種用法以免誤導讀者。
      方法可以用代表所屬對象的self自變量來引用本類其它的方法,如:
    class Bag:
      def empty(self):
        self.data = []
      def add(self, x):
        self.data.append(x)
      def addtwice(self, x):
        self.add(x)
        self.add(x)
      實例化操作(“調用”一個類對象)生成一個空對象。許多類要求生成具有已知初識狀態的類。為此,類可以定義一個特殊的叫做__init__()的方法,如:
      def __init__(self):
        self.empty()
      一個類定義了__init__()方法以后,類實例化時就會自動為新生成的類實例調用調用__init__() 方法。所以在Bag例子中,可以用如下程序生成新的初始化的實例:
      x = Bag()
      當然,__init__()方法可以有自變量,這樣可以實現更大的靈活性。在這樣的情況下,類實例化時指定的自變量被傳遞給__init__()方法。例如:
      >>> class Complex:
      ...   def __init__(self, realpart, imagpart):
      ...     self.r = realpart
      ...     self.i = imagpart
      ...
      >>> x = Complex(3.0,-4.5)
      >>> x.r, x.i
      (3.0, -4.5)
      方法可以和普通函數一樣地引用全局名字。方法的全局作用域是包含類定義的模塊。(注意類本身并不被用作全局作用域!)雖然我們很少需要在方法中使用全局數據,全局作用域還是有許多合法的用途:例如,導入全局作用域的函數和模塊可以被方法使用,在同一模塊中定義的函數和方法也可以被方法使用。包含此方法的類一般也在此全局作用域中定義,下一節我們會看到一個方法為什么需要引用自己的類!
      9.5 繼承
      當然,一個語言如果不支持繼承就談不到“類”。導出類的定義方法如下:
      class 導出類名(基類名):
      <語句-1>
      .
      .
      .
      <語句-N>
      其中“基類名”必須在包含導出類定義的作用域中有定義。除了給出基類名外,還可以給出一個表達式,在基類定義于其它模塊中時這是有用的,如:
      class 導出類名 (模塊名.基類名):
      導出類定義的運行和基類運行的方法是一樣的。生成類對象是,基類被記憶。這用于解決屬性引用:如果類中未找到要求的屬性就到基類中去查找。如果基類還有基類的話這個規則遞歸地應用到更高的類。
      導出類在實例化時沒有任何特殊規則。“導出類名()”產生該類的一個新實例。方法引用這樣解決:搜索相應類屬性,如果必要的話逐級向基類查找,如果找到了一個函數對象就是有效的方法引用。
      導出類可以重寫基類的方法。因為方法在調用同一對象的其它方法時并無任何特殊權限,如果基類中某一方法調用同一基類的另一方法,在導出類中該方法調用的就可能是已經被導出類重寫后的方法了。(對C++程序員而言:Python中所有方法都是“虛擬函數”)。
      導出類中重寫的方法可能是需要擴充基類的同名方法而不是完全代替原來的方法。導出類調用基類同名方法很簡單:“基類名.方法名(self, 自變量表)”。對類用戶這種做法偶爾也是有用的。(注意只有基類在同一全局作用域定義或導入時才能這樣用)。
      8.5.1 多重繼承
      Python也支持有限的多重繼承。有多個基類的類定義格式如下:
      class 導出類名 (基類1, 基類2, 基類3):
      <語句-1>
      .
      .
      .
      <語句-N>
      關于多重繼承只需要解釋如何解決類屬性引用。類屬性引用是深度優先,從左向右進行的。所以,如果在導出類定義中未找到某個屬性,就先在基類1中查找,然后(遞歸地)在基類1 的基類中查找,如果都沒有找到,就在基類2中查找,如此進行下去。
      (對某些人來說寬度優先——先在基類2和基類3中查找再到基類1的基類中查找——看起來更自然。然而,這需要你在確定基類1與基類2的屬性沖突時明確知道這個屬性是在基類1本身定義還是在其基類中定義。深度優先規則不區分基類1的一個屬性到底是直接定義的還是繼承來的)。
      很顯然,如果不加約束地使用多重繼承會造成程序維護的惡夢,因為Python避免名字沖突只靠習慣約定。多重繼承的一個眾所周知的問題是當導出類有兩個基類恰好從同一個基類導出的。盡管很容易想到這種情況的后果(實例只有一份“實例變量”或數據屬性被共同的基類使用),但是這種做法有什么用處卻是不清楚的。
      9.6 私有變量
      Python對私有類成員有部分支持。任何象__spam這樣形式的標識符(至少有兩個前導下劃線,至多有一個結尾下劃線)目前被替換成_classname__spam,其中classname是所屬類名去掉前導下劃線的結果。這種攪亂不管標識符的語法位置,所以可以用來定義類私有的實例、變量、方法,以及全局變量,甚至于保存對于此類是私有的其它類的實例。如果攪亂的名字超過255個字符可能會發生截斷。在類外面或類名只有下劃線時不進行攪亂。
      名字攪亂的目的是給類一種定義“私有”實例變量和方法的簡單方法,不需擔心它的其它類會定義同名變量,也不怕類外的代碼弄亂實例的變量。注意攪亂規則主要是為了避免偶然的錯誤,如果你一定想做的話仍然可以訪問或修改私有變量。這甚至是有用的,比如調試程序要用到私有變量,這也是為什么這個漏洞沒有堵上的一個原因。(小錯誤:導出類和基類取相同的名字就可以使用基類的私有變量)。
      注意傳遞給exec,eval()或evalfile()的代碼不會認為調用它們的類的類名是當前類,這與global語句的情況類似,global的作用局限于一起字節編譯的代碼。同樣的限制也適用于getattr() ,setattr()和delattr(),以及直接訪問__dict__的時候。
      下面例子中的類實現了自己的__getattr__和__setattr__方法,把所有屬性保存在一個私有變量中,這在Python的新舊版本中都是可行的:
    class VirtualAttributes:
      __vdict = None
      __vdict_name = locals().keys()[0]
      def __init__(self):
        self.__dict__[self.__vdict_name] = {}
      def __getattr__(self, name):
        return self.__vdict[name]
      def __setattr__(self, name, value):
        self.__vdict[name] = value
      9.7 補充
      有時我們希望有一種類似Pascal的“record”或C的“struct”的類型,可以把幾個有名的數據項組合在一起。一個空類可以很好地滿足這個需要,如:
    class Employee:
      pass
     
      john = Employee() # 生成一個空職員記錄

      # 填充記錄的各個域
      john.name = 'John Doe'
      john.dept = 'computer lab'
      john.salary = 1000
      一段需要以某種抽象數據類型作為輸入的Python程序經常可以接受一個類作為輸入,該類只是模仿了應輸入的數據類型的方法。例如,如果你有一個函數是用來格式化一個文件對象中的數據,就可一個定義一個具有方法read()和readline()的類,該類可以不從文件輸入而是從一個字符串緩沖區輸入,把這個類作為自變量。
      實例方法對象也有屬性:m.im_self是方法所屬的實例,m.im_func是方法對應的函數對象。 
      9.7.1 例外可以是類
      用戶自定義的例外除了可以是字符串對象以外還可以是類。這樣可以定義可擴充的分層的類例外結構。
      raise語句有兩種新的有效格式:
      raise 類, 實例

      raise 實例
      在第一種形式中,“實例”必須是“類”的實例或“類”的導出類的實例。第二種形式是
      raise instance.__class__, instance
      的簡寫。except語句除了可以列出字符串對象外也可以列出類。execpt子句中列出的類如果是發生的例外類或基類則是匹配的(反過來不對——except中如果是導出類而發生的例外屬于基類時是不匹配的)。例如,下面的程序會顯示B、C、D:
    class B:
      pass
    class C(B):
      pass
    class D(C):
      pass
     
    for c in [B, C, D]:
      try:
        raise c()
      except D:
        print "D"
      except C:
        print "C"
      except B:
        print "B"
      注意如果把except子句的次序顛倒過來的話(“except B”放在最前),程序將顯示B,B ,B——因為第一個匹配的except子句被引發。
      當沒有處理的例外是類的時候,類名顯示在錯誤信息中,后面跟著一個冒號和一個空格,最后是實例用內置函數str()轉換成字符串的結果。

      第十章 進一步學習

      希望通過這個入門課程的學習增強了你對使用Python的興趣。下一步怎么辦呢? 你可以進一步去讀《庫參考手冊》,該手冊對類型、函數、模塊等給出了完整的(盡管比較簡短)參考材料,可以節省你寫Python程序時的大量時間。標準Python軟件包括了大量的C 和Python的代碼,有讀取Unix信箱的,用HTTP接收文檔的,生成隨機數的,解析命令行選項的,寫CGI程序的,壓縮數據的,等等許多功能;粗略看一看庫參考手冊就可以知道有那些內容。
      Python的主網站是http://www.python.org,這里有程序代碼、文檔及有Python相關的其它網頁的信息。這個網站在世界上許多地方有鏡像,例如歐洲、日本、澳大利亞,訪問距離近的鏡像網站可能比訪問主網站快。還有一個非正式的網站是 http://starship.skyport.net ,包含了一系列有關Python的個人網頁,許多人在這里放了可下載的軟件。
      對于關于Python的問題及錯誤報告,可以向comp.lang.python新聞組投稿,或向python-list@cwi.nl 郵件表發郵件。該新聞組和郵件表是互相轉發的,所以向其中一個發信會自動轉寄到另一個。每天有35-45份郵件,有問(答)問題的、建議新功能的、聲明新模塊的。在發信前應該先查閱 http://www.python.org/doc/FAQ.html 處的常見問題表(FAQ),或在Python軟件的Misc/ 子目錄中尋找該文件。該FAQ回答了許多反復出現的問題,可能已經有了你的問題的答案。
      你可以通過加入Python軟件活動來支持Python團體,該團體負責python.org網站、ftp和電子郵件服務器,并組織Python研討班。關于如何加入參見 http://www.python.org/psa/。

    ?

    posted on 2006-06-11 19:25 rd2pm 閱讀(1165) 評論(0)  編輯  收藏

    只有注冊用戶登錄后才能發表評論。


    網站導航:
    博客園   IT新聞   Chat2DB   C++博客   博問  
     

    主站蜘蛛池模板: 人人狠狠综合久久亚洲高清| 在线观看www日本免费网站| 永久中文字幕免费视频网站| 亚洲国产亚洲综合在线尤物| 99热这里有免费国产精品| 亚洲av不卡一区二区三区| 嫩草在线视频www免费观看| 亚洲爆乳无码一区二区三区| 你懂的在线免费观看| 亚洲人成精品久久久久| 国产精品免费高清在线观看| 亚洲国产精品久久久久婷婷软件| 久久永久免费人妻精品下载| 亚洲国产精品日韩在线| 成年人性生活免费视频| 精品成人一区二区三区免费视频| 亚洲av无码国产精品色在线看不卡 | 亚洲国产成人久久精品动漫| 免费国产污网站在线观看15| 亚洲成aⅴ人在线观看| 久久久久亚洲AV综合波多野结衣| 亚洲av永久无码精品古装片| 免费国产叼嘿视频大全网站| 亚洲最大福利视频网站| 成人免费视频69| 亚洲AV无码AV日韩AV网站| 亚洲精品乱码久久久久久不卡| 中文字幕成人免费高清在线视频| 亚洲成AV人片在线观看WWW| 亚洲黄色免费在线观看| 亚洲AV成人一区二区三区在线看| 在线免费观看国产视频| 国产裸体美女永久免费无遮挡| 亚洲国产精品久久66| 欧美男同gv免费网站观看| 国产精品亚洲综合网站| 国产精品亚洲综合专区片高清久久久| 国产麻豆一精品一AV一免费| 色噜噜亚洲男人的天堂| 亚洲一级特黄无码片| 日本免费人成在线网站|