Python回顧與整理6:條件和循環

0.說明python


        說起的內容爲:if、while、for及與他們相搭配的else、elif、break、continue和pass語句。
算法




1.if語句express


  • 語法緩存

if expression:
        expr_true_suite

        其中對於expression部分可使用邏輯鏈接詞an、or和not來實現多重判斷條件。數據結構

  • 單一語句的代碼塊ide

        即若是隻有一個語句執行時,能夠寫成下面這樣:
函數

if True: print 'OK'

        但仍是建議寫在不一樣的行。
工具




2.else語句性能


  • 語法測試

if expression:
        expr_true_suite
else:
        expr_false_suite
  • 避免「懸掛else」

        像C語言這種使用括號來分隔代碼塊的語言會出現這種狀況(即在有多個if語句蛙,else語句不知道是屬於哪個if語句的),但因爲Python是強制使用縮進來使代碼對齊的,所以不可能會出現這種問題。




3.elif語句


  • 語法

if expression1:
        expr1_true_suite
elif expression2:
        expr2_true_suite
elif expression3:
        expr3_true_suite
        ……
elif expressionN:
        expr1_true_suite
else:
        none_of_the_above_suite

        能夠有任意數量的elif語句,但只能有一個else語句。

  • switch/case語句的替代品

        在C語言中有switch/case語句用於在多個選項中進行選擇,Python雖然沒有switch/case語句,但卻有更好的解決方案來實現一樣的功能:

#方案一:大量的if-elif語句
if user.cmd == 'create':
        action = 'create item'
elif user.cmd == 'delete':
        action = 'delete item'        
elif user.cmd == 'update':
        action = 'update item'        
        
#方案二:用序列和成員關係操做符
if user.cmd in ('create', 'delete', 'update'):
        action = '%s item' % user.cmd
else:
        action = 'invalid choice... try again!'      
        
#方案三:使用字典
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循環這樣的序列查詢要快不少

        能夠看到,對於實現一樣的功能,Python中的解決方案更增強大和簡潔。




4.條件表達式(三元操做符)


  • 語法:X if C else Y

        使用以下:

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




5.while語句


  • 語法

while expression:
        suite_to_repeat
  • 計數循環

count = 0
while (count < 9):
        print 'the index is: ',count
        count += 1
  • 無限循環

while True:
        suite_to_repeat

        無限循環主要是用在Server/Client模式中Server端的設計,用以等待來自客戶端的鏈接,固然若是有必要的話也是能夠經過break語句來結束循環的。




6.for語句


        for語句提供了Python中最強大的循環結構,它能夠遍歷序列成員,能夠用在列表解析和生成器表達式中,它會自動地調用迭代器的next()方法,捕獲StopIteration異常並結束循環(這一切都是在內部發生的)。


(1)語法

for iter_var in iterable:
        suite_to_repeat

        每次循環,iter_var撫迭代變量被設置爲可迭代對象iterable(序列、迭代器或其餘支持迭代的對象)的當前元素,提供給suite_to_repeat語句使用。


(2)用於序列類型

        主要是下面用於序列迭代的三種方法:

  • 經過序列項迭代

>>> nameList = ['xpleaf', 'clyyh', 'cl']
>>> for name in nameList:
...   print name
... 
xpleaf
clyyh
cl
  • 經過序列索引迭代

>>> nameList = ['xpleaf', 'clyyh', 'cl']
>>> for nameIndex in range(len(nameList)):
...   print nameList[nameIndex]
... 
xpleaf
clyyh
cl

        顯然會比第一種方法慢不少。

  • 使用項和索引迭代

>>> nameList = ['xpleaf', 'clyyh', 'cl']
>>> for nameIndex, name in enumerate(nameList):
...   print nameIndex, name
... 
0 xpleaf
1 clyyh
2 cl


(3)用於迭代器類型

        for語句用於迭代器時,會自動幫咱們處理不少問題(在內部調用next()並處理StopIteration異常),不過須要注意的是,迭代器並不表明循環條目的集合。


(4)range()內建函數

        range()能夠用來生成一個數字順序列表,從而可使用for來進行迭代,它主要有如下兩種語法:

  • 完整語法

        以下:

range(start, end, step=1)

        即默認步長爲1,舉例以下:

>>> range(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(1, 10, 3)
[1, 4, 7]

        可是步長不能爲0,不然會出錯:

>>> range(1, 10, 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: range() step argument must not be zero
  • 簡單語法

range(end)    #即默認start=1
range(start, end)    #其實就是默認步長爲1的狀況

        舉例以下:

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(0, 10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


(5)xrange()內建函數

        當調用range()時,會直接生成一個列表並寫入內存中,但當生成的列表很大時,使用range()就不適合了,這時能夠考慮能夠xrange(),它返回一個可迭代的對象,而並不會生成一個完整的列表,所以它只被用於for循環中,不然是沒有意義的:

>>> xrange(10)
xrange(10)
>>> type(xrange(10))
<type 'xrange'>
>>> for num in xrange(10):
...   print num,
... 
0 1 2 3 4 5 6 7 8 9


(6)與序列相關的內建函數

        主要是:sorted()reversed()enumerate()zip()

        之因此說是與序列相關,是由於其中兩個函數(sorted()和zip())返回一個序列(列表),而另外兩個函數(reversed()和enumerate())返回迭代器,舉例以下:

  • sorted():返回一個列表

>>> nameTuple = ('xpleaf', 'cl')
>>> years = (1994, 1995)
>>> sorted(nameTuple)
['cl', 'xpleaf']
>>> for name in sorted(nameTuple):
...   print name,
... 
cl xpleaf
  • reversed():返回一個迭代器

>>> reversed(nameTuple)
<reversed object at 0x7f2fa02df390>
>>> for name in reversed(nameTuple):
...   print name,
... 
cl xpleaf
  • enumerate():返回一個迭代器

>>> enumerate(nameTuple)
<enumerate object at 0x7f2f9e1c1d20>
>>> for nameIndex, name in enumerate(nameTuple):
...   print nameIndex, name
... 
0 xpleaf
1 cl
  • zip():返回一個列表

>>> zip(nameTuple, years)
[('xpleaf', 1994), ('cl', 1995)]
>>> for name, year in zip(nameTuple, years):
...   print name, year
... 
xpleaf 1994
cl 1995




7.break語句

        

        break能夠結束當前循環而後跳轉到下一條語句(若是存在的話),可用於while和for兩各循環中:

>>> count = 0
>>> while True:
...   if count>10:
...     print 'OK'
...     break
...   count += 1
... 
OK




8.continue語句


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

        對在Python中,while循環是條件性的,for循環是迭代的,這就能夠跟上面的兩種狀況對應起來,可簡單舉例以下:

>>> count = 0
>>> while count<10:
...   count += 1
...   if count != 6:
...     continue
...   print count
... 
6
>>>
>>> for count in range(10):
...   if count != 6:
...     continue
...   print count
... 
6




9.pass語句


        主要是下面幾個方面會用到pass語句:

  • 有些地方在語法上要求有代碼

  • 標記後來要完成的代碼(好比先定義個類或函數,但什麼也不作)

  • 在異常處理中對於影響不大的異常能夠直接pass




10.再談else語句


        在Python中,除了在條件語句中使用else外,還能夠在while和for循環中使用else語句。當在循環中使用時,else子句只在循環完成後執行,即若是此時循環中存在break語句就會跳過該else語句。

        舉例以下:尋找一個數的最大約數

#!/usr/bin/env python


def showMaxFactor(num):
    count = num / 2  #從一半開始計數,這樣就能夠檢查這個數是否能夠被2整除,若是能夠,那就找到了最大的約數
    while count > 1:
        if num % count == 0:
            print 'largest factor of %d is %d' % (num, count)
            break
        count -= 1
    else:
        print num, 'is prime'

if __name__ == '__main__':
    showMaxFactor(10)
    showMaxFactor(31)

        這是是一個很是好的特性,能夠考慮一下,若是沒有該特性,要實現上面的功能,在邏輯上就確定沒有那麼清晰了。




11.迭代器和iter()函數


(1)什麼是迭代器

        所謂迭代器,其實就是一個有next()方法的Python對象,由於它爲類序列對象提供了一個類序列的接口,因此可使用for循環進行迭代(其實for循環在進行序列迭代時是先把序列轉換爲迭代器再進行迭代的,這是序列自己的功能,之因此會建立迭代器,是由於在序列中實現了__iter__()方法),因此只要是表現出序列行爲的對象(可迭代對象),均可以使用迭代器進行迭代。


(2)爲何要迭代器

        主要是理解下面幾點:

  • 對列表迭代帶來了性能上的加強(節省內存空間)

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


(3)如何迭代

        迭代器就是一個有next()方法的對象,而不是經過索引來計數。當你或是一個循環機制(好比for)須要下一個項時,調用迭代器的next()方法就能夠得到它,當所有項獲取後會有一個StopIteration異常,表示迭代已經完成。前面的reversed()和enumerate()函數返回的就是一個迭代器。

        固然迭代器也是有一些限制的,即不能向後移動、不能複製一個迭代器,當須要從新迭代同個對象時,就須要去建立另外一個迭代對象(不過迭代器自己有seek()方法能夠幫咱們完成這些事情)。


(4)使用迭代器

  • 序列

        以下:

>>> nameTuple = ('xpleaf', 'clyyh', 'cl')
>>> i = iter(myTuple)
>>> i.next()
'xpleaf'
>>> i.next()
'clyyh'
>>> i.next()
'cl'
>>> i.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

        實際上,序列會自動地建立它們本身的迭代器,因此一個for循環:

>>> nameTuple = ('xpleaf', 'clyyh', 'cl')
>>> for name in nameTuple:
...   print name,
... 
xpleaf clyyh cl

        其實是這樣工做的:

>>> fetch = iter(nameTuple)
>>> while True:
...   try:
...     i = fetch.next()
...   except StopIteration:
...     break
...   print i,
... 
xpleaf clyyh cl

        即會先建立該序列類型的一個迭代器,而後調用迭代器的next()方法進行迭代。由於序列自己只是爲咱們建立了迭代器而已,因此for循環語句爲咱們完成了下面兩件事:

(a)自動調用迭代器的next()方法

(b)監視StopIteration異常

  • 字典

        字典的迭代器會遍歷它的鍵,語句for eachKey in dict.keys()就能夠縮寫爲for eachKey in dict,以下:

>>> myDict = {'name': 'xpleaf', 'loving': 'cl'}
>>> for key in myDict.keys():
...   print key,
... 
name loving
>>>
>>> for key in myDict:
...   print key,
... 
name loving

        而根據前面的理論知識,能夠考慮它們是這樣工做的(實際上可能不是):

>>> fetch = iter(myDict)
>>> while True:
...   try:
...     i = fetch.next()
...   except StopIteration:
...     break
...   print i,
... 
name loving
  • 文件

        文件對象生成的迭代器會自動調用readline()方法,這樣循環就能夠訪問本文文件的全部行,便可以使用for eachLine in file來替換for eachLine in file.readlines,以下:

>>> f = open('welcome.txt', 'r')
>>> lines = f.readlines()
>>> lines
['Welcome to GDUT!\n', "Haha, I'm xpleaf, and I love Python programming!\n", 'Also, I love cl.\n']
>>> for line in lines:
...   print line,
... 
Welcome to GDUT!
Haha, I'm xpleaf, and I love Python programming!
Also, I love cl.
>>> 
>>> f = open('welcome.txt', 'r')
>>> for line in f:
...   print line,
... 
Welcome to GDUT!
Haha, I'm xpleaf, and I love Python programming!
Also, I love cl.

        根據前面的理論知識,實際上它們是這樣工做的:

>>> f = open('welcome.txt', 'r')
>>> fetch = iter(f.xreadlines())
>>> while True:
...   try:
...     i = fetch.next()
...   except StopIteration:
...     break
...   print i,
... 
Welcome to GDUT!
Haha, I'm xpleaf, and I love Python programming!
Also, I love cl.
# 猜想使用的是xreadlines()而不是readlines(),是由於readlines()返回的是一個完整的列表,這會佔用
# 大量的內存空間,顯然是不正確的,而xreadlines()返回的是一個迭代器。

        固然,由於文件對象自己就是一個迭代器,能夠直接調用它的next()方法來實現上面的功能:

>>> f = open('welcome.txt', 'r')
>>> while True:
...   try:
...     i = f.next()
...   except StopIteration:
...     break
...   print i,
... 
Welcome to GDUT!
Haha, I'm xpleaf, and I love Python programming!
Also, I love cl.


(5)可變對象和迭代器

        不建議在迭代可變對象的時候修改它們,好比一個可變的序列列表,由於一個序列的迭代器只是記錄你當前到達第幾個元素,因此若是在迭代時改變了元素(或刪除了元素),更新會當即反映到所迭代的條目上(序列上),以下:

>>> nameList = ['xpleaf', 'clyyh', 'cl']
>>> for name in nameList:
...   if name == 'xpleaf':
...     nameList.remove(name)
...   print name,
... 
xpleaf cl
>>> nameList
['clyyh', 'cl']
# 顯然一不注意,就會出現問題,由於更新是實時的,而迭代器只是記錄了當前位置,因此會看到有些問題,clyyh並無輸出
>>> nameList = ['xpleaf', 'clyyh', 'cl']
>>> for name in nameList:
...   if name == 'xpleaf':
...     nameList[nameList.index(name)] = 'yonghaoye'
...     print nameList
... 
['yonghaoye', 'clyyh', 'cl']

        須要注意的是,在迭代字典時,若是改變這個字典,就會出現問題:

>>> myDict = {'name': 'xpleaf', 'loving': 'cl'}
>>> for key in myDict:
...   print key, myDict[key]
...   if key == 'name':
...     myDict[key] = 'yonghaoye'
... 
name xpleaf
loving cl
# 只是改變值是沒有問題的
>>> myDict
{'name': 'yonghaoye', 'loving': 'cl'}
>>> 
>>> for key in myDict:
...   print key, myDict[key]
...   if key == 'name':
...     del myDict[key]
... 
name yonghaoye
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration
# 可是若是刪除字典中的一個鍵值對就會出現問題

        前面在迭代列表時刪除列表的元素也是能夠的,可是在迭代字典時刪除字典的鍵值對就會有問題,注意二者的迭代對象是不同的,前者列表迭代器,然後者是字典鍵迭代器,考慮到字典其自己特殊的存儲方式,這也是能夠理解的,固然要深刻研究的話,想必須要查看Python的源代碼了。

        因此在須要迭代時去刪除字典的鍵值對,可使用字典的keys()方法,由於keys()返回一個獨立於字典的列表。


(6)如何建立迭代器

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

  • iter(obj)

  • iter(func, sentinel)

        當傳遞一個參數給iter(),它會檢查你傳遞的是否是一個序列,若是是,那麼很簡單:根據索引從0一直迭代到序列結束(可是字典則不同)。固然也可使用類的方法,一個實現了__iter__和next()方法的類能夠做爲迭代器使用。

        若是是傳遞兩個參數給iter(),它會重複地調用func,直到迭代器的下個值等於sentinel。




12.列表解析


        列表解析(List comprehensions或list comps)能夠用來動態地建立列表,它主要有如下兩種語法形式:

  • 簡單語法

[expr for iter_var in iterable]

        舉例以下:

>>> [x ** 2 for x in range(6)]
[0, 1, 4, 9, 16, 25]

        若是不用列表解析建立相同的列表,就會有些麻煩:

>>> map(lambda x: x ** 2, range(6))
[0, 1, 4, 9, 16, 25]
# map()對全部的列表成員應用一個操做,lambda用於快速地建立只有一行的函數對象
  • 擴展語法

[expr for iter_var in iterable if cond_expr]

        舉例以下:

# 挑選序列中的奇數
>>> seq = [11, 10, 9, 9, 10, 10, 9, 8, 23, 9, 7, 18, 12, 11, 12]
>>> [x for x in seq if x % 2]
[11, 9, 9, 9, 23, 9, 7, 11]
# 注意這裏使用的方法`if x % 2`,這是判斷廳偶數的一個很是好的方法

        若是不用列表解析,則也會挺麻煩:

>>> filter(lambda x:x % 2, seq)
[11, 9, 9, 9, 23, 9, 7, 11]
# filter()基於一個條件表達式過濾列表成員

        顯然會看到使用列表解析就會方便不少,也有下面的相關例子:

  • 矩陣樣例

        好比要迭代一個3行5列的矩陣,可使用下面的算法:

>>> [(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)]
  • 磁盤文件樣例

        計算文件大小的,算法以下:

sum([len(word) for line in f for word in line.split()])

        上面兩個例子其實都是嵌套循環,從中也能夠看到列表解析的強大之處。




13.生成器表達式


        生成器是特定的函數,容許你返回一個值,而後「暫停」代碼的執行,稍後恢復,關於生成器在後面中會有說起,這裏介紹的是生成器表達式。

        列表解析的一個不足就是必要生成全部的數據,用以建立整個列表,這可能對有大量數據的迭代器有負面影響(佔用大量的內存),生成器表達式經過結合列表解析和生成器解決了這個問題。

        所謂生成器表達式,它並不真正建立數字列表,而是返回一個生成器,這個生成器在每次計算出一個條目後,把這個條目「產生」(yield)出來。即生成器表達式使用了「延遲計算」(lazy evaluation),因此它在使用內存上更有效(好比你想開發一個ftp軟件,這就很重要了,由於能夠大大節省內存空間)。

        其語法與列表解析對好比下:

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

        固然,生成器並不會讓列表解析廢棄,它只是一個內存使用更友好的結構。能夠看下面的應用例子。


(1)磁盤文件樣例

        前面計算文本文件的大小,使用的是下面的方法:

sum([len(word) for line in f for word in line.split()])

        這相關因而把f文件中的全部單詞都讀取出來,而後放入到列表中,從而生成該文件的一個完整單詞列表保存在內存中,對於比較小的文件,這是不錯的方法,但若是這個文件自己的大小就已經超出了內存空間的大小,這樣你的主機確定就會崩潰了。所以使用生成器表達式就能夠解決這個問題:

sum(len(word) for line in f for word in line.split())
# 注意sum的參數不只能夠是列表,還能夠是可迭代對象,好比生成器表達式

        實際使用以下:

>>> f = open('welcome.txt', 'r')
>>> sum(len(word) for line in f for word in line.split())
68
# 固然也能夠計算單詞個數
>>> f = open('welcome.txt', 'r')
>>> for word in (word for line in f for word in line.split()):
...   count += 1
>>> count
15


(2)交叉配對樣例

        生成器表達式就好像是懶惰的列表解析,由於它不會先去產生一個計算結果,直接你須要使用時它纔會去生產,一個交叉配對的例子以下:

>>> 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
<generator object <genexpr> at 0x7feecdd441e0>
>>> 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)


(3)重構樣例

        主要是考慮內存的節省問題,由於在處理大文件時用普通的迭代方式就須要把文件內容所有讀入到內存中,這是須要很是多的內存的,而使用生成器表達式就能夠緩解這個問題,以下面一個計算文件最長行字符數的例子:

>>> f = open('welcome.txt', 'r')
>>> longest = max(len(x.strip()) for x in f)
>>> f.close()
>>> longest
48
# 這裏改進後的例子,原來是使用列表解析,會佔用大量的內存空間,而使用生成器表達式則不會

        固然,重構過程的一個思想方法也是很是不錯的,能夠參考書上的例子。

        另外,屢次提到讀取文件時佔用內存的狀況,這主要是指不要嘗試把一個大文件保存爲Python的數據結構,不然會佔用大量的內存,使用迭代器自己是能夠節省內存的,以下面的一個測試,big_file的大小爲1.4GB:

>>> f = open('big_file', 'r')
>>> f2 = open('new_file', 'w')
>>> for line in f:
...   f2.write(line)
... 
>>> f.close()
>>> f2.close()

        用for line in file的方式來讀取文件時,其實用的就是迭代器的方式,前面已經有提過,這裏的測試結果是,在文件複製的過程當中,內存的佔用率並無太大的變化,說明Python解釋器每處理一行數據都會將原來緩存在內存中的一行數據刪除掉(垃圾回收機制),而後再去讀取另一行,這樣會得到比較高的性能,而若是直接使用f.readlines()的方法,會發現內存佔用立刻變大。

        而使用生成器表達式的狀況是針對你但願經過生成一個列表來統計一些數據時內存佔用大的問題,由於生成該列表自己就要把數據緩存到內存中,使用列表解析器時就須要生成一個列表,生成器表達式就是彌補列表解析的不足而提出的。




14.相關模塊


        主要是itertools模塊,它是迭代器的一些輔助工具。

相關文章
相關標籤/搜索