復合語句中包含了其他語句(語句組);它以某種方式影響或控制著其他語句的執(zhí)行。一般來講,復合語句會跨越多個行,然而一個完整的復合語句也可以簡化在一行中。
if,while 及for 語句實現(xiàn)了傳統(tǒng)的流控制機制。try語句為一組語句指定了異常處理器和/或資源清除代碼,with 表達式允許在代碼塊上下文執(zhí)行代碼初始化并做后續(xù)處理。函數(shù)及類的定義也被看作是復合語句。
復合語句由一個或多個‘子句’組成。一個子句由一個頭部和一個‘代碼序列’組成。特定復合語句的子句頭具有相同的縮進層次。每個子句頭均以一個唯一的標識關鍵字開始,并以一個冒號結束。一個語句序列是由子句控制的一組語句。一個語句序列可以包含一個或多個以分號分隔且與子句頭同行的語句?;蛘咚部梢允且粋€或多個在后續(xù)各行中縮進的語句。只有在后者的情況下子句序列允許包括有嵌套的復合語句,一下形式是非法的,這樣限制原因是 if 后面有 else 子句的話,會導致語義不明確。
if test1: if test2: print(x)
還需要注意的是,在這樣的上下文中,分號的優(yōu)先級比冒號的高,所以在下面的例子中,要么所有的 print() 方法都會被執(zhí)行,要么所有方法都不會被執(zhí)行。
if x < y < z: print(x); print(y); print(z)
總結:
compound_stmt ::= if_stmt
| while_stmt
| for_stmt
| try_stmt
| with_stmt
| funcdef
| classdef
suite ::= stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
statement ::= stmt_list NEWLINE | compound_stmt
stmt_list ::= simple_stmt (";" simple_stmt)* [";"]
注意以 NEWLINE
結尾的語句可能后綴一個 DEDENT
。同時需要注意的是,可選的續(xù)行子句通常以某個不能開始一個語句的關鍵字開頭,因此這里沒有歧義(‘不定義的 else ’的問題已經由 Python 根據(jù)對嵌套的語句的縮進要求解決掉了)。
為了能夠敘述清楚,以下章節(jié)中的每個子句的語法規(guī)則格式都被分行說明。
if 語句用于條件性執(zhí)行:
if_stmt ::= "if" expression ":" suite
( "elif" expression ":" suite )*
["else" ":" suite]
通過依次計算每個表達式的值,直到找到表達式為 true 的值時,它會準確地選擇執(zhí)行相應一個語句序列(對真和假的定義參見 Boolean operations 章節(jié));然后該語句序列被執(zhí)行( if 語句的其它部分不會被執(zhí)行或計算)。如果表達式的值都為 false,并且存在 else 子句,則 else 子句被執(zhí)行。
只要條件表達式的值為 true,while語句會重復執(zhí)行某個代碼段:
while_stmt ::= "while" expression ":" suite
["else" ":" suite]
在上例中,會重復計算表達式(expression)的值,并且如果表達式為真就執(zhí)行第一個語句序列(suit);如果為假(可能是在執(zhí)行第一次計算時)就會執(zhí)行 else 子句(如果給出的話),并退出循環(huán)。
第一個語句序列中的 break 語句可以實現(xiàn)不執(zhí)行 else 子句而直接退出循環(huán)。第一個語句序列中的 continue 子句可以跳過該子句的其余部分,直接進入下次表達式的計算。
for 語句用于迭代有序序列或其他可迭代對象的元素(比如字符串,數(shù)組或列表)。
for_stmt ::= "for" target_list "in" expression_list ":" suite
["else" ":" suite]
表達式列表(expression list)僅被計算一次,它應該生成一個可迭代的對象。為 expression_list
的結果創(chuàng)建一個迭代器。對于迭代器中的每一個元素,語句序列都會以迭代器返回的結果為序執(zhí)行一次。每個元素使用標準的賦值規(guī)則(詳見 Assignment statements )依次賦給循環(huán)控制對象表, 然后執(zhí)行語句序列。元素迭代完后(當?shù)蛄袨榭栈虻鲯伋?StopIteration 異常),執(zhí)行語句序列中的 else 子句(如果存在)然后循環(huán)終止。
第一個語句序列中的 break 語句可以實現(xiàn)不執(zhí)行 else 子句而終止循環(huán)。在第一個語句序列中的 continue 語句可以跳過該子句的其余部分,直接進行下個元素的迭代計算,或者當?shù)瓿珊筮M入 else 子句。
for循環(huán)語句序列可以對循環(huán)控制對象列表中的變量賦值。這將覆蓋所有以前分配給那些變量的值,包括for循環(huán)中的語句序列中的變量:
for i in range(10):
print(i)
i = 5 # this will not affect the for-loop
# because i will be overwritten with the next
# index in the range
循環(huán)結束時循環(huán)控制對象列表中的名字并未刪除,但是如果序列為空,它在循環(huán)中根本不會被賦值。提示:內建函數(shù) range() 返回一個整數(shù)列表,可以用于模擬 Pascal 語言中的 for i := a to b do
的行為;例如 list(range(3))
返回列表 [0,1,2]
。
注意:如果序列對象在循環(huán)過程中被修改(只有可變類型會出現(xiàn)這種情況,例如列表),這里有一些需要注意的地方。有一個內部計數(shù)器用于跟蹤下一輪循環(huán)使用的元素,并且每迭代一次便增加一次。當這個計數(shù)器的值達到了序列的長度時循環(huán)終止。這就意味著如果從語句序列中刪除當前(或前一個元素)元素,下一個元素會被跳過而不被執(zhí)行(因為當前索引值的元素已經處理過了)。另一方面,如果在當前元素前插入一個元素,下一輪循環(huán)時當前元素會被再次重復處理。這會導致難以察覺的錯誤,但可以通過使用含有整個有序類型對象的片段而生成的臨時拷貝避免這個問題,例如:
for x in a[:]:
if x < 0: a.remove(x)
try 語句可以為一組語句指定異常處理器和/或資源清理代碼:
try_stmt ::= try1_stmt | try2_stmt
try1_stmt ::= "try" ":" suite
("except" [expression ["as" identifier]] ":" suite)+
["else" ":" suite]
["finally" ":" suite]
try2_stmt ::= "try" ":" suite
"finally" ":" suite
except 子句指定了一個或多個異常處理器。 如果在 try 子句中未捕獲任何異常,則異常處理器不會被執(zhí)行。當 try 子句中有異常捕獲時,就會開始查找異常處理器。它會依次查找異常處理子句,直到找到能夠匹配該異常的子句。如果存在未指定異常的 except 語句,則必須放在最后,它會匹配任何異常。當 except 子句中聲明了異常類型時,該類型表達式的值會被計算. 如果結果對象與該異常匹配, 那么該子句就匹配了這個異常。只有滿足以下條件才認為一個對象匹配某個異常:1、該對象是異常對象本身或其基類;2、該對象是一個數(shù)組,包含了一個與該異常兼容的對象。
如果沒有 except 子句能夠匹配異常,將會在調用棧 [1] 的外圍代碼中繼續(xù)查找異常處理器。
如果在 except 子句頭部計算表達式時就引發(fā)了異常, 原來的異常處理器查找工作就會被中斷, 并在外層代碼及調用棧搜索新的異常處理器(就好像處理整個 try 語句發(fā)生了異常一樣)。
當找到了一個匹配的 except 子句時,異常被賦值給 except 子句中 as 關鍵字后指定的對象(如果存在),并且執(zhí)行該 except 語句序列。所有 except 子句都必須包含可執(zhí)行的代碼塊。當該代碼塊執(zhí)行結束后, 會轉到整個 try 語句之后繼續(xù)正常執(zhí)行(這意味著, 如果有兩個嵌套的異常處理器用于捕獲同一個異常, 并且異常由內層的處理器處理, 那么外層處理器就不會響應這個異常)。
當使用 as target
關鍵詞給異常賦值時,該 target 會在 except 子句結束后被銷毀。例如:
except E as N:
foo
可以解釋為:
except E as N:
try:
foo
finally:
del N
這就意味著必須給異常指派一個不同的名稱,以保證在 except 子句結束后可以索引到它。這些異常最后會被清除,因為這些異常與堆棧信息會綁定在一起,他們與棧幀形成了循環(huán)引用。直到下次垃圾回收發(fā)生前,所有這些棧中的局部變量都是存活的。
except 子句被執(zhí)行前,該異常的詳細信息存儲在 sys 模塊中,可以通過 sys.exc_info().sys.exc_info() 方法讀取,該方法返回由異常類、異常實例以及能夠標識程序中異常發(fā)生位置的堆棧信息(詳見 The standard type hierarchy)組成的三元組組成。當返回發(fā)生異常的函數(shù)時,sys.exc_info() 的值會恢復調用之前的值。
當程序控制器從 try 子句中執(zhí)行結束后,可以執(zhí)行可選的 else 子句。[2]在 else 子句中引發(fā)的異常不會在前面的 except 子句中得到處理。
如果存在 finally 塊,它是一個資源“清理”處理器。當執(zhí)行try 代碼塊,包括 except 與 else 過程中有異常發(fā)生且未被處理時,這些異常就會被臨時保存下來。finally 塊會被執(zhí)行,如果有臨時保存的異常的話,該異常會在 finally 塊執(zhí)行結束后被重新拋出。如果 finally 塊產生了另一個異常,已被保存的異常會作為新異常的上下文保存下來。如果 finally 塊中執(zhí)行了 return 或 break 語句,則忽略已保存的異常:
>>> def f():
... try:
... 1/0
... finally:
... return 42
...
>>> f()
42
在執(zhí)行 finally 塊時,程序的異常信息是無效的。
try...finally 語句中,try 代碼塊中的 return,break 或者 continue執(zhí)行后,finally 塊也會被執(zhí)行。在 finally 塊中不允許出現(xiàn) continue 子句(該問題是由當前實現(xiàn)導致的——這個限制可能會在后續(xù)的版本中去掉)。
函數(shù)的返回值由程序最后的 return 語句的執(zhí)行結果決定。由于 finally 塊一定會被執(zhí)行,所以finally 塊中的 return 語句一定是最后才會被執(zhí)行的。
>>> def foo():
... try:
... return 'try'
... finally:
... return 'finally'
...
>>> foo()
'finally'
更多關于異常的信息可以查看 Exceptions 章節(jié),關于使用 raise 語句拋出異常的詳細信息可以從 The raise statement 部分找到。
with 語句使用上下文管理器中定義的方法將執(zhí)行的代碼塊包裹起來(詳見 With Statement Context Managers)。為了便于重用,允許使用傳統(tǒng)的 try...except...finally 異常處理模式對該語句進行封裝。
with_stmt ::= "with" with_item ("," with_item)* ":" suite
with_item ::= expression ["as" target]
使用 with 語句執(zhí)行一個“元素”的處理過程如下:
1、根據(jù)上下文表達式(with_item 中給出的表達式)的值得到一個上下文管理器。
2、加載上下文管理器的 __exit__()
方法,后續(xù)使用。
3、調用上下文管理器的 __enter
__()`方法。
4、如果 with 語句中包含一個對象,則將 __enter__()
方法的返回值賦值給該對象。
注意:with 語句能夠保證只要
__enter__()
方法能夠正確返回,則__exit__()
) 方法一定會被調用。因此,如果在給對象列表賦值過程中發(fā)生了錯誤,對該錯誤的處理方式與語句序列中的錯誤處理方式是一致的。見步驟6。
5、執(zhí)行語句序列。
6、上下文管理器的 __exit__()
方法被調用。如果異常導致必須退出語句序列,其類型、值以及堆棧信息會作為參數(shù)傳遞給 __exit__()
。否則,會給該方法傳遞三個 None 參數(shù)。
如果由于異常導致語句退出,且 __exit__()
的返回值為 false,該異常會被再次拋出。如果返回值為 true,則異常被抑制并且會繼續(xù)執(zhí)行 with 語句之后的代碼。
如果由于其他非異常原因導致語句退出,則__exit__()
的返回值被忽略,且會從退出的正常位置繼續(xù)執(zhí)行。
上下文管理器會像處理多個嵌套 with 語句一樣處理多個元素:
with A() as a, B() as b:
suite
等價于
with A() as a:
with B() as b:
suite
3.1 版本中新特性:支持多上下文表達式。
參見:
PEP 0343 - “with” 語句
詳細介紹了Python 中 with 語句的特性,知識背景以及一些參考實例。
函數(shù)定義,定義了一個用戶自定義的函數(shù)對象(參見 The standard type hierarchy)。
funcdef ::= [decorators] "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite
decorators ::= decorator+
decorator ::= "@" dotted_name ["(" [parameter_list [","]] ")"] NEWLINE
dotted_name ::= identifier ("." identifier)*
parameter_list ::= (defparameter ",")*
| "*" [parameter] ("," defparameter)* ["," "**" parameter]
| "**" parameter
| defparameter [","] )
parameter ::= identifier [":" expression]
defparameter ::= parameter ["=" expression]
funcname ::= identifier
函數(shù)定義是一個可執(zhí)行語句。它在當前局部命名空間中將函數(shù)名稱與函數(shù)對象(函數(shù)的可執(zhí)行代碼的組合)捆綁在一起。該函數(shù)對象包括著一個全局命名空間的引用,以便在調用時使用。
函數(shù)定義不執(zhí)行函數(shù)體;只有當函數(shù)被調用時才會執(zhí)行函數(shù)體。[3]
函數(shù)的定義可能被若干 decorator 修飾表達式修飾。當函數(shù)被定義時在函數(shù)定義的范圍內計算修飾表達式的值。其結果必須是一個回調,該回調作為函數(shù)對象的唯一參數(shù)被調用。返回值綁定函數(shù)名稱,而不是綁定函數(shù)對象。多裝飾被應用于嵌套方式。例如下方的代碼:
@f1(arg)
@f2
def func(): pass
等價于
def func(): pass
func = f1(arg)(f2(func))
當一個或多個 parameters(參數(shù))包含形參 =
表達式時,這樣的函數(shù)就稱為具有“默認參數(shù)值”的函數(shù)。在調用有默認參數(shù)值參數(shù)的函數(shù)時,其對應的參數(shù)就可以忽略,這種情況下默認值會用于替代該參數(shù)。如果一個參數(shù)有默認值,該參數(shù)之后,*
之前的所有參數(shù)都必須有默認值——這是一個在語法中未表述的句法限制。
默認參數(shù)值在函數(shù)定義被執(zhí)行時從左至右計算。這意味著在函數(shù)定義時,該表達式僅被執(zhí)行一次,且在每次調用時都使用相同的“預計算”值。這在理解默認參數(shù)值是一個像列表或者字典這樣的可變對象時尤其值得注意。如果函數(shù)修改了這個對象(例如給一個列表中追加了一個元素),默認值也隨之被修改。這顯然不是我們期望的。一個避免這個問題的方法就是使用 None
作為默認值,并在函數(shù)體中做顯式測試,例如:
def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin
函數(shù)調用語義的詳細介紹請參見 Call 章節(jié)。通常一個函數(shù)調用會給所有出現(xiàn)在參數(shù)列表中的參數(shù)賦值,要么通過位置參數(shù),或者通過關鍵字參數(shù),或者通過默認值。如果參數(shù)列表中包含 *identifier
形式,它會被初始化為一個數(shù)組用于接收所有額外的位置參數(shù),其默認為空數(shù)組。如果參數(shù)列表中包含 **identifier
形式,它會被初始化為一個新的字典用于接收所有二外的關鍵字參數(shù),其默認值為一個空字典。*
或 *identifier
后的參數(shù)為 keyword-only 參數(shù),只能通過關鍵字參數(shù)傳值。
參數(shù)列表可能包含“參數(shù)名稱后緊跟 : expression
”形式的注解。所有參數(shù)都有注解,甚至 *identifier
或 **identifier
形式的參數(shù)也有注解。函數(shù)參數(shù)列表后可能包含 -> expression
形式的“return”注解。這些注解可以是任意合法的 Python 表達式,并且會在函數(shù)定義執(zhí)行時計算這些表達式的值。注解的計算順序可能會與其在源代碼中出現(xiàn)的書序不同。注解的出現(xiàn)不會改變一個函數(shù)的語義。函數(shù)對象的 annotations _ _屬性中的參數(shù)名稱可以作為字典的 key,其對應的 value 為注解的值。
也可以創(chuàng)建能直接在表達式中使用的匿名函數(shù)(未與名字綁定的函數(shù))。這是通過 lambda 表達式實現(xiàn)的,詳見 Lambdas 章節(jié)。注意 lambda 僅僅是一個簡單函數(shù)的縮寫形式;以“def”定義的函數(shù)可以被傳遞或賦予一個新的名字,就像以 lambda 表達式定義的函數(shù)一樣。以“def”形式定義的函數(shù)功能要更強大些,因為它允許執(zhí)行多條語句及注解。
程序員注意:函數(shù)是一類對象。在函數(shù)定義中執(zhí)行“def
”,定義了一個可返回或傳遞的局部函數(shù)。在嵌套函數(shù)中使用的自由變量可以獲取包含 def 的函數(shù)對象的局部變量。詳見 Naming and binding章節(jié)。
參見:
PEP 3107 - 函數(shù)注解
函數(shù)注解的原始規(guī)范。
一個類定義定義了一個類對象(見 [The Standard type hierarchy] (https://docs.python.org/3/reference/datamodel.html#types)):
classdef ::= [decorators] "class" classname [inheritance] ":" suite
inheritance ::= "(" [parameter_list] ")"
classname ::= identifier
一個類定義是一個可執(zhí)行的語句。繼承關系表通常會給出一個基類列表(獲取更多高級應用可以參見 Customizing class creation),因此繼承關系列表中的每個元素都生成一個允許子類化的類對象。在繼承關系列表中未包含的類默認繼承自 object 類;因此,
class Foo:
pass
等價于
class Foo(object):
pass
然后類的語句序列在一個新的堆棧結構中(見 Naming and binding)使用新創(chuàng)建的局部命名空間以及原始的全局命名空間執(zhí)行。(通常情況下,該套件包含的大多是函數(shù)定義。)當該類的語句序列執(zhí)行結束后就丟棄其執(zhí)行堆棧,但是它的局部命名空間會被保存下來。[4]然后使用其繼承關系表創(chuàng)建基類,將保存下來的命名空間作為屬性字典創(chuàng)建新的類對象。在原始的命名空間中,類的名稱會被綁定在這個類對象上。
類的創(chuàng)建可以使用 metaclasses 大量定制。
類中也可以加修飾符:就像修飾函數(shù)一樣,
@f1(arg)
@f2
class Foo: pass
等價于
class Foo: pass
Foo = f1(arg)(f2(Foo))
類修飾符表達式的值計算方式與函數(shù)修飾符相同。其結果必須是一個類對象,并將該類對象與一個類名綁定。
程序員注意:類定義中定義的變量都是類的屬性;類的實例共享這些屬性。實例屬性可以通過
self.name = value
的方式賦值。類屬性及實例屬性都可以通過self.name
獲取,并且如果使用這種方式屬性的話,如果實例屬性與類屬性由相同的名稱,那么實例屬性會覆蓋類屬性。類屬性可以作為實例屬性的默認值,但是使用可變值可能會導致不可預期的結果。Descriptors 可以用于創(chuàng)建包含不同實現(xiàn)細節(jié)的實例變量。
參見: PEP 3115 - Python 3 中的 Metaclasses PEP 3129 - 類修飾符
腳注
[1] 該異常會傳遞到調用堆棧中,除非在 finally 代碼塊中碰巧拋出了另一個異常。這個新異常會導致舊異常丟失。
[2] 在遇到異?;蛘邎?zhí)行 [return](),[continue]() 或 [break]() 語句的時,程序控制器會離開 except 塊。
[3] 一個字符串文本作為函數(shù)體的第一段語句出現(xiàn)時會被轉換為函數(shù)的__doc__
屬性以及函數(shù)的 [docstring](https://docs.python.org/3/glossary.html#term-docstring)。
[4] 一個字符串文本作為類中的第一段語句出現(xiàn)時會被轉換為命名空間的 __doc__
元素以及類的docstring。