2020年10月29日星期四

013 與小卡特一起學 Python 第 13 章 函數

 申明本站飛宇網 https://feiyetopro.blogspot.com/自網路收集整理之書籍文章影音僅供預覽交流學習研究,其[書籍、文章、影音]情節內容, 評論屬其個人行為, 與本網站無關。版權歸原作者和出版社所有,請在下載 24 小時內刪除,不得用作商業用途;如果您喜歡其作品,請支持訂閱購買[正版]謝謝!


13 章 函數

我們的程式很快就會變得越來越大,越來越複雜。需要一些方法把它們分成較小的部分進行組織,這樣更易於編寫,也更容易明白。

要把程式分解成較小的部分,主要有 3 種方法。函數(function)就像是代碼的積木,可以反復地使用。利用物件(object),可以把程式中的各部分描述為自包含的單元。模組(module)就是包含程式各部分的單獨的檔。在這一章中,我們將學習函數,後面兩章會討論物件和模組。學習完這些知識,我們就具備了所需要的全部基本工具,可以開始使用圖形和聲音並且創建遊戲了。

 

13.1 函數——積木

最簡單地講,函數就是可以完成某個工作的代碼塊。這是可以用來構建更大程式的一個小部分。可以把這個小部分與其他部分放在一起,就像用積木搭房子一樣。


創建或定義函數要使用 Python  def 關鍵字。然後可以利用函數名來使用或調用這個函數。下面先來看一個簡單的例子。

創建一個函數


代碼清單 13-1 中的代碼首先定義了一個函數,然後使用這個函數。這個函數會在螢幕上列印一個郵寄地址。

代碼清單 13-1 創建和使用函數


1 行中,我們使用 def 關鍵字定義了一個函數。在函數名後面有一對括弧(),然後是一個冒號:

def printMyAddress():

 

後面很快就會解釋這個括弧做什麼用。冒號告訴 Python 接下來是一個代碼塊(就像 for 迴圈、while 迴圈和 if 語句中一樣)。

下面就是構成這個函數的代碼。


代碼清單 13-1 的最後一行是主程序:這裡給出函數名和括弧來調用這個函數。程式就從這裡開始運行。正是這一行讓程式開始運行剛才定義的函數中的代碼。

主程序調用函數時,就像是這個函數在説明主程序完成它的任務。

def 塊中的代碼並不是主程序的一部分,所以程式運行時,它會跳過這一部分,從 def 塊以外的第一行代碼開始運行。右圖顯示了調用函數時會發生什麼。我在程式最後額外增加了一行代碼,它會在函數完成後列印一條消息。

這個圖中包括以下步驟。

1. 從這裡開始。這是主程序的開始。

2. 調用函數時,跳到函數中的第一行代碼。

3. 執行函數中的每一行代碼。

4. 函數完成時,從離開主程序的那個位置繼續執行。

 

13.2 調用函數

調用函數是指運行函數裡的代碼。如果我們定義了一個函數,但是從來不調用它,這些代碼就永遠也不會運行。

調用函數時要使用函數名和一對括弧。有時括弧裡還會有些東西,有時也可能什麼也沒有。

試著運行代碼清單 13-1 中的程式,看看會發生什麼。你會看到這樣的結果:

>>> =================== RESTART ===================

>>> 

Warren Sande

123 Main Street

Ottawa, Ontario, Canada

K2M 2E9

>>> 

 

從下面這個更簡單的程式也可以得到同樣的輸出:

print "Warren Sande"

print "123 Main Street"

print "Ottawa, Ontario, Canada"

print "K2M 2E9"

print

 

那為什麼要自找麻煩使用代碼清單 13-1 中的函數讓問題更複雜呢?

使用函數的主要原因是,一旦定義了函數,就可以通過調用反復地使用。所以如果我們想把位址列印 5 次,可以這樣做:

printMyAddress()

printMyAddress()

printMyAddress()

printMyAddress()

printMyAddress()

 

輸出將是:

Warren Sande

123 Main Street

Ottawa, Ontario, Canada

K2M 2E9

 

Warren Sande

123 Main Street

Ottawa, Ontario, Canada

K2M 2E9

 

Warren Sande

123 Main Street

Ottawa, Ontario, Canada

K2M 2E9

 

Warren Sande

123 Main Street

Ottawa, Ontario, Canada

K2M 2E9

 

Warren Sande

123 Main Street

Ottawa, Ontario, Canada

K2M 2E9

 

你可能會說:可以不用函數,用迴圈也能做同樣的事情。


我就知道你會這麼講……對於這種情況,你確實可以用迴圈做同樣的事情。不過,如果希望在程式的不同位置列印位址,而不是全部都一次完成,迴圈就實現不了了。

使用函數還有一個原因,每次函數運行時可以讓它有不同的表現。我們將在下一節瞭解這是如何做到的。

 

13.3 向函數傳遞參數

現在來看括弧做什麼用:它用來傳遞參數(argument)!


不,卡特,電腦非常聽話,它們永遠也不會爭論1。在程式設計中,參數這個詞是指你交給函數的一條資訊。我們把這稱為:你向函數傳遞參數。

1argument 也有爭論的意思,卡特顯然是把這裡的 argument 理解為爭論了。——編者注


假設你希望對你的所有家庭成員使用這個位址列印函數。所有人的位址都是一樣的,但是每一次人名會有所不同。不能在函數中把人名硬編碼寫成 Warren Sande,你可以建立一個變數。調用函數時將這個變數傳遞到函數。

要說明這是如何工作的,最容易的方法就是舉例子。在代碼清單 13-2 中,我修改了位址列印函數,要使用一個對應人名的參數。參數是有名字的,就像其他變數一樣。我把這個變數命名為 myName

函數運行時,變數 myName 會填入調用函數時為它傳入的任何參數。調用函數時,我們把參數放在括弧裡,通過這種方式將參數傳入函數。

因此,在代碼清單 13-2 中,參數 myName 賦值為 Carter Sande

代碼清單 13-2 向函數傳遞參數


運行代碼,你會得到期望的結果:

>>> ===================== RESTART =====================

>>> 

Carter Sande

123 Main Street

Ottawa, Ontario, Canada

K2M 2E9

 

這看上去與第一個程式(沒有使用參數)得到的輸出完全相同。不過,我們每次可以用不同方式列印位元址,比如:

printMyAddress("Carter Sande")

printMyAddress("Warren Sande")

printMyAddress("Kyra Sande")

printMyAddress("Patricia Sande")

 

現在每次調用函數時輸出都不同。人名會變,因為我們每次都向函數傳入了不同的人名。

>>> ============================ RESTART ============================

>>> 

Carter Sande

123 Main Street

Ottawa, Ontario, Canada

K2M 2E9

 

Warren Sande

123 Main Street

Ottawa, Ontario, Canada

K2M 2E9

 

Kyra Sande

123 Main Street

Ottawa, Ontario, Canada

K2M 2E9

 

Patricia Sande

123 Main Street

Ottawa, Ontario, Canada

K2M 2E9

 

注意,我們向函數傳入什麼值,函數中就會使用什麼值,並作為位元址的人名部分列印出來。


如果每次函數運行時有多個資訊不同,就需要多個參數。下麵就來討論這個問題。

 

13.4 有多個參數的函數

在代碼清單 13-2 中,我們的函數只有一個參數。不過函數完全可以有多個參數。實際上,你想要有多少個參數就可以有多少個參數。下面來看一個帶兩個參數的例子,我想,通過這個例子,你會對多個參數有所認識。在這個基礎上,你可以根據具體需要為程式中的函數增加參數。


術語箱

談到向函數傳遞資訊時,你可能還會聽到這樣一個詞:形參(parameter)。有些人說參數(argument)和形參(parameter)可以互換。所以你可以說,

我向這個函數傳遞兩個形參(parameter,或者

我向這個函數傳遞兩個參數(argument

不過有些人認為,談到傳遞部分(調用函數)時應當稱作實參(argument),而談到接收部分(函數內部)時應該稱為形參(parameter)。


使用參數(不論是 argument 還是 parameter)討論向函數傳遞值時,程式師都明白你是什麼意思。

要向街道上的每一個人發送卡特的信,我們的位址列印函數需要兩個參數:一個對應人名,另一個對應門牌號碼。代碼清單 13-3 顯示了這個函數。

代碼清單 13-3 帶兩個參數的函數


使用多個參數時,要用逗號來分隔,就像清單中的元素一樣,這就引入了下一個話題……

多少才算太多

前面說過,想向函數傳遞多少參數就可以有多少個參數。這一點不假,但是如果你的函數有超過 5 6 個參數,可能就應該考慮採用別的做法了。一種做法是把所有參數收集到一個列表中,然後把這個清單傳遞到函數。這樣一來,就只是傳遞一個變數(清單變數),只不過其中包含有一組值。這樣可以讓你的代碼更易讀。

 



13.5 返回值的函數

目前為止,函數只是為我們做一些工作。不過函數的一個突出作用是:它們還可以向你發回一些東西。

我們已經知道,可以向函數發送資訊(參數),不過函數還可以向調用者發回資訊。從函數返回的值稱為結果(result)或返回值(return value)。


返回一個值

要讓函數返回一個值,需要在函數中使用 Python 關鍵字 return。下面給出一個例子:

def calculateTax(price, tax_rate):

    taxTotal = price + (price * tax_rate)

    return taxTotal

 

這會把值 taxTotal 發回到調用這個函數的程式部分。

不過發回這個值時,它會去哪裡呢?返回值會回到調用這個函數的代碼。看下面的例子:

totalPrice = calculateTax(7.99, 0.06)

 

calculateTax 函數會返回一個值:8.4694,這個值將賦給 totalPrice

使用運算式的任何地方都可以使用函數來返回值。可以把返回值賦給一個變數(就像前面一樣),也可以在另一個運算式中使用,或者列印出來,例如:

>>>print calculateTax(7.99, 0.06)

8.4694

>>>total = calculateTax(7.99, 0.06) + calculateTax(6.59, 0.08)

 

對返回值也可以不做任何處理,就像這樣:

>>>calculateTax(7.49, 0.07)

 

在上面這個例子中,函數會運行,計算出稅後總價格,不過我們沒有使用這個結果。

下面用一個有返回值的函數建立程式。在代碼清單 13-4 中,calculateTax() 函數返回了一個值。向這個函數提供稅前價格和稅率,它會返回稅後價格。我們把這個值賦給一個變數。所以不像前面那樣只是使用函數的名,這裡還需要一個變數和一個等號(=),然後是函數名。變數會賦為 calculateTax() 函數返回的結果。

代碼清單 13-4 創建和使用有返回值的函數


試著鍵入代碼清單 13-4 中的程式,保存並運行這個程式。注意這個代碼中的稅率固定為 0.06(等於 6 個百分點)。如果程式必須處理不同的稅率,可以讓用戶輸入價格的同時還要輸入稅率。

 

13.6 變數作用域

你可能已經注意到,有些變數在函數之外,如 totalPrice,還有一些變數在函數內部,如 total。這些變數只是同一個東西的兩個不同名字。這就像第2章中所說的 YourTeacher = MyTeacher


在我們的 calculateTax 例子中,totalPrice  total 是貼在同一個東西上的兩個標籤。對於函數而言,函數內的名字只是在函數運行時才會創建。在函數運行之前或者完成運行之後甚至根本不存在。Python 提供了記憶體管理(memory management),可以自動完成這個工作。Python 在函數運行時會創建新的名字在函數內使用,當函數完成時會把它們刪除。最後這部分很重要:函數運行結束時,其中的所有名字都不再存在。

函數運行時,函數之外的名字被擱置一邊,而沒有用到。只有函數內部的名字會被用到。程式中使用(或者可以使用)變數的部分稱為這個變數的作用域(scope)。

區域變數

在代碼清單 13-4 中,變數 price  total 只在函數內使用。我們說 pricetotal  tax_rate 的作用域是 calculateTax() 函數。這也稱為這些變數是局部的(local)。pricetotal  tax_rate 變數是 calculateTax() 函數中的區域變數。

要瞭解這是什麼意思,一種方法是向代碼清單 13-4 中的程式增加一行代碼,嘗試在函數之外的某個位置列印 price 的值。代碼清單 13-5 做了這個嘗試。

代碼清單 13-5 嘗試列印一個區域變數


如果運行這個程式,會得到這樣一個錯誤:


錯誤消息的最後一行解釋了這個問題的原委:在 calculateTax() 函數以外,變數 price 根本沒有定義。它只是在函數運行時才存在。試圖在這個函數之外列印 price 的值時(此時函數並沒有運行),就會得到一個錯誤。

全域變數

與區域變數 price 對應,代碼清單 13-5 中的變數 my_price  totalPrice 在函數之外定義(程式主部分中)。我們使用全域變數(global)表示有更大作用域的變數。在這種情況下,更大是指程式的主部分,而不是函數內部。如果擴展代碼清單 13-5 中的程式,完全可以在另一個位置使用變數 my_price  totalPrice,它們仍然有之前給定的值。它們仍在合法的作用域中(in scope)。因為我們可以在程式的任何地方使用這些變數,所以把它們稱作全域變數(global variable)。

在代碼清單 13-5 中,試圖在函數之外列印一個函數內的變數時,會得到一條錯誤消息。這個變數不存在,也就是說它在作用域之外(out of scope)。如果反過來:從函數內列印一個全域變數,你認為會發生什麼?

代碼清單 13-6 試圖從 calculateTax() 函數中列印變數 my_price。試試看會發生什麼。

代碼清單 13-6 在函數中使用全域變數


可以嗎?真的可以!不過為什麼呢?

開始討論變數作用域時,我曾經說過,Python 利用記憶體管理在函數運行時自動創建區域變數。記憶體管理還會做其他事情。如果在函數中使用主程序中定義的變數名,Python 允許你使用這個全域變數,只要你不要試圖改變它。

所以你可以這樣做:

print my_price

 

或者這樣做:

your_price = my_price

 

因為它們都不會改變 my_price

如果函數的任何部分試圖改變這個變數,Python 會創建一個新的區域變數。所以如果你打算這樣做:

my_price = my_price + 10

 

那麼 my_price 將是 Python 在函數運行時創建的一個新的區域變數。

在代碼清單 13-6 的例子中,列印出的值是全域變數 my_price,因為函數沒有改變這個變數。代碼清單 13-7 中的程式表明,如果確實試圖在函數內部改變全域變數,你會得到一個新的區域變數。試著運行這個程式,看看會有什麼結果。

代碼清單 13-7 嘗試在函數內部修改一個全域變數


如果運行代碼清單 13-7 中的代碼,會有下面的輸出:


可以看到,現在有兩個名為 my_price 的不同變數,分別有不同的值。一個是 calculateTax() 函數中的區域變數,我們將它設置為 10 000。另一個是主程序中定義的全域變數,用來獲取用戶的輸入,它的值是 7.99

 

13.7 強制為全域

上一節中,我們看到,如果試圖從函數內改變一個全域變數的值,Python 會創建一個新的區域變數。這是為了防止函數無意地改變全域變數。

不過,有些情況下確實要在函數中改變一個全域變數。這該怎麼做呢?

可以用 Python 的一個關鍵字 global 來做到。可以這樣來使用:


如果使用 global 關鍵字,Python 不會建立名為 my_price 的區域變數,而是會使用名為 my_price 的全域變數。另外,如果還沒有名為 my_price 的全域變數,Python 就會創建一個。

 

13.8 關於變數命名的一點建議

在前面的幾節中已經看到,可以對全域變數和區域變數使用相同的變數名。Python 會在需要時自動創建新的區域變數,或者也可以用 global 關鍵字阻止它創建。不過,我強烈建議你不要重複使用變數名。

你可能已經從一些例子中注意到,往往很難知道一個變數是局部的還是全域的,這讓代碼更加混亂,因為存在同名的不同變數。而且,只要有混亂,錯誤就會乘虛而入。

所以對目前的狀況來說,建議你對區域變數和全域變數使用不同的名字。這樣就不會有混亂,也能把錯誤拒之門外。


你學到了什麼

在這一章,你學到了以下內容。

·    什麼是函數。

·    什麼是參數(argument parameter)。

·    如何向函數傳遞一個參數。

·    如何向函數傳遞多個參數。

·    如何讓函數向調用者返回一個值。

·    變數作用域是什麼,什麼是區域變數和全域變數。

·    如何在函數中使用全域變數。

測試題

1. 使用哪個關鍵字來創建函數?

2. 如何調用函數?

3. 如何向函數傳遞資訊(參數)?

4. 函數最多可以有多少個參數?

5. 如何從函數返回資訊?

6. 函數運行結束後,函數中的區域變數會發生什麼?

動手試一試

1. 編寫一個函數,用大寫字母列印你的名字,就像這樣:

  CCCC       A       RRRRR  TTTTTTT  EEEEEE  RRRRR

 C    C     A A      R    R    T     E       R    R

C          A   A     R    R    T     EEEE    R    R

C         AAAAAAA    RRRRR     T     E       RRRRR

 C    C  A       A   R    R    T     E       R    R

  CCCC  A         A  R     R   T     EEEEEE  R     R

 

編寫一個程式多次調用這個函數。

2. 建立一個函數,可以列印全世界任何人名、位址、街道、城市、州或省、郵遞區號和國家。(提示:這需要 7 個參數。可以把它們作為單獨的參數傳入,也可以作為一個列表。)

3. 嘗試使用代碼清單 13-7 的例子,不過要求 my_price 是全域變數,以便看到結果輸出有什麼區別。

4. 編寫一個函數計算零錢的總面值,包括五分幣、二分幣和一分幣(類似於第 5 章中最後一個動手試一試問題)。函數應當返回這些硬幣的總面值。然後編寫一個程式調用這個函數。程式運行時應當得到類似下麵的輸出:

quarters: 3

dimes: 6

nickels: 7

pennies: 2

total is $1.72

 

 

0 留言:

發佈留言