# 第五章 條件、循環和其他語句
> 來源:http://www.cnblogs.com/Marlowes/p/5329066.html
> 作者:Marlowes
讀者學到這里估計都有點不耐煩了。好吧,這些數據結構什么的看起來都挺好,但還是沒法用它們做什么事,對吧?
下面開始,進度會慢慢加快。前面已經介紹過了幾種基本語句(`print`語句、`import`語句、賦值語句)。在深入介紹_條件語句_和_循環語句_之前,我們先來看看這幾種基本語句更多的使用方法。隨后你會看到列表推倒式(list comprehension)如何扮演循環和條件語句的角色——盡管它本身是表達式。最后介紹`pass`、`del`和`exec`語句的用法。
## 5.1 `print`和`import`的更多信息
隨著更加深入第學習Python,可能會出現這種感覺:有些自己以為已經掌握的知識點,還隱藏著一些讓人驚訝的特性。首先來看看`print`(在Python3.0中,`print`不再是語句——而是函數(功能基本不變))和`import`的幾個比較好的特性。
_注:對于很多應用程序來說,使用`logging`模塊記日志比`print`語句更合適。更多細節請參見第十九章。_
### 5.1.1 使用逗號輸出
前面的章節中講解過如何使用`print`來打印表達式——不管是字符串還是其他類型進行自動轉換后的字符串。但是事實上打印多個表達式也是可行的,只要將它們用逗號隔開就好:
```
>>> print "Age", 19
Age 19
```
可以看到,每個參數之間都插入了一個空格符。
_注:print的參數并不能像我們預期那樣構成一個元組:_
```
>>> 1, 2, 3
(1, 2, 3)
>>> print 1, 2, 3
1 2 3
>>> print (1, 2, 3)
(1, 2, 3)
```
如果想要同時輸出文本和變量值,卻又不希望使用字符串格式化的話,那這個特性就非常有用了:
```
>>> name = "XuHoo"
>>> salutation = "Mr."
>>> greeting = "Hello,"
>>> print greeting, salutation, name
Hello, Mr. XuHoo
# 注意,如果greeting字符串不帶逗號,那么結果中怎么能得到逗號呢?像下面這樣做是不行的:
>>> print greeting, ",", salutation, name
Hello , Mr. XuHoo
# 因為上面的語句會在逗號前加入空格。下面是一種解決方案:
>>> print greeting + ",", salutation, name
Hello, Mr. XuHoo
# 這樣一來,問候語后面就只會增加一個逗號。
```
如果在結尾處加上逗號,那么接下來的語句會與前一條語句在同一行打印,例如:
```
print "Hello", print "world!"
# 輸出 Hello, world!(這只在腳本中起作用,而在交互式Python會話中則沒有效果。在交互式會話中,所有的語句都會被單獨執行(并且打印出內容))
```
### 5.1.2 把某件事作為另一件事導入
從模塊導入函數的時候,通常可以使用以下幾種方式:
```
import somemodule # or
from somemodule import somefunction
# or
from somemodule import somefunction, anotherfunction, yetanotherfunction
# or
from somemodule import *
```
只有確定自己想要從給定的模塊導入所有功能時,才應該使用最后一個版本。但是如果兩個模塊都有`open`函數,那又該怎么辦?只需要使用第一種方式導入,然后像下面這樣使用函數:
```
import module1 import module2
module1.open(...)
module2.open(...)
```
但還有另外的選擇:可以在語句末尾增加一個`as`子句,在該子句后給出想要使用的別名。例如為整個模塊提供別名:
```
>>> import math as foobar
>>> foobar.sqrt(4)
2.0
# 或者為函數提供別名
>>> from math import sqrt as foobar
>>> foobar(4)
2.0
# 對于open函數,可以像下面這樣使用:
from module1 import open as open1
from module2 import open as open2
```
注:有些模塊,例如`os.path`是分層次安排的(一個模塊在另一個模塊的內部)。有關模塊結構的更多信息,請參見第十章關于包的部分。
## 5.2 賦值魔法
就算是不起眼的賦值語句也有一些特殊的技巧。
### 5.2.1 序列解包
賦值語句的例子已經給過不少,其中包括對變量和數據結構成員的(比如列表中的位置和分片以及字典中的槽)賦值。但賦值的方法還不止這些。比如,多個賦值操作可以_同時_進行:
```
>>> x, y, z = 1, 2, 3
>>> print x, y, z 1 2 3
# 很有用吧?用它交換兩個(或更多個)變量也是沒問題的:
>>> x, y = y, x >>> print x, y, z 2 1 3
```
事實上,這里所做的事情叫做序列解包(sequence unpacking)或_遞歸解包_——將多個值的序列解開,然后放到變量的序列中。更形象一點的表示就是:
```
>>> values = 1, 2, 3
>>> values
(1, 2, 3) >>> x, y, z = values >>> x 1
>>> y 2
>>> z 3
```
當函數或者方法返回元組(或者其他序列或可迭代對象)時,這個特性尤其有用。假設需要獲取(和刪除)字典中任意的鍵-值對,可以使用`popitem`方法,這個方法將鍵-值作為元組返回。那么這個元組就可以直接賦值到兩個變量中:
```
>>> scoundrel = {"name": "XuHoo", "girlfriend": "None"}
# =_=
>>> key, value = scoundrel.popitem()
>>> key 'girlfriend'
>>> value 'None'
```
它允許函數返回一個以上的值并且打包成元組,然后通過一個賦值語句很容易進行訪問。所解包的序列中的元素數量必須和放置在賦值符號=左邊的變量數量完全一致,否則Python會在賦值時引發異常:
```
>>> x, y, z = 1, 2 Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: need more than 2 values to unpack
>>> x, y, z = 1, 2, 3, 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack
```
_注:Python3.0中有另外一個解包的特性:可以像在函數的參數列表中一樣使用星號運算符(參見第六章)。例如,`a, b, *rest = [1, 2, 3, 4]`最終會在`a`和`b`都被賦值之后將所有的其他的參數都收集到`rest`中。本例中,`rest`的結果將會是`[3, 4]`。使用星號的變量也可以放在第一個位置,這樣它就總會包含一個列表。右側的賦值語句可以是可迭代對象。_
### 5.2.2 鏈式賦值
鏈式賦值(charned assignment)是將同一個值賦給多個變量的捷徑。它看起來有些像上節中并行賦值,不過這里只處理一個值:
```
x = y = somefunction() # 和下面語句的效果是一樣的:
y = somefunction()
x = y
# 注意上面的語句和下面的語句不一定等價:
x = somefunction()
y = somefunction()
```
有關鏈式賦值更多的信息,請參見本章中的“同一性運算符”一節。
### 5.2.3 增量賦值
這里沒有將賦值表達式寫為`x=x+1`,而是將表達式運算符(本例中是`±`)放置在賦值運算符`=`的左邊,寫成`x+=1`,。這種寫法叫做增量賦值(augmented assignmnet),對于`*`、`/`、`%`等標準運算符都適用:
```
>>> x = 2
>>> x += 1
>>> x *= 2
>>> x 6
# 對于其他數據類型也適用(只要二元運算符本身適用于這些數據類型即可):
>>> fnord = "foo"
>>> fnord += "bar"
>>> fnord *= 2
>>> fnord 'foobarfoobar'
```
增量賦值可以讓代碼更加緊湊和簡練,很多情況下會更易讀。
## 5.3 語句塊:縮排的樂趣
語句塊并非一種語句,而是在掌握后面兩節的內容之前應該了解的知識。
語句塊是在條件為真(條件語句)時執行或者執行多次(循環語句)的_一組_語句。在代碼前放置空格來縮進語句即可創建語句塊。
_注:使用`tab`字符也可以縮進語句塊。Python將一個`tab`字符解釋為到下一個`tab`字符位置的移動,而一個`tab`字符位置為8個空格,但是標準且推薦的方式是只用空格,尤其是在每個縮進需要4個空格的時候。_
塊中的每行都應該縮進_同樣的量_。下面的偽代碼(并非真正Python代碼)展示了縮進的工作方法:
```
this is a line
this is another line:
this is another block
continuing the same block
the last line of this block
phew, there we escaped the inner block
```
很多語言使用特殊單詞或者字符(比如`begin`或`{`)來表示一個語句塊的開始,使用另外的單詞或者字符(比如`end`或者`}`)表示語句塊的結束。在Python中,冒號(`:`)用來標識語句塊的開始,塊中的每一個語句都是縮進(縮進量相同)。當回退到和已經閉合的塊一樣的縮進量時,就表示當前塊已經結束了(很多程序編輯器和集成開發環境都知道如何縮進語句塊,可以幫助用戶輕松把握縮進)。
現在我確信你已經等不及想知道語句塊怎么使用了。廢話不多說,我們來看一下。
## 5.4 條件和條件語句
到目前為止的程序都是一條一條語句順序執行的。在這部分中會介紹讓程序選擇是否執行語句塊的方法。
### 5.4.1 這就是布爾變量的作用
_真值_(也叫作_布爾值_,這個名字根據在真值上做過大量研究的George Boole命名的)是接下來內容的主角。
_注:如果注意力夠集中,你就會發現在第一章的“管窺:`if`語句”中就已經描述過`if`語句。到目前為止這個語句還沒有被正式介紹。實際上,還有很多`if`語句的內容沒有介紹。_
下面的值在作為布爾表達式的時候,會被解釋器看做假(`False`):
```
False None 0 "" () [] {}
```
換句話說,也就是標準值`False`和`None`、所有類型的數字`0`(包括浮點型、長整型和其他類型)、空序列(比如空字符串、元組和列表)以及空的字典都為假。其他的一切(至少當我們討論內建類型是是這樣——第九章內會討論構建自己的可以被解釋為真或假的對象)都被解釋為真,包括特殊值`True`(Python經驗豐富的Laura Creighton解釋說這個區別類似于“有些東西”和“沒有東西”的區別,而不是_真_和_假_的區別)。
明白了嗎?也就是說Python中的所有值都能被解釋為真值,初次接觸的時候可能會有些搞不明白,但是這點的確非常有用。“標準的”布爾值為`True`和`False`。在一些語言中(例如C和Python2.3以前的版本),標準的布爾值為`0`(表示假)和`1`(表示真)。事實上,`True`和`False`只不過是`1`和`0`的一種“華麗”的說法而已——看起來不同,但作用相同。
```
>>> True
True
>>> False
False
>>> True == 1
True
>>> False == 0
True
>>> True + False
1
>>> True + False + 19
20
```
那么,如果某個邏輯表達式返回`1`或`0`(在老版本Python中),那么它實際的意思是返回`True`或`False`。
布爾值`True`和`False`屬于布爾類型,`bool`函數可以用來(和`list`、`str`以及`tuple`一樣)轉換其他值。
```
>>> bool("I think, therefore I am")
True
>>> bool(19)
True
>>> bool("")
False
>>> bool(0)
False
```
因為所有值都可以用作布爾值,所以幾乎不需要對它們進行顯示轉換(可以說Python會自動轉換這些值)。
注:盡管`[]`和`""`都是假肢(也就是說`bool([])==bool("")==False`),它們本身卻并不相等(也就是說`[]!=""`)。對于其他不同類型的假值對象也是如此(例如`()!=False`)。
### 5.4.2 條件執行和`if`語句
真值可以聯合使用(馬上就要介紹),但還是讓我們先看看它們的作用。試著運行下面的腳本:
```
name = raw_input("What is your name? ")
if name.endswith("XuHoo"):
print "Hello, Mr.XuHoo"
```
這就是`if`語句,它可以實現_條件執行_,即如果條件(在`if`和冒號之間的表達式)判定為_真_,那么后面的語句塊(本例中是單個`print`語句)就會被執行。如果條件為假,語句塊就不會被執行(你猜到了,不是嗎)。
注:在第一章的“管窺:`if`語句”中,所有語句都寫在一行中。這種書寫方式和上例中的使用單行語句塊的方式是等價的。
### 5.4.3 else子句
前一節的例子中,如果用戶輸入了以`XuHoo`作為結尾的名字,那么`name.endswit`方法就會返回真,使得`if`進入語句塊,打印出問候語。也可以使用`else`子句增加一種選擇(之所以叫做_子句_是因為它不是獨立的語句,而只能作為`if`語句的一部分)。
```
name = raw_input("What is your name? ")
if name.endswith("XuHoo"):
print "Hello, Mr.XuHoo"
else:
print "Hello. stranger"
```
如果第一個語句塊沒有被執行(因為條件被判定為假),那么就會站轉入第二個語句塊,可以看到,閱讀Python代碼很容易,不是嗎?大聲把代碼讀出來(從`if`開始),聽起來就像正常(也可能不是很正常)句子一樣。
### 5.4.4 elif子句
如果需要檢查多個條件,就可以使用`elif`,它是`else if`的簡寫,也是`if`和`else`子句的聯合使用,也就是具有條件的`else`子句。
```
name = input("Enter a number: ")
if num > 0:
print "The number is positive"
elif num < 0:
print "The number is negative"
else:
print "The number is zero"
```
_注:可以使用`int(raw_input(...))`函數來代替`input(...)`。關于兩者的區別,請參見第一章。_
### 5.4.5 嵌套代碼塊
下面的語句中加入了一些不必要的內容。if語句里面可以嵌套使用`if`語句,就像下面這樣:
```
name = raw_input("What is your name? ")
if name.endswith("XuHoo"):
if name.startswith("Mr."):
print "Hello, Mr. XuHoo"
elif name.startswith("Mrs."):
print "Hello, Mrs. XuHoo"
else:
print "Hello, XuHoo"
else:
print "Hello, stranger"
```
如果名字是以`XuHoo`結尾的話,還要檢查名字的開頭——在第一個語句塊中的單獨的`if`語句中。注意這里`elif`的使用。最后一個選項中(`else`子句)沒有條件——如果其他的條件都不滿足就使用最后一個。可以把任何一個`else`子句放在語句塊外面。如果把里面的`else`子句放在外面的話,那么不以`Mr.`或`Mrs.`開頭(假設這個名字是`XuHoo`)的名字都被忽略掉了。如果不寫最后一個`else`子句,那么陌生人就被忽略掉。
### 5.4.6 更復雜的條件
以上就是有關if語句的所有知識。下面讓我們回到條件本身,因為它們才是條件執行時真正有趣的部分。
1\. 比較運算符
用在條件中的最基本的運算符就是_比較運算符_了,它們用來比較其他對象。比較運算符已經總結在表5-1中。
表5-1 Python中的比較運算符
```
x = y x 等于 y
x < y x 小于 y
x > y x 大于 y
x >= y x 大于等于 y
x <= y x 小于等于 y
x != y x 不等于 y
x is y x 和 y 是同一個對象
x is not y x 和 y 是不同的對象
x in y x 是 y 容器(例如,序列)的成員
x not in y x 不是 y 容器(例如,序列)的成員
```
**比較不兼容類型**
理論上,對于相對大小的任意兩個對象`x`和`y`都是可以使用比較運算符(例如,`<`和`<=`)比較的,并且都會得到一個布爾值結果。但是只有在`x`和`y`是相同或者近似類型的對象時,比較才有意義(例如,兩個整型數或者一個整型數和一個浮點型數進行比較)。
正如將一個整型數添加到一個字符串中是沒有意義的,檢查一個整型是否比一個字符串小,看起來也是毫無意義的。但奇怪的是,在Python3.0之前的版本中這卻是可以的。對于此類比較行為,讀者應該敬而遠之,因為結果完全不可靠,在每次程序執行的時候得到的結果都可能不同。在Python3.0中,比較不兼容類型的對象已經不再可行。
_注:如果你偶然遇見 `x <> y` 這樣的表達式,它的意思其實就是 `x != y`。不建議使用`<>`運算符,應該盡量避免使用它。_
在Python中比較運算符和賦值運算符一樣是可以_連接_的——幾個運算符可以連在一起使用,比如:`0<age<100`。
_注:比較對象的時候可以使用第二章中介紹的內建的`cmp`函數。_
有些運算符值得特別關注,下面的章節中會對此進行介紹。
2\. 相等運算符
如果想要知道兩個東西是否相等,應該使用相等運算符,即兩個等號"==":
```
>>> "foo" == "foo" True
>>> "foo" == "bar" False
# 相等運算符需要使用兩個等號,如果使用一個等號會出現下面的情況
>>> "foo" = "foo"
File "<stdin>", line 1
SyntaxError: can't assign to literal
```
單個相等運算符是賦值運算符,是用來_改變_值的,而不能用來比較。
3\. `is`:同一性運算符
這個運算符比較有趣。它看起來和`==`一樣,事實上卻不同:
```
>>> x = y = [1, 2, 3]
>>> z = [1, 2, 3]
>>> x == y
True >>> x == z
True >>> x is y
True >>> x is z
False
```
到最后一個例子之前,一切看起來都很好,但是最后一個結果很奇怪,`x`和`z`相等卻不等同,為什么呢?因為`is`運算符是判定_同一性_而不是_相等性_的。變量`x`和`y`都被綁定到同一列表上,而變量`z`被綁定在另外一個具有相同數值和順序的列表上。它們的值可能相等,但是卻不是同一個_對象_。
這看起來有些不可理喻吧?看看這個例子:
```
>>> x = [1, 2, 3]
>>> y = [2, 4]
>>> x is not y
True
>>> del x[2]
>>> y[1] = 1
>>> y.reverse()
>>> y
[1, 2]
>>> x
[1, 2] # 本例中,首先包括兩個不同的列表x和y。可以看到 x is not y 與(x is y 相反),這個已經知道了。之后我改動了一下列表,盡管它們的值相等了,但是還是兩個不同的列表。
>>> x == y
True
>>> x is y
False # 顯然,兩個列表值等但是不等同。
```
總結一下:使用`==`運算符來判定兩個對象是否_相等_。使用`is`判定兩者是否_等同_(同一個對象)。
_注:避免將`is`運算符用于比較類似數值和字符串這類不可變值。由于Python內部操作這些對象的方式的原因,使用`is`運算符的結果是不可預測的。_
4\. `in`:成員資格運算符
`in`運算符已經介紹過了(在2.2.5節)。它可以像其他比較運算符一樣在條件語句中使用。
```
name = raw_input("What is your name? ")
if "s" in name:
print "Your name contains the letter 's'."
else:
print "Your name does not contains the letter 's'."
```
5\. 字符串和序列比較
字符串可以按照字母順序排列進行比較。
```
>>> "alpha" < "beta" True
```
_注:實際的順序可能會因為使用不同的本地化設置(`locale`)而和上邊的例子有所不同(請參見標準庫文檔中`locale`模塊一節)。_
如果字符串內包括大寫字母,那么結果就會有點亂(實際上,字符是按照本身的順序值排列的。一個字母的順序值可以用`ord`函數查到,`ord`函數與`chr`函數功能相反)。如果要忽略大小寫字母的區別,可以使用字符串方法`upper`和`lower`(請參見第三章)。
```
>>> "FnOrD".lower() == "Fnord".lower()
True # 其他的序列也可以用同樣的方式進行比較,不過比較的不是字符而是其他類型的元素。
>>> [1, 2] < [2 ,1]
True # 如果一個序列中包括其他序列元素,比較規則也同樣適用于序列元素。
>>> [2, [1, 4]] < [2, [1, 5]]
True
```
6\. 布爾運算符
返回布爾值的對象已經介紹過許多(事實上,所有值都可以解釋為布爾值,所有的表達式也都返回布爾值)。但有時想要檢查一個以上的條件。例如,如果需要編寫讀取數字并且判斷該數字是否位于1~10之間(也包括10)的程序,可以像下面這樣做:
```
number = input("Enter a number between 1 and 10: ")
if number <= 10:
if number >= 1:
print "Great!"
else:
print "Wrong!"
else:
print "Wrong!"
# 這樣做沒問題,但是方法太笨了。笨在需要寫兩次print "Wrong!"。在復制上浪費精力可不是好事。那么怎么辦?很簡單:
number = input("Enter a number between 1 and 10: ")
if number <= 10 and number >= 1:
print "Great!"
else:
print "Wrong!"
```
_注:本例中,還有(或者說應該使用)更簡單的方法,即使用連接比較:`1<=number<=10`。_
`and`運算符就是所謂的布爾運算符。它連接兩個布爾值,并且在兩者都為真時返回真,否則返回假。與它同類的還有兩個運算符,`or`和`not`。使用這三個運算符就可以隨意結合真值。
```
if ((cash > price) or customer_has_good_credit) and not out_of_stock:
give_goods()
```
**短路邏輯和條件表達式**
布爾運算符有個有趣的特性:只有在需要求值時才進行求值。舉例來說,表達式 `x and y` 需要兩個變量都為真時才為真,所以如果x為假,表達式就會立刻返回`False`,而不管`y`的值。實際上,如果`x`為假,表達式會返回`x`的值——否則它就返回`y`的值。(能明白它是怎么達到預期效果的嗎?)這種行為被稱為_短路邏輯_(short-circuit logic)或_惰性求值_(lazy evaluation):布爾運算符通常被稱為邏輯運算符,就像你看到的那樣第二個值有時“被短路了”。這種行為對于`or`來說也同樣適用。在`x or y`中,`x`為真時,它直接返回`x`值,否則返回`y`值。(應該明白什么意思吧?)注意,這意味著在布爾運算符之后的所有代碼都不會執行。
這有什么用呢?它主要是避免了無用地執行代碼,可以作為一種技巧使用,假設用戶應該輸入他/她的名字,但也可以選擇什么都不輸入,這時可以使用默認值`"<unknown>"`。可以使用`if`語句,但是可以很簡潔的方式:
```
name = raw_input("Please enter your name: ") or "<unknown>"
```
換句話說,如果`raw_input(...)`語句的返回值為真(不是空字符串),那么它的值就會賦值給`name`,否則將默認的`"<unknown>"`賦值給`name``。
這類短路邏輯可以用來實現C和Java中所謂的三元運算符(或條件運算符)。在Python2.5中有一個內置的條件表達式,像下面這樣:
```
a if b else c
```
如果b為真,返回a,否則,返回c。(注意,這個運算符不用引入臨時變量,就可以直接使用,從而得到與`raw_input(...)`例子中同樣的結果)
### 5.4.7 斷言
`if`語句有個非常有用的“近親”,它的工作方式多少有點像下面這樣(偽代碼):
```
if not condition:
crash program
```
究竟為什么會需要這樣的代碼呢?就是因為與其讓程序在晚些時候崩潰,不如在錯誤條件出現時直接讓它崩潰。一般來說,你可以要求某些條件必須為真(例如,在檢查函數參數的屬性時,或者作為初期測試和調試過程中的輔助條件)。語句中使用的關鍵字是`assert`。
```
>>> age = 10
>>> assert 0 < age < 100
>>> age = -1
>>> assert 0 < age < 100
Traceback (most recent call last):
File "<stdin>", line 1, in <module> AssertionError
```
如果需要確保程序中的某一個條件一定為真才能讓程序正常工作的話,assert語句就有用了,他可以在程序中置入檢查點。
條件后可以添加字符串,用來解釋斷言:
```
>>> age = -1
>>> assert 0 < age < 100, "The age must be realistic"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: The age must be realistic
```
## 5.5 循環
現在你已經知道當條件為真(或假)時如何執行了,但是怎么才能重復執行多次呢?例如,需要實現一個每月提醒你付房租的程序,但是就我們目前學習到的知識而言,需要向下面這樣編寫程序(偽代碼):
```
發郵件
等一個月
發郵件
等一個月
發郵件
等一個月
(繼續下去······)
```
但是如果想讓程序繼續執行直到認為停止它呢?比如想像下面這樣做(還是偽代碼):
```
當我們沒有停止時:
發郵件
等一個月
```
或者換個簡單些的例子。假設想要打印1~100的所有數字,就得再次用這個笨方法:
```
print 1
print 2
print 3
······
print 99
print 100
```
但是如果準備用這種笨方法也就不會學Python了,對吧?
### 5.5.1 `while`循環
為了避免上例中笨重的代碼,可以像下面這樣做:
```
x = 1
while x <= 100
print x
x += 1
```
那么Python里面應該如何寫呢?你猜對了,就像上面那樣。不是很復雜吧?一個循環就可以確保用戶輸入了名字:
```
name = ""
while not name:
name = raw_input("Please enter your name: ")
print "Hello, %s!" % name
```
運行這個程序看看,然后在程序要求輸入名字時按下回車鍵。程序會再次要求輸入名字,因為`name`還是空字符串,其求值結果為`False`。
_注:如果直接輸入一個空格作為名字又會如何?試試看。程序會接受這個名字,因為包括一個空格的字符串并不是空的,所以不會判定為假。小程序因此出現了瑕疵,修改起來也很簡單:只需要把`while not name`改為`while not name or name.isspace()`即可,或者可以使用`while not name.strip()`。_
### 5.2.2 `for`循環
`while`語句非常靈活。它可以用來在_任何條件_為真的情況下重復執行一個代碼塊。一般情況下這樣用就夠了,但是有些時候還得量體裁衣。比如要為一個集合(序列和其他可迭代對象)的每個元素都執行一個代碼塊。
_注:可迭代對象是指可以按次序迭代的對象(也就是用于`for`循環中的)。有關可迭代和迭代器的更多信息,請參見第九章,現在讀者可以將其看做序列。_
這個時候可以使用`for`語句:
```
words = ["this", "is", "an", "ex", "parrot"]
for word in words:
print word
# 或者
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for number in numbers:
print number
# 因為迭代(循環的另一種說法)某范圍的數字是很常見的,所以有個內建的范圍函數提供使用:
>>> range(0, 10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Range函數的工作方式類似于分片。它包含下限(本例中為0),但不包含上限(本例中為10)。如果希望下限為0,可以只提供上限:
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 下面的程序會打印1~100的數字:
for number in range(1, 100):
print number
# 它比之前的while循環更簡潔。
```
_注:如果能使用`for`循環,就盡量不用`while`循環。_
`xrange`函數的循環行為類似于`range`函數,區別在于`range`函數一次創建整個序列,而`xrange`一次只創建一個數(在Python3.0中,`range`會被轉換成`xrange`風格的函數)。當需要迭代一個巨大的序列時`xrange`會更高效,不過一般情況下不需要過多關注它。
### 5.5.3 循環遍歷字典元素
一個簡單的`for`語句就能遍歷字典的所有鍵,就像遍歷訪問序列一樣:
```
d = {"x": 1, "y": 2, "z": 3}
for key in d:
print key, "corresponds to", d[key]
```
在Python2.2之前,還只能用`keys`等字典方法來獲取鍵(因為不允許直接迭代字典)。如果只需要值,可以使用`d.values`替代`d.keys`。`d.items`方法會將鍵-值對作為元組返回,`for`循環的一大好處就是可以循環中使用序列解包:
```
for key, value in d.items():
print key, "corrsponds", value
```
_注:字典元素的順序通常是沒有定義的。換句話說,迭代的時候,字典中的鍵和值都能保證被處理,但是處理順序不確定。如果順序很重要的話,可以將鍵值保存在單獨的列表中,例如在迭代前進行排序。_
### 5.5.4 一些迭代工具
在Python中迭代序列(或者其他可迭代對象)時,有一些函數非常好用。有些函數位于`itertools`模塊中(第十章中介紹),還有一些Python的內建函數也十分方便。
1\. 并行迭代
程序可以同時迭代兩個系列。比如有下面兩個列表:
```
names = ["XuHoo", "Marlowes", "GuoYing", "LeiLa"]
ages = [19, 19, 22, 22]
# 如果想要打印名字和對應的年齡,可以像下面這樣做:
for i in range(len(names)):
print names[i], "is", ages[i], "years old"
```
這里 `i` 是循環索引的標準變量名(可以自己隨便定義,一般情況下`for`循環都以 `i` 作為變量名)。
而內建的`zip`函數就可以用來進行并行迭代,可以把兩個序列“壓縮”在一起,然后返回一個元組的列表:
```
>>> zip(names, ages)
[("XuHoo", 19), ("Marlowes", 19), ("GuoYing", 22), ("LeiLa", 22)]
# 現在我可以在循環中解包元組:
for name, age in zip(names, ages):
print name, "is", age, "years old"
# zip函數也可以作用于任意多的序列。關于它很重要的一點是zip可以處理不等長的序列,當最短的序列"用完"的時候就會停止:
>>> zip(range(5), xrange(100000000))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
```
在上面的代碼中,不推薦用`range`替換`xrange`——盡管只需要前五個數字,但`range`會計算所有的數字,這要花費很長的時間。而是用`xrange`就沒這個問題了,它只計算前五個數字。
2.按索引迭代
有些時候想要迭代訪問序列中的對象,同時還要獲取當前對象的索引。例如,在一個字符串列表中替換所有包含`"xxx"`的子字符。實現的方法肯定有很多,假設你想像下面這樣做:
```
for string in strings: if "xxx" in string:
index = strings.index(string)
# Search for the string in the list of strings
strings[index] = "[censored]"
# 沒問題,但是在替換前要搜索給定的字符串似乎沒必要。如果不替換的話,搜索還會返回錯誤的索引(前面出現的同一個詞的索引)。一個比較好的版本如下:
index = 0 for string in strings:
if "xxx" in string:
strings[index] = "[censored]"
index += 1
```
方法有些笨,不過可以接受。另一種方法是使用內建的`enumerate`函數:
```
for index, string in enumerate(strings):
if "xxx" in string:
strings[index] = "[censored]"
```
這個函數可以在提供索引的地方迭代索引-值對。
3\. 翻轉和排序迭代
讓我們看看另外兩個有用的函數:`reversed`和`sorted`。它們同列表的`reverse`和`sort`(`sorted`和`sort`使用同樣的參數)方法類似,但作用于任何序列或可迭代對象上,不是原地修改對象,而是返回翻轉或排序后的版本:
```
>>> sorted([4, 3, 6, 8, 3])
[3, 3, 4, 6, 8]
>>> sorted("Hello, world!")
[' ', '!', ',', 'H', 'd', 'e', 'l', 'l', 'l', 'o', 'o', 'r', 'w']
>>> list(reversed("Hello, world!"))
['!', 'd', 'l', 'r', 'o', 'w', ' ', ',', 'o', 'l', 'l', 'e', 'H']
>>> "".join(reversed("Hello, world!"))
'!dlrow ,olleH'
```
注意,雖然`sorted`方法返回列表,`reversed`方法卻返回一個更加不可思議的可迭代對象。它們具體的含義不用過多關注,大可在`for`循環以及`join`方法中使用,而不會有任何問題。不過卻不能直接對它使用索引、分片以及調用`list`方法,如果希望進行上述處理,那么可以使用`list`類型轉換返回對象,上面的例子中已經給出具體的做法。
### 5.5.5 跳出循環
一般來說,循環會一直執行到條件為假,或者到序列元素用完時。但是有些時候可能會提前中斷一個循環,進行新的迭代(新一"輪"的代碼執行),或者僅僅就是像結束循環。
1\. `break`
結束(跳出)循環可以使用`break`語句。假設需要尋找100以內的最大平方數,那么程序可以開始從100往下迭代到0.當找到一個平方數時就不需要繼續循環了,所以可以跳出循環:
```
from math import sqrt
for n in range(99, 0, -1):
root = sqrt(n)
if root == int(root):
print n break
```
如果執行這個程序的話,會打印出`81`,然后程序停止。注意,上面的代碼中`range`函數增加了第三個參數——表示_步長_,步長表示每對相鄰數字之間的差別。將其設置為負值的話就會想例子中一樣反向迭代。它也可以用來跳過數字:
```
>>> range(0, 10, 2)
[0, 2, 4, 6, 8]
```
2\. `continue`
`continue`語句比`break`語句用得要少得多。它會讓當前的迭代結束,“跳”到下一輪循環的開始。它最基本的意思是“跳過剩余的循環體,但是不結束循環”。當循環體很大而且很復雜的時候,這會很有用,有些時候因為一些原因可能會跳過它——這個時候可以使用`continue`語句:
```
for x in seq:
if condition1:
continue
if condition2:
continue
if condition3:
continue
do_something()
do_something_else()
do_another_thing()
etc()
# 很多時候,只要使用if語句就可以了:
for x in seq:
if not (condition1 or condition2 or condition3):
do_something()
do_something_else()
do_another_thing()
etc()
```
盡管`continue`語句非常有用,它卻不是最本質的。應該習慣使用`break`語句,因為在`while True`語句中會經常用到它。下一節會對此進行介紹。
3\. `while True/break`習語
Python中的`while`和`for`循環非常靈活,但一旦使用`while`語句就會遇到一個需要更多功能的問題。如果需要當用戶在提示符下輸入單詞時做一些事情,并且在用戶不輸入單詞后結束循環。可以使用下面的方法:
```
word = "dummy"
while word:
# 處理word
word = raw_input("Please enter a word: ")
print "The word was " + word
# 下面是一個會話示例:
Please enter a word: first
The word was first
Please enter a word: second
The word was second
Please enter a word:
```
代碼按要求的方式工作(大概還能做些比直接打印出單詞更有用的工作)。但是代碼有些丑。在進入循環之前需要給word賦一個啞值(未使用的)。使用啞值(dummy value)就是工作沒有盡善盡美的標志。讓我們試著避免它:
```
word = raw_input("Please enter a word: ")
# 處理word
while word:
print "The word was " + word
word = raw_input("Please enter a word: ")
# 啞值沒有了。但是有重復的代碼(這樣也不好):要用一樣的賦值語句在兩個地方兩次調用raw_input。能否不這么做呢?可以使用while True/break語句:
while True:
word = raw_input("Please enter a word: ")
if not word:
break
# 處理word
print "The word was " + word
```
`while True`的部分實現了一個永遠不會自己停止的循環。但是在循環內部的if語句中加入條件也是可以的,在條件滿足時使用`break`語句。這樣一來就可以在循環內部任何地方而不是只在開頭(像普通的`while`循環一樣)終止循環。`if/break`語句自然地將循環分為兩部分:第一部分負責初始化(在普通的`while`循環中,這部分需要重復),第二部分則在循環條件為真的情況下使用第一部分內初始化好的數據。
盡管應該避免在代碼中頻繁使用`break`語句(因為這可能會讓循環的可讀性降低,尤其是在一個循環中使用多個`break`語句的時候),但這個特殊的技術用得非常普遍,大多數Python程序員(包括你自己)都能理解你的意思。
### 5.5.6 循環中的`else`子句
當在循環內使用`break`語句時,通常是因為“找到”了某物或者因為某事“發生”了。在跳出是做一些事情是很簡單的(比如`print n`),但是有些時候想要在沒有跳出之前做些事情。那么怎么判斷呢?可以使用布爾變量,再循環前將其設定為`False`,跳出后設定為`True`。然后再使用`if`語句查看循環是否跳出了:
```
broke_out = False for x in seq:
do_something(x)
if condition(x):
broke_out = True
break
do_something_else(x)
if not broke_out:
print "I didn't break out!"
# 更簡單的方式是在循環中增加一個else子句——它僅在沒有調用`break`時執行。讓我們用這個方法重寫剛才的例子:
from math import sqrt
for n in range(99, 81, -1):
root = sqrt(n)
if root == int(root):
print n break
else:
print "Didn't find it!"
```
注意我將下限改為81(不包括81)以測試`else`子句。如果執行程序的話,它會打印出`"Didn't find it!"`,因為(就像在`break`那節看到的一樣)100以內最大的平方數時81。`for`和`while`循環中都可以使用`continue`、`break`語句和`else`子句。
## 5.6 列表推導式——輕量級循環
_列表推導式_(list comprehension)是利用其他列表創建新列表(類似于數學術語中的集合推導式)的一種方法。它的工作方式類似于`for`循環,也很簡單:
```
>>> [x*x for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
```
列表有`range(10)`中每個x的平方組成。太容易了?如果只想打印出那些能被3整除的平方數呢?那么可以使用模除運算符——`y%3`,當數字可以被3整除時返回0(注意,`x`能被3整除時,`x`的平方必然也可以被3整除)。這個語句可以通過增加一個`if`部分添加到列表推導式中:
```
>>> [x*x for x in range(10) if x % 3 == 0]
[0, 9, 36, 81]
# 也可以增加更多for語句的部分:
>>> [(x, y) for x in range(3) for y in range(3)]
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
# 作為對比,下面的代碼使用兩個for語句創建了相同的列表:
result = []
for x in range(3):
for y in range(3):
result.append((x, y))
print result
# 也可以和if子句聯合使用,像以前一樣:
>>> girls = ["alice", "bernice", "clarice"] >>> boys = ["chris", "arnold", "bob"]
>>> [b+"+"+g for b in boys for g in girls if b[0] == g[0]]
['chris+clarice', 'arnold+alice', 'bob+bernice'] # 這樣就得到了那些名字首字母相同的男孩和女孩。
```
_注:使用普通的圓括號而不是方括號不會得到“元組推導式”。在Python2.3及以前的版本中只會得到錯誤。在最近的版本中,則會得到一個生成器。請參見9.7節獲得更多信息。_
**更優秀的方案**
男孩/女孩名字對的例子其實效率不高,因為它會檢查每個可能的配對。Python有很多解決這個問題的方法,下面的方法是Alex Martelli推薦的:
```
girls = ["alice", "bernice", "clarice"]
boys = ["chris", "arnold", "bob"]
letterGirls = {}
for girl in girls:
letterGirls.setdefault(girl[0], []).append(girl)
print [b+"+"+g for b in boys for g in letterGirls[b[0]]]
```
這個程序創建了一個叫做`letterGirls`的字典,其中每一項都把單字母作為鍵,以女孩名字組成的列表作為值。(`setdefault`字典方法在前一章中已經介紹過)在字典建立后,列表推導式循環整個男孩集合,并且查找那些和當前男孩名字首字母相同的女孩集合。這樣列表推導式就不用嘗試所有的男孩女孩的組合,檢查首字母是否匹配。
## 5.7 三人行
作為本章的結束,讓我們走馬觀花地看一下另外三個語句:`pass`、`del`和`exec`。
### 5.7.1 什么都沒發生
有的時候,程序什么事情都不用做嗎。這種情況不多,但是一旦出現,就應該讓`pass`語句出馬了。
```
>>> pass
>>>
```
似乎沒什么動靜。
那么究竟為什么使用一個什么都不做的語句?它可以在代碼中做占位符使用。比如程序需要一個`if`語句,然后進行測試,但是缺少其中一個語句塊的代碼,考慮下面的情況:
```
if name == "Ralph Auldus Melish":
print "Welcome!"
elif name == "End":
# 還沒完······
elif name == "Bill Gates":
print "Access Denied"
# 代碼不會執行,因為Python中空代碼塊是非法的。解決方案就是在語句塊中加上一個pass語句:
if name == "Ralph Auldus Melish":
print "Welcome!"
elif name == "End":
# 還沒完······
pass
elif name == "Bill Gates":
print "Access Denied"
```
_注:注釋和`pass`語句聯合的代替方案是插入字符串。對于那些沒有完成的函數(參見第六章)和類(參見第七章)來說這個方法尤其有用,因為它們會扮演文檔字符串(docstring)的角色(第六章中會有解釋)。_
### 5.7.2 使用del刪除
一般來說,Python會刪除那些不再使用的對象(因為使用者不會再通過任何變量或數據結構引用它們):
```
>>> scoundrel = {"age": 42, "first name": "Robin", "last name": "of Locksley"} >>> robin = scoundrel >>> scoundrel
{'last name': 'of Locksley', 'first name': 'Robin', 'age': 42}
>>> robin
{'last name': 'of Locksley', 'first name': 'Robin', 'age': 42}
>>> scoundrel = None >>> robin = None
```
首先,`robin`和`scoundrel`都被綁定到同一個字典上。所以當設置`scoundrel`為`None`的時候,字典通過`robin`還是可用的。但是當我把`robin`也設置為`None`的時候,字典就“漂”在內存里了,沒有任何名字綁定到它上面。沒有辦法獲取和使用它,所以Python解釋器(以其無窮的智慧)直接刪除了那個字典(這種行為被稱為_垃圾收集_)。注意,也可以使用`None`之外的其他值。字典同樣會“消失不見”。
另外一個方法就是使用`del`語句(我們在第二章和第四章里面用來刪除序列和字典元素的語句),它不僅會移除一個對象的引用,也會移除那個名字本身。
```
>>> x = 1
>>> del x
>>> x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
# 看起來很簡單,但有時理解起來有些難度。例如,下面的例子中,x和y都指向同一個列表:
>>> x = ["Hello", "world"]
>>> y = x
>>> y[1] = "Python"
>>> x
['Hello', 'Python']
# 會有人認為刪除x后,y也就隨之消失了,但并非如此:
>>> del x
>>> y
['Hello', 'Python']
```
為什么會這樣?`x`和`y`都指向同一個列表。但是刪除`x`并不會影響`y`。原因就是刪除的只是_名稱_,而不是列表本身(值)。事實上,在Python中是沒有辦法刪除值的(也不需要過多考慮刪除值的問題,因為在某個值不再使用的時候,Python解釋器會負責內存的回收)。
### 5.7.3 使用`exec`和`eval`執行和求值字符串
有些時候可能會需要動態地創造Python代碼,然后將其作為語句執行或作為表達式計算,這可能近似于“黑暗魔法”——在此之前,一定要慎之又慎,仔細考慮。
_警告:本節中,會學到如何執行存儲在字符串中的Python代碼。這樣做會有很嚴重的潛在安全漏洞。如果程序將用戶提供的一段內容中的一部分字符串作為代碼執行,程序可能會失去對代碼執行的控制,這種情況在網絡應用程序——比如CGI腳本中尤其危險,這部分內容會在第十五章介紹。_
1\. `exec`
執行一個字符串的語句是`exec`(在Python3.0中,`exec`是一個函數而不是語句):
```
>>> exec "print 'Hello, world!'"
Hello, world!
```
但是,使用簡單形式的`exec`語句絕不是好事。很多情況下可以給它提供命名空間——可以放置變量的地方。你想這樣做,從而使代碼不會干擾命名空間(也就是改變你的變量),比如,下面的代碼中使用了名稱`sqrt`:
```
>>> from math import sqrt
>>> exec "sqrt = 1"
>>> sqrt(4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
```
想想看,為什么一開始我們要這樣做?`exec`語句最有用的地方在于可以動態地創建代碼字符串。如果字符串是從其他地方獲得——很有可能是用戶——那么幾乎不能確定其中到底包含什么代碼。所以為了安全起見,可以增加一個字典,起到命名空間的作用。
_注:命名空間的概念,或稱為作用域(`scope`),是非常重要的知識,下一章會深入學習,但是現在可以把它想象成保存變量的地方,類似于不可見的字典。所以在程序執行`x=1`這類賦值語句時,就將鍵`x`和值`1`放在當前的命名空間內,這個命名空間一般來說都是全局命名空間(到目前為止絕大多數都是如此),但這并不是必須的。_
可以通過增加 `in<scope>` 來實現,其中`<scope>`就是起到放置代碼字符串命名空間作用的字典。
```
>>> from math import sqrt
>>> scope = {}
>>> exec "sqrt = 1" in scope
>>> sqrt(4) 2.0
>>> scope["sqrt"]
1
```
可以看到,潛在的破壞性代碼并不會覆蓋`sqrt`函數,原來的函數能正常工作,而通過`exec`賦值的變量`sqrt`只在它的作用域內有效。
注意,如果需要將`scope`打印出來的話,會看到其中包含很多東西,因為內建的`__builtins__`字典自動包含所有的內建函數和值:
```
>>> len(scope) 2
>>> scope.keys()
['__builtins__', 'sqrt']
```
2\. `eval`
`eval`(用于“求值”)是類似于`exec`的內建函數。`exec`語句會執行一系列_Python語句_,而`eval`會計算_Python表達式_(以字符串形式書寫),并且返回結果值。(`exec`語句并不返回任何對象,因為它本身就是語句)例如,可以使用下面的代碼創建一個Python計算器:
```
>>> eval(raw_input("Enter an arithmetic expression: "))
Enter an arithmetic expression: 6 + 18 * 2
42
```
_注:表達式`eval(raw_input(...))`事實上等同于`input(...)`。在Python3.0中,`raw_input`被重命名為`input`。_
跟`exec`一樣,`eval`也可以使用命名空間。盡管表達式幾乎不像語句那樣為變量重新賦值。(實際上,可以給`eval`語句提供兩個命名空間,一個全局的一個局部的。全局的必須是字典,局部的可以是任何形式的映射。)
警告:盡管表達式一般不給變量重新賦值,但它們的確可以(比如可以調用函數給全局變量重新賦值)。所以使用`eval`語句對付一些不可信任的代碼并不比`exec`語句安全。目前,在Python內沒有任何執行不可信任代碼的安全方式。一個可選的方案是使用Python的實現,比如Jython(參見第十七章),以及使用一些本地機制,比如Java的sandbox功能。
**初探作用域**
給`exec`或者`eval`語句提供命名空間時,還可以在真正使用命名空間前放置一些值進去:
```
>>> scope = {}
>>> scope["x"] = 2
>>> scope["y"] = 3
>>> eval("x * y", scope)
6
# 同理,exec或者eval調用的作用域也能在另一個上面使用:
>>> scope = {}
>>> exec "x = 2" in scope
>>> eval("x * x", scope)
4
```
事實上,`exec`語句和`eval`語句并不常用,但是它們可以作為后兜里的得力工具(當然,這僅僅是比喻而已)。
## 5.8 小結
本章中介紹了幾類語句和其他知識。
√ 打印。`print`語句可以用來打印由逗號隔開的多個值。如果語句以逗號結尾,后面的`print`語句會在同一行內繼續打印。
√ 導入。有些時候,你不喜歡你想導入的函數名——還有可能由于其他原因使用了這個函數名。可以使用`from ... as ...` 語句進行函數的局部重命名。
√ 賦值。通過序列解包和鏈式賦值功能,多個變量賦值可以一次性賦值,通過增量賦值可以原地改變變量。
√ 塊。塊是通過縮排使語句成組的一種方法。它們可以在條件以及循環語句中使用,后面的章節中會介紹,塊也可以在函數和類中使用。
√ 條件。條件語句可以根據條件(布爾表達式)執行或不執行一個語句塊。幾個條件可以串聯使用`if/elif/else`。這個主題下還有一種變體叫做條件表達式,形如 `a if b else c`(這種表達式其實類似于三元運算)。
√ 斷言。斷言簡單來說就是肯定某事(布爾表達式)為真。也可在后面跟上這么認為的原因。如果表達式為真,斷言就會讓程序崩潰(事實上是產生異常——第八章會介紹)。比起錯誤潛藏在程序中,直到你不知道它源在何處,更好的方法是盡早找到錯誤。
√ 循環。可以為序列(比如一個范圍內的數字)中的每一個元素執行一個語句塊,或者在條件為真的時候繼續執行一段語句。可以使用`continue`語句跳過塊中的其他語句,然后繼續下一次迭代,或者使用`break`語句跳出循環。還可以選擇在循環結尾加上`else`子句,當沒有執行循環內部的`break`語句的時候便會執行`else`子句中的內容。
√ 列表推導式。它不是真正的語句,而是看起來像循環的表達式,這也是我將它歸到循環語句中的原因。通過列表推導式,可以從舊列表中產生新的列表、對元素應用函數、過濾不需要的元素,等等。這個功能很強大,但是很多情況下,直接使用循環和條件語句(工作也能完成),程序會更易讀。
√ `pass`、`del`、`exec`和`eval`語句。`pass`語句什么都不做,可以作為占位符使用。`del`語句用來刪除變量,或者數據結構的一部分,但是不能用來刪除值。`exec`語句用于執行Python程序相同的方式來執行字符串。內建的`eval`函數對寫在字符串中的表達式進行計算并且返回結果。
### 5.8.1 本章的新函數
本章涉及的新函數如表5-2所示。
表5-2 本章的新函數
```
chr(n) 當傳入序號n時,返回n所代表的包含一個字符的字符串(0≤n<256)。
eval(source[, globals[, locals]]) 將字符串作為表達式計算,并且返回值。
enumerate(seq) 產生用于迭代的(索引,值)對。
ord(c) 返回單字符字符串的int值。
range([start,] stop[, step]) 創建整數的列表。
reversed(seq) 產生seq中值的反向版本,用于迭代。
sorted(seq[, cmp][, key][, reverse]) 返回seq中值排序后的列表。
xrange([start,] stop[, step]) 創造xrange對象用于迭代。
zip(seq1, seq2 ...) 創造用于并行迭代的新序列。
```
### 5.8.2 接下來學什么
現在基本知識已經學完了。實現任何自己能想到的算法已經沒問題了,也可以讓程序讀取參數并且打印結果。下面兩章中,將會介紹可以創建較大程序,卻不讓代碼冗長的知識。這也就是我們所說的_抽象_(abstraction)。