### 4. 控制流
除了前面介紹的[while](#)語句,Python也有其它語言常見的流程控制語句,但是稍有不一樣。
### 4.1. [if](#)語句
也許最知名的語句類型是[if](#)語句。例如:
~~~
>>> x = int(raw_input("Please enter an integer: "))
Please enter an integer: 42
>>> if x < 0:
... x = 0
... print 'Negative changed to zero'
... elif x == 0:
... print 'Zero'
... elif x == 1:
... print 'Single'
... else:
... print 'More'
...
More
~~~
可以有零個或多個[elif](#)部分,[else](#)部分是可選的。關鍵字'[elif](#)'是'else if'的簡寫,可以有效避免過深的縮進。[if](#)...[elif](#)...[elif](#)...序列用于替代其它語言中的switch或case語句。
### 4.2. [for](#)語句
Python中的[for](#)語句和你可能熟悉的C或Pascal中的有點不同。和常見的依據一個等差數列迭代(如 Pascal),或讓用戶能夠自定義迭代步驟和停止條件(如 C)不一樣,Python的[for](#)語句按照元素出現的順序迭代任何序列(列表或字符串)。例如(沒有雙關意):
~~~
>>> # Measure some strings:
... words = ['cat', 'window', 'defenestrate']
>>> for w in words:
... print w, len(w)
...
cat 3
window 6
defenestrate 12
~~~
如果要在循環內修改正在迭代的序列(例如,復制所選的項目),建議首先制作副本。迭代序列不會隱式地創建副本。使用切片就可以很容易地做到:
~~~
>>> for w in words[:]: # Loop over a slice copy of the entire list.
... if len(w) > 6:
... words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']
~~~
### 4.3. [range()](# "range")函數
如果你確實需要遍歷一個數字序列,內置函數[range()](# "range")非常方便。它將生成包含算術數列的列表:
~~~
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
~~~
給定的終點永遠不會在生成的列表中;range(10)生成一個包含10個值的鏈表,索引的值和對應元素的值相等。也可以讓 range 函數從另一個數值開始,或者可以指定一個不同的步進值(甚至是負數,有時這也被稱為‘步長’):
~~~
>>> range(5, 10)
[5, 6, 7, 8, 9]
>>> range(0, 10, 3)
[0, 3, 6, 9]
>>> range(-10, -100, -30)
[-10, -40, -70]
~~~
若要依據索引迭代序列,你可以結合使用[range()](# "range")和[len()](# "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
~~~
然而,在大部分情況下使用[enumerate()](# "enumerate")函數會更加方便,請參見[*循環的技巧*](#)。
### 4.4. [break](#)和[continue](#)語句,以及循環中[else](#)子句
[break](#)語句和C中的類似,用于跳出最近的[for](#)或[while](#)循環。
循環語句可以有一個else子句;當([for](#))循環迭代完整個列表或([while](#))循環條件變為假,而非由[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:
... # loop fell through without finding a factor
... 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
~~~
(是的,這是正確的代碼。看仔細:else子句屬于[for](#)循環,**不**屬于[if](#)語句。)
與循環一起使用時,else子句與[try](#)語句的else子句比與[if](#)語句的具有更多的共同點:[try](#)語句的else子句在未出現異常時運行,循環的else子句在未出現break時運行。更多關于[try](#)語句和異常的內容,請參見[*處理異常*](#)。
[continue](#)語句,也是從C語言借來的,表示繼續下一次迭代:
~~~
>>> for num in range(2, 10):
... if num % 2 == 0:
... print "Found an even number", num
... continue
... print "Found a number", num
Found an even number 2
Found a number 3
Found an even number 4
Found a number 5
Found an even number 6
Found a number 7
Found an even number 8
Found a number 9
~~~
### 4.5. [pass](#)語句
[pass](#)語句什么也不做。它用于語法上必須要有一條語句,但程序什么也不需要做的場合。例如:
~~~
>>> while True:
... pass # Busy-wait for keyboard interrupt (Ctrl+C)
...
~~~
它通常用于創建最小的類:
~~~
>>> class MyEmptyClass:
... pass
...
~~~
另一個使用[pass](#)的地方是編寫新代碼時作為函數體或控制體的占位符,這讓你在更抽象層次上思考。[pass](#)語句將被默默地忽略:
~~~
>>> def initlog(*args):
... pass # Remember to implement this!
...
~~~
### 4.6.定義函數
我們可以創建一個生成任意上界菲波那契數列的函數:
~~~
>>> def fib(n): # write Fibonacci series up to n
... """Print a Fibonacci series up to n."""
... a, b = 0, 1
... while a < n:
... print a,
... a, b = b, a+b
...
>>> # Now call the function we just defined:
... fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
~~~
關鍵字[def](#)引入函數的*定義*。其后必須跟有函數名和以括號標明的形式參數列表。組成函數體的語句從下一行開始,且必須縮進。
函數體的第一行可以是一個可選的字符串文本;此字符串是該函數的文檔字符串,或稱為*docstring*。(更多關于 docstrings 的內容可以在 [*文檔字符串*](#)一節中找到。)有工具使用 docstrings 自動生成在線的或可打印的文檔,或者讓用戶在代碼中交互瀏覽;在您編寫的代碼中包含 docstrings 是很好的做法,所以讓它成為習慣吧。
*執行*一個函數會引入一個用于函數的局部變量的新符號表。更確切地說,函數中的所有的賦值都是將值存儲在局部符號表;而變量引用首先查找局部符號表,然后是上層函數的局部符號表,然后是全局符號表,最后是內置名字表。因此,在函數內部全局變量不能直接賦值(除非在一個[global](#)語句中命名),雖然可以引用它們。
函數調用的實際參數在函數被調用時引入被調函數的局部符號表;因此,參數的傳遞使用*傳值調用*(這里的*值*始終是對象的*引用*,不是對象的值)。[[1]](#)一個函數調用另一個函數時,會為該調用創建一個新的局部符號表。
函數定義會在當前符號表內引入函數名。函數名對應值的類型是解釋器可識別的用戶自定義函數。此值可以分配給另一個名稱,然后也可作為函數。這是通用的重命名機制:
~~~
>>> fib
<function fib at 10042ed0>
>>> f = fib
>>> f(100)
0 1 1 2 3 5 8 13 21 34 55 89
~~~
如果你使用過其他語言,你可能會反對說:fib不是一個函數,而是一個過程(子程序),因為它并不返回任何值。事實上,沒有[return](#)語句的函數也返回一個值,盡管是一個很無聊的值。此值被稱為None(它是一個內置的名稱)。如果 None只是唯一的輸出,解釋器通常不會打印出來。如果你真的想看到這個值,可以使用[print](#) 語句:
~~~
>>> fib(0)
>>> print fib(0)
None
~~~
寫一個函數返回菲波那契數列的列表,而不是打印出來,非常簡單:
~~~
>>> def fib2(n): # return Fibonacci series up to n
... """Return a list containing the Fibonacci series up to n."""
... result = []
... a, b = 0, 1
... while a < n:
... result.append(a) # see below
... a, b = b, a+b
... return result
...
>>> f100 = fib2(100) # call it
>>> f100 # write the result
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
~~~
此示例中,像往常一樣,演示了一些新的 Python 功能:
- [return](#)語句從函數中返回一個值。不帶表達式參數的[return](#)返回None。函數直接結束后也返回None。
- 語句result.append(a)調用列表對象result 的一個*方法*。方法是‘隸屬于'某個對象的函數,被命名成obj.methodname的形式,其中obj是某個對象(或是一個表達式),methodname是由對象類型定義的方法的名稱。不同類型定義了不同的方法。不同類型的方法可能具有相同的名稱,而不會引起歧義。(也可以使用 *class* 定義你自己的對象類型和方法,請參見[*類*](#))本例中所示的append()方法是列表對象定義的。它在列表的末尾添加一個新的元素。在本例中它等同于result=result+[a],但效率更高。
### 4.7.更多關于定義函數
可以定義具有可變數目的參數的函數。有三種形式,也可以結合使用。
#### 4.7.1.默認參數值
最有用的形式是指定一個或多個參數的默認值。這種方法創建的函數被調用時,可以帶有比定義的要少的參數。例如:
~~~
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
while True:
ok = raw_input(prompt)
if ok in ('y', 'ye', 'yes'):
return True
if ok in ('n', 'no', 'nop', 'nope'):
return False
retries = retries - 1
if retries < 0:
raise IOError('refusenik user')
print complaint
~~~
這個函數可以通過幾種方式調用:
- 只給出強制參數: ask_ok (' Doyoureallywanttoquit?')
- 給出一個可選的參數: ask_ok ('OKtooverwritethefile?',2)
- 或者給出所有的參數: ask_ok ('OKtooverwritethefile?',2,'Comeon,onlyyesorno!')
此示例還引入了[in](#)關鍵字。它測試一個序列是否包含特定的值。
默認值在*定義域*中的函數定義的時候計算,例如:
~~~
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)
~~~
這將會打印
~~~
[1]
[1, 2]
[1, 2, 3]
~~~
如果你不想默認值在隨后的調用中共享,可以像這樣編寫函數:
~~~
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
~~~
#### 4.7.2. 關鍵字參數
函數也可以通過kwarg=value形式的[*關鍵字參數*](#)調用。例如,下面的函數:
~~~
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, "!"
~~~
接受一個必選參數(voltage)和三個可選參數(state,action和type)。可以用下列任意一種方式調用這個函數:
~~~
parrot(1000) # 1 positional argument
parrot(voltage=1000) # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump') # 3 positional arguments
parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
~~~
但下面的所有調用將無效:
~~~
parrot() # required argument missing
parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument
parrot(110, voltage=220) # duplicate value for the same argument
parrot(actor='John Cleese') # unknown keyword argument
~~~
在函數調用中,關鍵字的參數必須跟隨在位置參數的后面。傳遞的所有關鍵字參數必須與函數接受的某個參數相匹配(例如actor不是parrot函數的有效參數),它們的順序并不重要。這也包括非可選參數(例如 parrot(voltage=1000) 也是有效的)。任何參數都不可以多次賦值。下面的示例由于這種限制將失敗:
~~~
>>> def function(a):
... pass
...
>>> function(0, a=0)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: function() got multiple values for keyword argument 'a'
~~~
當最后一個形參以**name形式出現時,它接收一個字典(見[*映射類型 — 字典*](#)),該字典包含了所有未出現在形式參數列表中的關鍵字參數。它還可能與*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
keys = sorted(keywords.keys())
for kw in keys:
print kw, ":", keywords[kw]
~~~
它可以這樣調用:
~~~
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper='Michael Palin',
client="John Cleese",
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
~~~
注意在打印關鍵字參數之前,通過對關鍵字字典 keys() 方法的結果進行排序,生成了關鍵字參數名的列表;如果不這樣做,打印出來的參數的順序是未定義的。
#### 4.7.3.任意參數列表
最后,一個最不常用的場景是讓函數可以被可變個數的參數調用。這些參數被放在一個元組(見[*元組和序列*](#))中。在可變個數的參數之前,可以有零到多個普通的參數。
~~~
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
~~~
#### 4.7.4. 參數列表的分拆
當傳遞的參數已經是一個列表或元組時,情況與之前相反,你要分拆這些參數,因為函數調用要求獨立的位置參數。例如,內置的[range()](# "range")函數期望單獨的*start*和*stop*參數。如果它們不是獨立的,函數調用時使用 *-操作符將參數從列表或元組中分拆開來:
~~~
>>> range(3, 6) # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> range(*args) # call with arguments unpacked from a list
[3, 4, 5]
~~~
以同樣的方式,可以用**-操作符讓字典傳遞關鍵字參數:
~~~
>>> def parrot(voltage, state='a stiff', action='voom'):
... print "-- This parrot wouldn't", action,
... print "if you put", voltage, "volts through it.",
... print "E's", state, "!"
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
~~~
#### 4.7.5. lambda表達式
可以使用[lambda](#)關鍵字創建小的匿名函數。下面這個函數返回它的兩個參數的和:lambdaa,b:a + b。Lambda 函數可以用于任何需要函數對象的地方。在語法上,它們被局限于只能有一個單獨的表達式。在語義上,他們只是普通函數定義的語法糖。像嵌套的函數定義,lambda 函數可以從包含范圍引用變量:
~~~
>>> def make_incrementor(n):
... return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43
~~~
上面的示例使用 lambda 表達式返回一個函數。另一個用途是將一個小函數作為參數傳遞:
~~~
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
~~~
#### 4.7.6. 文檔字符串
有關文檔字符串的內容和格式的約定正不斷涌現。
第一行永遠應該是對象用途的簡短、精確的總述。為了簡單起見,不應該明確的陳述對象的名字或類型,因為這些信息可以從別的途徑了解到(除非這個名字碰巧就是描述這個函數操作的動詞)。這一行應該以大寫字母開頭,并以句號結尾。
如果在文檔字符串中有更多的行,第二行應該是空白,在視覺上把摘要與剩余的描述分離開來。以下各行應該是一段或多段描述對象的調用約定、 其副作用等。
Python 解釋器不會從多行的文檔字符串中去除縮進,所以必要的時候處理文檔字符串的工具應當自己清除縮進。這通過使用以下約定可以達到。第一行 *之后* 的第一個非空行字符串確定整個文檔字符串的縮進的量。(我們不用第一行是因為它通常緊靠著字符串起始的引號,其縮進格式不明晰。)所有行起始的等于縮進量的空格都將被過濾掉。不應該發生縮進較少的行,但如果他們發生,應去除所有其前導空白。留白的長度應當等于擴展制表符的寬度(正常是 8 個空格)。
這里是一個多行文檔字符串的示例:
~~~
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
...
>>> print my_function.__doc__
Do nothing, but document it.
No, really, it doesn't do anything.
~~~
### 4.8. 插曲:編碼風格
既然你將要編寫更長更復雜的 Python 片段,這是談一談 *編碼風格* 的好時機。大多數語言可以編寫成(或者更準確地講,*格式化成*)不同的風格;其中有一些會比其他風格更具可讀性。讓你的代碼對別人更易讀永遠是個好想法,養成良好的編碼風格對此有很大的幫助。
對于 Python 而言, [**PEP 8**](http://www.python.org/dev/peps/pep-0008) 已成為大多數項目遵循的風格指南;它給出了一個高度可讀,視覺友好的編碼風格。每個 Python 開發者應該閱讀一下;這里是為你提取出來的最重要的要點:
-
使用 4 個空格的縮進,不要使用制表符。
4 個空格是小縮進(允許更深的嵌套)和大縮進(易于閱讀)之間很好的折衷。制表符會引起混亂,最好棄用。
-
折行以確保其不會超過 79 個字符。
這有助于小顯示器用戶閱讀,也可以讓大顯示器能并排顯示幾個代碼文件。
-
使用空行分隔函數和類,以及函數內的大塊代碼。
-
如果可能,注釋獨占一行。
-
使用 docstrings。
-
運算符周圍和逗號后面使用空格,但是括號里側不加空格: a=f(1,2)+g(3,4)。
-
命名您的類和函數一致 ;慣例是使用駝峰命名法的類和使用lower_case_with_underscores 的函數和方法 。始終使用self作為方法的第一個參數的名稱(關于類和方法的更多信息請參見[*初識類*](#))。
-
如果希望你的代碼在國際化環境中使用,不要使用奇特的編碼。簡單的 ASCII 在任何情況下永遠工作得最好。
腳注
| [[1]](#) | 實際上,更好的描述是*通過對象的引用調用*,因為如果傳遞的是一個可變的對象,那么調用者可以看到被調用者對它所做的任何改變(插入到一個列表中元素)。 |
|-----|-----|
- Python 2 教程
- 1. 吊吊你的胃口
- 2. Python 解釋器
- 3. Python簡介
- 4. 控制流
- 5. 數據結構
- 6. 模塊
- 7. 輸入和輸出
- 8. 錯誤和異常
- 9. 類
- 10. 標準庫概覽
- 11. 標準庫概覽 — 第II部分
- 12.現在怎么辦?
- 13. 交互式輸入的編輯和歷史記錄
- 14. 浮點數運算:問題和局限
- Python 2 標準庫
- 1. 引言
- 2. 內建函數
- 3. 不太重要的內建函數
- 4. 內建的常量
- 5. 內建的類型
- 6. 內建的異常
- 7. String Services
- 8. Data Types
- 9. Numeric and Mathematical Modules
- 10. File and Directory Access
- 11. Data Persistence
- 13. File Formats
- 14. Cryptographic Services
- 15. Generic Operating System Services
- 16. Optional Operating System Services
- 17. Interprocess Communication and Networking
- 18. Internet Data Handling
- 20. Internet Protocols and Support
- 26. Debugging and Profiling
- 28. Python Runtime Services
- Python 2 語言參考
- 1. 簡介
- 2. 詞法分析
- 3. 數據模型
- 4. 執行模型
- 5. 表達式
- 6. 簡單語句
- 7. 復合語句
- 8. 頂層的組件
- 9. 完整的語法規范
- Python 3 教程
- 1. 引言
- 2. Python 解釋器
- 3. Python簡介
- 4. 控制流
- 5. 數據結構
- 6. 模塊
- 7. 輸入和輸出
- 8. 錯誤和異常
- 9. 類
- 10. 標準庫概覽
- 11. 標準庫概覽 — 第II部分
- 12.現在怎么辦?
- 13. 交互式輸入的編輯和歷史記錄
- 14. 浮點數運算:問題和局限