python濃縮(8)條件和循環

8.1 if 語句

if 語句的語法以下:python

if expression:
    expr_true_suite

8.1.1 多重條件表達式

單個 if 語句能夠經過使用布爾操做符 and , or 和 not實現多重判斷條件或是否認判斷條件.程序員

if not warn and (system_load >= 10):
    print "WARNING: losing resources"

8.2 else 語句

if expression:
    expr_true_suite
else:
    expr_false_suite

8.2.1 避免「懸掛 else」

縮進避免了語法上正確的代碼中存在潛在的問ti. 其中一個就是"懸掛 else (dangling else)" 。express

Python 的縮進使用強制使代碼正確對齊, 決定 else 屬於哪個 if . 限制您的選擇從而減小了不肯定性。編程

8.3 elif (即 else-if )語句

if expression1:
    expr1_true_suite
elif expression2:
    expr2_true_suite
elif expressionN:
    exprN_true_suite
else:
    none_of_the_above_suite

switch/case 語句的替代品麼?服務器

未來, Python 可能會支持 switch /case , 可是能夠用其餘的 Python結構來模擬它. 網絡

if user.cmd == 'create':
    action = "create item"
elif user.cmd == 'delete':
    action = 'delete item'
elif user.cmd == 'update':
    action = 'update item'
else:
    action = 'invalid choice... try again!'

上面的語句徹底能夠知足咱們的須要,不過咱們還能夠用序列和成員關係操做符來簡化它:數據結構

if user.cmd in ('create', 'delete', 'update'):
    action = '%s item' % user.cmd
else:
    action = 'invalid choice... try again!'

能夠用 Python 字典給出更加優雅的解決方案, 將在第七章 "映射和集合類型"中介紹字典.less

msgs = {'create': 'create item','delete': 'delete item','update': 'update item'}
default = 'invalid choice... try again!'
action = msgs.get(user.cmd, default)

衆所周知, 使用映射對象(好比字典)的一個最大好處就是它的搜索操做比相似 if-elif-else語句或是 for 循環這樣的序列查詢要快不少.編程語言

8.4 條件表達式(即"三元操做符")

 X if C else Y .有了三元運算符後你就只須要一行完成條件判斷和賦值操做,在 2.5 之前的版本中,程序員最多這樣作(實際上是一個 hack ):函數式編程

>>> smaller = (x < y and [x] or [y])[0]
>>> smaller
3
```

在 2.5 和更新的版本中, 你可使用更簡明的條件表達式:

>>> smaller = x if x < y else y
>>> smaller
3

8.5 while 語句

8.5.1 通常語法

while expression:
    suite_to_repeat

8.5.2 計數循環

count = 0
while (count < 9):
    print 'the index is:', count
    count += 1

8.5.3 無限循環

"無限"的循環不必定是壞事, 許多通信服務器的客戶端/服務器系統就是經過它來工做的. 

while True:
    handle, indata = wait_for_client_connect()
    outdata = process_request(indata)
    ack_result_to_client(handle, outdata)

例如上邊的代碼就是故意被設置爲無限循環的. 這是由於服務器代碼是用來等待客戶端(可能經過網絡)來鏈接的. 客戶端向服務器發送請求, 服務器處理請求.請求被處理後, 服務器將向客戶端返回數據, 而此時客戶端可能斷開鏈接或是發送另外一個請求.對於服務器而言它已經完成了對這個客戶端的任務, 它會返回最外層循環等待下一個鏈接. 在第16 章, "網絡編程" 和第 17 章節 "Internet 客戶端編程" 裏你將瞭解關於如何處理客戶端/服務器的更多信息.

8.6 for 語句

它能夠遍歷序列成員, 能夠用在列表解析生成器表達式中, 它會自動地調用迭代器的next()方法, 捕獲 StopIteration 異常並結束循環(全部這一切都是在內部發生的).

8.6.1 通常語法

for 循環會訪問一個可迭代對象(例如序列或是迭代器)中的全部元素, 並在全部條目都處理事後結束循環. 它的語法以下:

for iter_var in iterable:
    suite_to_repeat

每次循環, iter_var 迭代變量被設置爲可迭代對象(序列, 迭代器, 或者是其餘支持迭代的對象)的當前元素

8.6.2 用於序列類型

本節中, 咱們將學習用 for 循環迭代不一樣的序列對象. 樣例將涵蓋字符串, 列表, 以及元組.

>>> for eachLetter in 'Names':
... print 'current letter:', eachLetter
...
current letter: N
current letter: a
current letter: m
current letter: e
current letter: s

迭代序列有三種基本方法:

(1)經過序列項迭代

>>> nameList = ['Walter', "Nicole", 'Steven', 'Henry']
>>> for eachName in nameList:
... print eachName, "Lim"
...
Walter Lim
Nicole Lim
Steven Lim
Henry Lim

===經過序列索引迭代===

(2)另個方法就是經過序列的索引來迭代:

>>> nameList = ['Cathy', "Terry", 'Joe', 'Heather','Lucy']
>>> for nameIndex in range(len(nameList)):
... print "Liu,", nameList[nameIndex]
...
Liu, Cathy
Liu, Terry
Liu, Joe
Liu, Heather
Liu, Lucy

這裏咱們使用了內建的 len() 函數得到序列長度, 使用 range() 函數(咱們將在下面詳細討論它)建立了要迭代的序列.

若是你對性能有所瞭解的話, 那麼毫無疑問你會意識到直接迭代序列要比經過索引迭代快. 若是你不明白, 那麼你能夠仔細想一想.

===使用項和索引迭代===

(3)一箭雙鵰的辦法是使用內建的 enumerate() 函數:

>>> nameList = ['Donn', 'Shirley', 'Ben', 'Janice',
... 'David', 'Yen', 'Wendy']
>>> for i, eachLee in enumerate(nameList):
... print "%d %s Lee" % (i+1, eachLee)
...
1 Donn Lee
2 Shirley Lee
3 Ben Lee
4 Janice Lee
5 David Lee
6 Yen Lee
7 Wendy Lee

8.6.3 用於迭代器類型

用 for 循環訪問迭代器和訪問序列的方法差很少. 惟一的區別就是 for 語句會爲你作一些額外的事情. 迭代器並不表明循環條目的集合.迭代器對象有一個 next() 方法, 調用後返回下一個條目. 全部條目迭代完後, 迭代器引起一個 StopIteration 異常告訴程序循環結束. for 語句在內部調用 next() 並捕獲異常.

使用迭代器作 for 循環的代碼與使用序列條目幾乎徹底相同. 事實上在大多狀況下, 你沒法分辨出你迭代的是一個序列仍是迭代器, 所以,這就是爲何咱們在說要遍歷一個迭代器時,實際上可能咱們指的是要遍歷一個序列,迭代器,或是一個支持迭代的對象(它有next()方法)。

8.6.4 range() 內建函數

=== range() 的完整語法===

Python 提供了兩種不一樣的方法來調用 range() . 完整語法要求提供兩個或三個整數參數:

range(start, end, step =1)

range() 會返回一個包含全部 k 的列表, 這裏 start <= k < end , 從 start 到 end , k 每次遞增 step . step 不能夠爲零,不然將發生錯誤.step默認爲1.

>>> range(2, 19, 3)
[2, 5, 8, 11, 14, 17]

===range() 簡略語法===

range() 還有兩種簡略的語法格式:

range(end)
range(start, end) # start 默認爲 0 , step 默認爲 1

核心筆記: 爲何 range() 不是隻有一種語法?

你已經知道了 range() 的全部語法, 有些人可能會問一個挑剔的問ti, 爲何不把這兩種語法合併成一個下面這樣的語法?

range(start=0, end, step =1) # 錯誤

這個語法不可使用兩個參數調用. 由於 step 要求給定 start . 換句話說, 你不能只傳遞end 和 step 參數. 由於它們會被解釋器誤認爲是 start 和 end .

8.6.5 xrange() 內建函數

xrange() 相似 range() , 不過當你有一個很大的範圍列表時, xrange() 可能更爲適合, 由於它不會在內存裏建立列表的完整拷貝. 它只被用在 for 循環中, 在 for 循環外使用它沒有意義。

一樣地, 你能夠想到, 它的性能遠高出 range(), 由於它不生成整個列表。在 Python 的未來版本中, range() 可能會像 xrange() 同樣, 返回一個可迭代對象(不是列表也不是一個迭代器) - 它會像前邊一章討論的那樣.

8.6.6 與序列相關的內建函數

sorted(), reversed(), enumerate(), zip()

下邊是使用循環相關和序列相關函數的例子. 爲何它們叫"序列相關"呢? 是由於其中兩個函數( sorted() 和 zip() )返回一個序列(列表), 而另外兩個函數( reversed() 和 enumerate() )返回迭代器(相似序列)

>>> albums = ('Poe', 'Gaudi', 'Freud', 'Poe2')
>>> years = (1976, 1987, 1990, 2003)
>>> for album in sorted(albums):
... print album,
...
Freud Gaudi Poe Poe2
>>> for album in reversed(albums):
... print album,
...
Poe2 Freud Gaudi Poe
>>> for i, album in enumerate(albums):
... print i, album
...
0 Poe
1 Gaudi
2 Freud
3 Poe2
>>> for album, yr in zip(albums, years):
... print yr, album
...

1976 Poe
1987 Gaudi
1990 Freud
2003 Poe2

8.7 break 語句

相似 C 中的傳統 break .經常使用在當某個外部條件被觸發(通常經過 if 語句檢查), 須要當即從循環中退出時. break 語句能夠用在 while 和 for 循環中.

8.8 continue 語句

核心筆記: continue 語句

當遇到continue 語句時, 程序會終止當前循環,並忽略剩餘的語句, 而後回到循環的頂端. 在開始下一次迭代前,若是是條件循環, 將驗證條件表達式.若是是迭代循環,將驗證是否還有元素能夠迭代. 只有在驗證成功的狀況下, 纔會開始下一次迭代.

while 循環是條件性的, 而 for 循環是迭代的, 因此 continue 在開始下一次循環前要知足一些先決條件, 不然循環會正常結束。

valid = False
count = 3
passwdList = ["admin1", "admin2", "admin3"]
while count > 0:
    inputPasswd = input("enter password")
    # check for valid passwd
    for eachPasswd in passwdList:
        if inputPasswd == eachPasswd:
            valid = True
            break
    if not valid:
        print ("invalid input")
        count -= 1
        continue
    else:
        break

驗證用戶輸入. 用戶有三次機會來輸入正確的密碼, 若是失敗, 那麼 valid 變量將仍爲一個布爾假( 0 ), 而後咱們能夠採起必要的操做阻止用戶猜想密碼.

8.9 pass 語句

Python 還提供了 pass 語句. 有些地方在語法上要求要有代碼, 而Python 中沒有對應的空大括號或是分號( ; )來表示C 語言中的 "不作任何事". 所以, Python 提供了 pass 語句, 它不作任何事情,pass 一樣也可做爲開發中的小技巧, 標記你後來要完成的代碼, 例如這樣:

def foo_func():
    pass
或是
if user_choice == 'do_calc':
    pass 
else:
    pass

這樣的代碼結構在開發和調試時頗有用, 由於編寫代碼的時候你可能要先把結構定下來,但你不但願它干擾其餘已經完成的代碼. 在不須要它作任何事情地方, 放一個pass 將是一個很好的主意.

另外它在異常處理中也被常常用到, 咱們將在第 10 章中詳細介紹; 好比你跟蹤到了一個非致命的錯誤, 不想採起任何措施(你只是想記錄一下事件或是在內部進行處理).

8.10 再談 else 語句

Python 和C語言不一樣,能夠在 while 和 for 循環中使用 else 語句. 它們是怎麼工做的呢? 在循環中使用時, else子句只在循環完成後執行, 也就是說 break 語句也會跳過 else 塊.

(maxFact.py) 利用這個語法完成了 showMaxFactor() 函數.這個程序顯示出 10 到 20 中的數字的最大約數. 該腳本也會提示這個數是否爲素數.

def showMaxFactor(num):
    count = num/2
    while count > 1:
        if num%count == 0:
            print ("larger factor of %d is %d" % (num,count))
            break
        count-=1
    else:
        print (num, "is prime")

for eachNum in range(10, 21):
    showMaxFactor(eachNum)

一樣地, for 循環也能夠有 else 用於循環後處理. 它和 while 循環中的else 處理方式相同. 只要for 循環是正常結束的(不是經過 break ), else 子句就會執行.

表 8.1 條件及循環語句中的輔助語句總結

a. pass 在任何須要語句塊(一個或多個語句)的地方均可以使用(例如 elif , else , clasa ,def , try , except , finally ).

8.11 迭代器和 iter() 函數

8.11.1 什麼是迭代器?

爲類序列對象提供了一個類序列的接口. 咱們已經介紹過序列. 它們是一組數據結構。Python 的迭代無縫地支持序列對象, 並且它還容許程序員迭代非序列類型, 包括用戶定義的對象.迭代器用起來很靈巧, 你能夠迭代不是序列但表現出序列行爲的對象, 例如字典的 key , 一個文件的行, 等等. 當你使用循環迭代一個對象條目時, 你幾乎不可能分辨出它是迭代器仍是序列.你沒必要去關注這些, 由於 Python 讓它象一個序列那樣操做.

8.11.2 爲何要迭代器?

援引 PEP (234) 中對迭代器的定義:

.. 提供了可擴展的迭代器接口.

.. 對列表迭代帶來了性能上的加強.

.. 在字典迭代中性能提高.

.. 建立真正的迭代接口, 而不是原來的隨機對象訪問.

.. 與全部已經存在的用戶定義的類以及擴展的模擬序列和映射的對象向後兼容

.. 迭代非序列集合(例如映射和文件)時, 能夠建立更簡潔可讀的代碼.

8.11.3 如何迭代?

根本上說, 迭代器就是有一個 next() 方法的對象, 而不是經過索引來計數. 當你或是一個循環機制(例如 for 語句)須要下一個項時, 調用迭代器的 next() 方法就能夠得到它. 條目所有取出後, 會引起一個 StopIteration 異常, 這並不表示錯誤發生, 只是告訴外部調用者, 迭代完成.不過, 迭代器也有一些限制. 例如你不能向後移動, 不能回到開始, 也不能複製一個迭代器.若是你要再次(或者是同時)迭代同個對象, 你只能去建立另外一個迭代器對象. 不過, 這並不糟糕,由於還有其餘的工具來幫助你使用迭代器.

reversed() 內建函數將返回一個反序訪問的迭代器. enumerate() 內建函數一樣也返回迭代器.另外兩個新的內建函數, any() 和 all() , 若是迭代器中某個/全部條目的值都爲布爾真時,則它們返回值爲真. 本章先前部分咱們展現瞭如何在 for 循環中經過索引或是可迭代對象來遍歷條目. 同時 Python 還提供了一整個 itertools 模塊, 它包含各類有用的迭代器.

8.11.4 使用迭代器

===序列===

正如先前提到的, 迭代 Python 的序列對象和你想像的同樣:

>>> myTuple = (123, 'xyz', 45.67)
>>> i = iter(myTuple)
>>> i.next()
123
>>> i.next()
'xyz'
>>> i.next()
45.67
>>> i.next()
Traceback (most recent call last):
File "", line 1, in ?
StopIteration

若是這是一個實際應用程序, 那麼咱們須要把代碼放在一個 try-except 塊中. 序列如今會自動地產生它們本身的迭代器, 因此一個 for 循環:

for i in seq:
    do_something_to(i)

其實是這樣工做的:

fetch = iter(seq)
while True:
    try:
        i = fetch.next()
    except StopIteration:
        break
    do_something_to(i)

不過, 你不須要改動你的代碼, 由於 for 循環會自動調用迭代器的 next() 方法(以及監視StopIteration 異常).

===字典===

字典和文件是另外兩個可迭代的 Python 數據類型. 字典的迭代器會遍歷它的鍵(keys).語句 for eachKey in myDict.keys() 能夠縮寫爲 for eachKey in myDict , 例如:

>>> legends = { ('Poe', 'author'): (1809, 1849, 1976),... ('Gaudi', 'architect'): (1852, 1906, 1987),... ('Freud', 'psychoanalyst'): (1856, 1939, 1990)... }
...
>>> for eachLegend in legends:
... print 'Name: %s\tOccupation: %s' % eachLegend
... print ' Birth: %s\tDeath: %s\tAlbum: %s\n' % legends[eachLegend]
...
Name: Freud Occupation: psychoanalyst
Birth: 1856 Death: 1939 Album: 1990
Name: Poe Occupation: author
Birth: 1809 Death: 1849 Album: 1976
Name: Gaudi Occupation: architect
Birth: 1852 Death: 1906 Album: 1987

另外, Python 還引進了三個新的內建字典方法來定義迭代: myDict.iterkeys() (經過 keys 迭代), myDict.itervalues() (經過 values 迭代), 以及 myDicit.iteritems() (經過 key/value 對來迭代). 注意, in 操做符也能夠用於檢查字典的 key 是否存在 , 以前的布爾表達式myDict.has_key(anyKey) 能夠被簡寫爲 anyKey in myDict .

===文件===

文件對象生成的迭代器會自動調用 readline() 方法. 這樣, 循環就能夠訪問文本文件的全部行. 程序員可使用 更簡單的 for eachLine in myFile 替換 for eachLine in myFile.readlines() :

>>> myFile = open('config-win.txt')
>>> for eachLine in myFile:
... print eachLine, # comma suppresses extra \n
...
[EditorWindow]
font-name: courier new
font-size: 10
>>> myFile.close()

8.11.5 可變對象和迭代器

記住,在迭代可變對象的時候修改它們並非個好主意. 這在迭代器出現以前就是一個問題.一個流行的例子就是循環列表的時候刪除知足(或不知足)特定條件的項:

for eachURL in allURLs:
    if not eachURL.startswith('http://'):
        allURLs.remove(eachURL) # YIKES!!

除列表外的其餘序列都是不可變的, 因此危險就發生在這裏. 一個序列的迭代器只是記錄你當前到達第多少個元素, 因此若是你在迭代時改變了元素, 更新會當即反映到你所迭代的條目上.在迭代字典的 key 時, 你絕對不能改變這個字典. 使用字典的 keys() 方法是能夠的, 由於keys() 返回一個獨立於字典的列表. 而迭代器是與實際對象綁定在一塊兒的, 它將不會繼續執行下去:

>>> myDict = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> for eachKey in myDict:
...     print eachKey, myDict[eachKey]
...     del myDict[eachKey]
... a 1
Traceback (most recent call last):
File "", line 1, in ?
RuntimeError: dictionary changed size during iteration

這樣能夠避免有缺陷的代碼. 更多有關迭代器的細節請參閱 PEP 234 .

8.11.6 如何建立迭代器

對一個對象調用 iter() 就能夠獲得它的迭代器. 它的語法以下:

iter(obj)
iter(func, sentinel )

若是你傳遞一個參數給 iter() , 它會檢查你傳遞的是否是一個序列, 若是是, 那麼很簡單:根據索引從 0 一直迭代到序列結束. 另外一個建立迭代器的方法是使用類, 咱們將在第 13 章詳細介紹, 一個實現了 __iter__() 和 next() 方法的類能夠做爲迭代器使用.若是是傳遞兩個參數給 iter() , 它會重複地調用 func , 直到迭代器的下個值等於sentinel .

8.12 列表解析

列表解析來自函數式編程語言 Haskell . 能夠用來動態地建立列表. 在第 11 章, 函數中, 咱們函數式編程特性, 例如 lambda , map() ,以及 filter() 等。

經過列表解析 , 它們能夠被簡化爲一個列表解析式子. map() 對全部的列表成員應用一個操做, filter() 基於一個條件表達式過濾列表成員. 最後, lambda 容許你快速地建立只有一行的函數對象:

[expr for iter_var in iterable]

這個語句的核心是 for 循環, 它迭代 iterable 對象的全部條目. 前邊的 expr 應用於序列的每一個成員, 最後的結果值是該表達式產生的列表. 

計算序列成員的平方的 lambda 函數表達式:

>>> map(lambda x: x ** 2, range(6))
[0, 1, 4, 9, 16, 25]

咱們可使用下面這樣的列表解析來替換它:

>>> [x ** 2 for x in range(6)] # 你也能夠用括號包住表達式, 像 [(x ** 2) for x in range(6)]
[0, 1, 4, 9, 16, 25]

結合if語句,列表解析還提供了一個擴展版本的語法:

[expr for iter_var in iterable if cond_expr]
# 這個語法在迭代時會過濾/捕獲知足條件表達式 cond_expr 的序列成員.

判斷一個數值對象是奇數仍是偶數(奇數返回 1 , 偶數返回 0 ), 使用 filter() 和 lambda 挑選出序列中的奇數:

>>> seq = [11, 10, 9, 9, 10, 10, 9, 8, 23, 9, 7, 18, 12, 11, 12]
>>> filter(lambda x: x % 2, seq)
[11, 9, 9, 9, 23, 9, 7, 11]

使用列表解析,得到想要的數字:

>>> [x for x in seq if x % 2]
[11, 9, 9, 9, 23, 9, 7, 11]

===矩陣樣例===

你須要迭代一個有三行五列的矩陣麼? 很簡單:

>>> [(x+1,y+1) for x in range(3) for y in range(5)]

[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2,

3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5)]

===磁盤文件樣例===

假設咱們有以下這樣一個數據文件 hhga.txt , 須要計算出全部非空白字符的數目:

And the Lord spake, saying, "First shalt thou take out the Holy Pin. Then shalt thou count to three, no more, no less. Three shall be the number thou shalt count, and the number of the counting shall be three. Four shalt thou not count, nei- ther count thou two, excepting that thou then proceed to three. Five is right out. Once the number three, being the third number, be reached, then lobbest thou thy Holy Hand Grenade of Antioch towards thy foe, who, being naughty in My sight, shall snuff it."

咱們已經知道能夠經過 for line in data 迭代文件內容, 咱們還能夠把每行分割( split )爲單詞:

>>> f = open('hhga.txt', 'r')
>>> len([word for line in f for word in line.split()])
91

快速地計算文件大小

import os
>>> os.stat('hhga.txt').st_size
499L

假定文件中至少有一個空白字符, 咱們知道文件中有少於 499 個非空字符. 咱們能夠把每一個單詞的長度加起來, 獲得和.

>>> f.seek(0)
>>> sum([len(word) for line in f for word in line.split()])
408

這裏咱們用 seek() 函數回到文件的開頭, 由於迭代器已經訪問完了文件的全部行. 一個清晰明瞭的列表解析完成了以前須要許多行代碼才能完成的工做! 如你所見, 列表解析支持多重嵌套for 循環以及多個 if 子句. 完整的語法能夠在官方文檔中找到. 你也能夠在 PEP 202 中找到更多關於列表解析的資料.

8.13 生成器表達式

生成器表達式是列表解析的一個擴展. 生成器是特定的函數, 容許你返回一個值, 而後"暫停"代碼的執行, 稍後恢復. 咱們將在第 11 章中討論生成器.列表解析的一個不足就是必需要生成全部的數據, 用以建立整個列表. 這可能對有大量數據的迭代器有負面效應. 生成器表達式經過結合列表解析和生成器解決了這個問ti.生成器表達式與列表解析很是類似,並且它們的基本語法基本相同;

不過它並不真正建立數字列表, 而是返回一個生成器,這個生成器在每次計算出一個條目後,把這個條目「產生」(yield)出來. 生成器表達式使用了"延遲計算"(lazy evaluation), 因此它在使用內存上更有效. 咱們來看看它和列表解析到底有多類似:

列表解析:
[expr for iter_var in iterable if cond_expr]
生成器表達式:
(expr for iter_var in iterable if cond_expr)

生成器並不會讓列表解析廢棄, 它只是一個內存使用更友好的結構, 基於此, 有不少使用生成器地方. 下面咱們提供了一些使用生成器表達式的例子。

===磁盤文件樣例===

 計算文本文件中非空白字符總和例子中,若是這個文件的大小變得很大, 那麼這行代碼的內存性能會很低, 由於咱們要建立一個很長的列表用於存放單詞的長度.爲了不建立龐大的列表, 咱們可使用生成器表達式來完成求和操做. 它會計算每一個單詞的長度而後傳遞給 sum() 函數(它的參數不只能夠是列表,還能夠是可迭代對象,好比生成器表達式).

這樣, 咱們能夠獲得優化後的代碼(代碼長度, 還有執行效率都很高效):

>>> sum(len(word) for line in data for word in line.split())
408

咱們所作的只是把方括號刪除: 少了兩字節, 並且更節省內存 ... 很是地環保!

=== 交叉配對例子 ===

生成器表達式就好像是懶惰的列表解析. 它還能夠用來處理其餘列表或生成器, 例如這裏的 rows 和 cols :

rows = [1, 2, 3, 17]
def cols(): 
    yield 56
    yield 2
    yield 1

不須要建立新的列表, 直接就能夠建立配對. 咱們可使用下面的生成器表達式:

x_product_pairs = ((i, j) for i in rows for j in cols())
如今咱們能夠循環 x_product_pairs , 它會懶惰地循環 rows 和 cols :
>>> for pair in x_product_pairs:
... print pair
...
(1, 56)
(1, 2)
(1, 1)
(2, 56)
(2, 2)
(2, 1)
(3, 56)
(3, 2)
(3, 1)
(17, 56)
(17, 2)
(17, 1)

=== 重構樣例 ===

咱們經過一個尋找文件最長的行的例子來看看如何改進代碼. 在之前, 咱們這樣讀取文件:

f = open('AAA', 'r')
longest = 0
while True:
    linelen = len(f.readline().strip())
    if not linelen:
        break
    if linelen > longest:
        longest = linelen
f.close()
return longest

從那時起, 咱們認識到若是讀取了全部的行, 那麼應該儘早釋放文件資源. 若是這是一個不少進程都要用到的日誌文件, 那麼理所固然咱們不能一直拿着它的句柄不釋放. 是的, 咱們的例子是用來展現的, 可是你應該獲得這個理念. 因此讀取文件的行的首選方法應該是這樣:

f = open('/etc/motd', 'r')
longest = 0
allLines = f.readlines()
f.close()
for line in allLines:
    linelen = len(line.strip())
    if linelen > longest:
        longest = linelen
return longest

列表解析容許咱們稍微簡化咱們代碼, 並且咱們能夠在獲得行的集合前作必定的處理. 在下段代碼中, 除了讀取文件中的行以外,咱們還調用了字符串的 strip() 方法處理行內容.

f = open('/etc/motd', 'r')
longest = 0
allLines = [x.strip() for x in f.readlines()]
f.close()
for line in allLines:
    linelen = len(line)
    if linelen > longest:
        longest = linelen
return longest

在處理大文件時候都有問ti, 由於 readlines() 會讀取文件的全部行. 後來咱們有了迭代器, 文件自己就成爲了它本身的迭代器, 不須要調用 readlines() 函數. 咱們已經作到了這一步, 爲何不去直接得到行長度的集合呢(以前咱們獲得的是行的集合)? 這樣, 咱們就可使用 max() 內建函數獲得最長的字符串長度:

f = open('/etc/motd', 'r')
allLineLens = [len(x.strip()) for x in f]
f.close()
return max(allLineLens)

你一行一行迭代 f 的時候, 列表解析須要文件的全部行讀取到內存中,而後生成列表. 咱們能夠進一步簡化代碼: 使用生成器表達式替換列表解析, 而後把它移到 max()函數裏, 這樣, 全部的核心部分只有一行:

f = open('/etc/motd', 'r')
longest = max(len(x.strip()) for x in f)
f.close()
return longest

最後, 咱們能夠去掉文件打開模式(默認爲讀取), 而後讓 Python 去處理打開的文件.

return max(len(x.strip()) for x in open('/etc/motd'))

生成器表達式在 Python 2.4 中被加入, 你能夠在 PEP 289 中找到更多相關內容.

8.14 R 相關模塊

Python 2.2 引進了迭代器, 在下一個發行 (2.3) 中, itertools 模塊被加入, 用來幫助那些發現迭代器威力但又須要一些輔助工具的開發者. 有趣的是若是你閱讀關於 itertools 中實用程序的文檔, 你會發現生成器. 因此在迭代器和生成器間有必定的聯繫. 你能夠在第 11 章 "函數"中瞭解更多.

相關文章
相關標籤/搜索