摘要:函數體由表達式組成。我們說頭部控制語句組。于是,函數體內的賦值語句不會影響全局幀。包含了多種假值,包括和布爾值。布爾值表示了邏輯表達式中的真值。執行測試以及返回布爾值的函數通常以開頭,并不帶下劃線例如等等。返回值之后會和預期結果進行比對。
1.5 控制
來源:1.5 Control
譯者:飛龍
協議:CC BY-NC-SA 4.0
我們現在可以定義的函數能力有限,因為我們還不知道一種方法來進行測試,并且根據測試結果來執行不同的操作。控制語句可以讓我們完成這件事。它們不像嚴格的求值子表達式那樣從左向右編寫,并且可以從它們控制解釋器下一步做什么當中得到它們的名稱。這可能基于表達式的值。
1.5.1 語句目前為止,我們已經初步思考了如何求出表達式。然而,我們已經看到了三種語句:賦值、def和return語句。這些 Python 代碼并不是表達式,雖然它們中的一部分是表達式。
要強調的是,語句的值是不相干的(或不存在的),我們使用執行而不是求值來描述語句。
每個語句都描述了對解釋器狀態的一些改變,執行語句會應用這些改變。像我們之前看到的return和賦值語句那樣,語句的執行涉及到求解所包含的子表達式。
表達式也可以作為語句執行,其中它們會被求值,但是它們的值會舍棄。執行純函數沒有什么副作用,但是執行非純函數會產生效果作為函數調用的結果。
考慮下面這個例子:
>>> def square(x): mul(x, x) # Watch out! This call doesn"t return a value.
這是有效的 Python 代碼,但是并不是想表達的意思。函數體由表達式組成。表達式本身是個有效的語句,但是語句的效果是,mul函數被調用了,然后結果被舍棄了。如果你希望對表達式的結果做一些事情,你需要這樣做:使用賦值語句來儲存它,或者使用return語句將它返回:
>>> def square(x): return mul(x, x)
有時編寫一個函數體是表達式的函數是有意義的,例如調用類似print的非純函數:
>>> def print_square(x): print(square(x))
在最高層級上,Python 解釋器的工作就是執行由語句組成的程序。但是,許多有意思的計算工作來源于求解表達式。語句管理程序中不同表達式之間的關系,以及它們的結果會怎么樣。
1.5.2 復合語句通常,Python 的代碼是語句的序列。一條簡單的語句是一行不以分號結束的代碼。復合語句之所以這么命名,因為它是其它(簡單或復合)語句的復合。復合語句一般占據多行,并且以一行以冒號結尾的頭部開始,它標識了語句的類型。同時,一個頭部和一組縮進的代碼叫做子句(或從句)。復合語句由一個或多個子句組成。
: ... : ... ...
我們可以這樣理解我們已經見到的語句:
表達式、返回語句和賦值語句都是簡單語句。
def語句是復合語句。def頭部之后的組定義了函數體。
為每種頭部特化的求值規則指導了組內的語句什么時候以及是否會被執行。我們說頭部控制語句組。例如,在def語句的例子中,我們看到返回表達式并不會立即求值,而是儲存起來用于以后的使用,當所定義的函數最終調用時就會求值。
我們現在也能理解多行的程序了。
執行語句序列需要執行第一條語句。如果這個語句不是重定向控制,之后執行語句序列的剩余部分,如果存在的話。
這個定義揭示出遞歸定義“序列”的基本結構:一個序列可以劃分為它的第一個元素和其余元素。語句序列的“剩余”部分也是一個語句序列。所以我們可以遞歸應用這個執行規則。這個序列作為遞歸數據結構的看法會在隨后的章節中再次出現。
這一規則的重要結果就是語句順序執行,但是隨后的語句可能永遠不會執行到,因為有重定向控制。
實踐指南:在縮進代碼組時,所有行必須以相同數量以及相同方式縮進(空格而不是Tab)。任何縮進的變動都會導致錯誤。
1.5.3 定義函數 II:局部賦值一開始我們說,用戶定義函數的函數體只由帶有一個返回表達式的一個返回語句組成。實際上,函數可以定義為操作的序列,不僅僅是一條表達式。Python 復合語句的結構自然讓我們將函數體的概念擴展為多個語句。
無論用戶定義的函數何時被調用,定義中的子句序列在局部環境內執行。return語句會重定向控制:無論什么時候執行return語句,函數調用的流程都會中止,返回表達式的值會作為被調用函數的返回值。
于是,賦值語句現在可以出現在函數體中。例如,這個函數以第一個數的百分數形式,返回兩個數量的絕對值,并使用了兩步運算:
>>> def percent_difference(x, y): difference = abs(x-y) return 100 * difference / x >>> percent_difference(40, 50) 25.0
賦值語句的效果是在當前環境的第一個幀上,將名字綁定到值上。于是,函數體內的賦值語句不會影響全局幀。函數只能操作局部作用域的現象是創建模塊化程序的關鍵,其中純函數只通過它們接受和返回的值與外界交互。
當然,percent_difference函數也可以寫成一個表達式,就像下面這樣,但是返回表達式會更加復雜:
>>> def percent_difference(x, y): return 100 * abs(x-y) / x
目前為止,局部賦值并不會增加函數定義的表現力。當它和控制語句組合時,才會這樣。此外,局部賦值也可以將名稱賦為間接量,在理清復雜表達式的含義時起到關鍵作用。
新的環境特性:局部賦值。
1.5.4 條件語句Python 擁有內建的絕對值函數:
>>> abs(-2) 2
我們希望自己能夠實現這個函數,但是我們當前不能直接定義函數來執行測試并做出選擇。我們希望表達出,如果x是正的,abs(x)返回x,如果x是 0,abx(x)返回 0,否則abs(x)返回-x。Python 中,我們可以使用條件語句來表達這種選擇。
>>> def absolute_value(x): """Compute abs(x).""" if x > 0: return x elif x == 0: return 0 else: return -x >>> absolute_value(-2) == abs(-2) True
absolute_value的實現展示了一些重要的事情:
條件語句。Python 中的條件語句包含一系列的頭部和語句組:一個必要的if子句,可選的elif子句序列,和最后可選的else子句:
if: elif : else:
當執行條件語句時,每個子句都按順序處理:
求出頭部中的表達式。
如果它為真,執行語句組。之后,跳過條件語句中隨后的所有子句。
如果能到達else子句(僅當所有if和elif表達式值為假時),它的語句組才會被執行。
布爾上下文。上面過程的執行提到了“假值”和“真值”。條件塊頭部語句中的表達式也叫作布爾上下文:它們值的真假對控制流很重要,但在另一方面,它們的值永遠不會被賦值或返回。Python 包含了多種假值,包括 0、None和布爾值False。所有其他數值都是真值。在第二章中,我們就會看到每個 Python 中的原始數據類型都是真值或假值。
布爾值。Python 有兩種布爾值,叫做True和False。布爾值表示了邏輯表達式中的真值。內建的比較運算符,>、<、>=、<=、==、!=,返回這些值。
>>> 4 < 2 False >>> 5 >= 5 True
第二個例子讀作“5 大于等于 5”,對應operator模塊中的函數ge。
>>> 0 == -0 True
最后的例子讀作“0 等于 -0”,對應operator模塊的eq函數。要注意 Python 區分賦值(=)和相等測試(==)。許多語言中都有這個慣例。
布爾運算符。Python 也內建了三個基本的邏輯運算符:
>>> True and False False >>> True or False True >>> not False True
邏輯表達式擁有對應的求值過程。這些過程揭示了邏輯表達式的真值有時可以不執行全部子表達式而確定,這個特性叫做短路。
為了求出表達式
求出子表達式
如果結果v是假值,那么表達式求值為v。
否則表達式的值為子表達式
為了求出表達式
求出子表達式
如果結果v是真值,那么表達式求值為v。
否則表達式的值為子表達式
為了求出表達式not
求出
這些值、規則和運算符向我們提供了一種組合測試結果的方式。執行測試以及返回布爾值的函數通常以is開頭,并不帶下劃線(例如isfinite、isdigit、isinstance等等)。
1.5.5 迭代除了選擇要執行的語句,控制語句還用于表達重復操作。如果我們編寫的每一行代碼都只執行一次,程序會變得非常沒有生產力。只有通過語句的重復執行,我們才可以釋放計算機的潛力,使我們更加強大。我們已經看到了重復的一種形式:一個函數可以多次調用,雖然它只定義一次。迭代控制結構是另一種將相同語句執行多次的機制。
考慮斐波那契數列,其中每個數值都是前兩個的和:
0, 1, 1, 2, 3, 5, 8, 13, 21, ...
每個值都通過重復使用“前兩個值的和”的規則構造。為了構造第 n 個值,我們需要跟蹤我們創建了多少個值(k),以及第 k 個值(curr)和它的上一個值(pred),像這樣:
>>> def fib(n): """Compute the nth Fibonacci number, for n >= 2.""" pred, curr = 0, 1 # Fibonacci numbers k = 2 # Position of curr in the sequence while k < n: pred, curr = curr, pred + curr # Re-bind pred and curr k = k + 1 # Re-bind k return curr >>> fib(8) 13
要記住逗號在賦值語句中分隔了多個名稱和值。這一行:
pred, curr = curr, pred + curr
具有將curr的值重新綁定到名稱pred上,以及將pred + curr的值重新綁定到curr上的效果。所有=右邊的表達式會在綁定發生之前求出來。
while子句包含一個頭部表達式,之后是語句組:
while:
為了執行while子句:
求出頭部表達式。
如果它為真,執行語句組,之后返回到步驟 1。
在步驟 2 中,整個while子句的語句組在頭部表達式再次求值之前被執行。
為了防止while子句的語句組無限執行,它應該總是在每次通過時修改環境的狀態。
不終止的while語句叫做無限循環。按下
函數的測試是驗證函數的行為是否符合預期的操作。我們的函數現在已經足夠復雜了,我們需要開始測試我們的實現。
測試是系統化執行這個驗證的機制。測試通常寫為另一個函數,這個函數包含一個或多個被測函數的樣例調用。返回值之后會和預期結果進行比對。不像大多數通用的函數,測試涉及到挑選特殊的參數值,并使用它來驗證調用。測試也可作為文檔:它們展示了如何調用函數,以及什么參數值是合理的。
要注意我們也將“測試”這個詞用于if或while語句的頭部中作為一種技術術語。當我們將“測試”這個詞用作表達式,或者用作一種驗證機制時,它應該在語境中十分明顯。
斷言。程序員使用assert語句來驗證預期,例如測試函數的輸出。assert語句在布爾上下文中只有一個表達式,后面是帶引號的一行文本(單引號或雙引號都可以,但是要一致)如果表達式求值為假,它就會顯示。
>>> assert fib(8) == 13, "The 8th Fibonacci number should be 13"
當被斷言的表達式求值為真時,斷言語句的執行沒有任何效果。當它是假時,asset會造成執行中斷。
為fib編寫的test函數測試了幾個參數,包含n的極限值:
>>> def fib_test(): assert fib(2) == 1, "The 2nd Fibonacci number should be 1" assert fib(3) == 1, "The 3nd Fibonacci number should be 1" assert fib(50) == 7778742049, "Error at the 50th Fibonacci number"
在文件中而不是直接在解釋器中編寫 Python 時,測試可以寫在同一個文件,或者后綴為_test.py的相鄰文件中。
Doctest。Python 提供了一個便利的方法,將簡單的測試直接寫到函數的文檔字符串內。文檔字符串的第一行應該包含單行的函數描述,后面是一個空行。參數和行為的詳細描述可以跟隨在后面。此外,文檔字符串可以包含調用該函數的簡單交互式會話:
>>> def sum_naturals(n): """Return the sum of the first n natural numbers >>> sum_naturals(10) 55 >>> sum_naturals(100) 5050 """ total, k = 0, 1 while k <= n: total, k = total + k, k + 1 return total
之后,可以使用 doctest 模塊來驗證交互。下面的globals函數返回全局變量的表示,解釋器需要它來求解表達式。
>>> from doctest import run_docstring_examples >>> run_docstring_examples(sum_naturals, globals())
在文件中編寫 Python 時,可以通過以下面的命令行選項啟動 Python 來運行一個文檔中的所有 doctest。
python3 -m doctest
高效測試的關鍵是在實現新的函數之后(甚至是之前)立即編寫(以及執行)測試。只調用一個函數的測試叫做單元測試。詳盡的單元測試是良好程序設計的標志。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/45498.html
摘要:對象表示信息,但是同時和它們所表示的抽象概念行為一致。通過綁定行為和信息,對象提供了可靠獨立的日期抽象。名稱來源于實數在中表示的方式浮點表示。另一方面,對象可以表示很大范圍內的分數,但是不能表示所有有理數。 2.1 引言 來源:2.1 Introduction 譯者:飛龍 協議:CC BY-NC-SA 4.0 在第一章中,我們專注于計算過程,以及程序設計中函數的作用。我們看到了...
摘要:為通用語言設計解釋器的想法可能令人畏懼。但是,典型的解釋器擁有簡潔的通用結構兩個可變的遞歸函數,第一個求解環境中的表達式,第二個在參數上調用函數。這一章接下來的兩節專注于遞歸函數和數據結構,它們是理解解釋器設計的基礎。 3.1 引言 來源:3.1 Introduction 譯者:飛龍 協議:CC BY-NC-SA 4.0 第一章和第二章描述了編程的兩個基本元素:數據和函數之間的...
摘要:使用消息傳遞,我們就能使抽象數據類型直接擁有行為。構造器以類似的方式實現它在參數上調用了叫做的方法。抽象數據類型允許我們在數據表示和用于操作數據的函數之間構造界限。 2.7 泛用方法 來源:2.7 Generic Operations 譯者:飛龍 協議:CC BY-NC-SA 4.0 這一章中我們引入了復合數據類型,以及由構造器和選擇器實現的數據抽象機制。使用消息傳遞,我們就能...
摘要:的最常見的作用是構造異常實例并拋出它。子句組只在執行過程中的異常產生時執行。每個子句指定了需要處理的異常的特定類。將強制轉為字符串會得到由返回的人類可讀的字符串。 3.4 異常 來源:3.4 Exceptions 譯者:飛龍 協議:CC BY-NC-SA 4.0 程序員必須總是留意程序中可能出現的錯誤。例子數不勝數:一個函數可能不會收到它預期的信息,必需的資源可能會丟失,或者網...
摘要:另一個賦值語句將名稱關聯到出現在莎士比亞劇本中的所有去重詞匯的集合,總計個。表達式是一個復合表達式,計算出正序或倒序出現的莎士比亞詞匯集合。在意圖上并沒有按照莎士比亞或者回文來設計,但是它極大的靈活性讓我們用極少的代碼處理大量文本。 1.1 引言 來源:1.1 Introduction 譯者:飛龍 協議:CC BY-NC-SA 4.0 計算機科學是一個極其寬泛的學科。全球的分布...
閱讀 1572·2021-10-14 09:42
閱讀 3815·2021-09-07 09:59
閱讀 1292·2019-08-30 15:55
閱讀 572·2019-08-30 11:17
閱讀 3337·2019-08-29 16:06
閱讀 500·2019-08-29 14:06
閱讀 3123·2019-08-28 18:14
閱讀 3642·2019-08-26 13:55