鍍金池/ 教程/ Python/ 復(fù)合語句
復(fù)合語句
數(shù)據(jù)模型
完整的語法規(guī)范
執(zhí)行模型
表達(dá)式
導(dǎo)入系統(tǒng)
詞法分析
簡單語句
頂層組件
介紹

復(fù)合語句

復(fù)合語句中包含了其他語句(語句組);它以某種方式影響或控制著其他語句的執(zhí)行。一般來講,復(fù)合語句會跨越多個行,然而一個完整的復(fù)合語句也可以簡化在一行中。

if,whilefor 語句實現(xiàn)了傳統(tǒng)的流控制機(jī)制。try語句為一組語句指定了異常處理器和/或資源清除代碼,with 表達(dá)式允許在代碼塊上下文執(zhí)行代碼初始化并做后續(xù)處理。函數(shù)及類的定義也被看作是復(fù)合語句。

復(fù)合語句由一個或多個‘子句’組成。一個子句由一個頭部和一個‘代碼序列’組成。特定復(fù)合語句的子句頭具有相同的縮進(jìn)層次。每個子句頭均以一個唯一的標(biāo)識關(guān)鍵字開始,并以一個冒號結(jié)束。一個語句序列是由子句控制的一組語句。一個語句序列可以包含一個或多個以分號分隔且與子句頭同行的語句。或者它也可以是一個或多個在后續(xù)各行中縮進(jìn)的語句。只有在后者的情況下子句序列允許包括有嵌套的復(fù)合語句,一下形式是非法的,這樣限制原因是 if 后面有 else 子句的話,會導(dǎo)致語義不明確。

    if test1: if test2: print(x)

還需要注意的是,在這樣的上下文中,分號的優(yōu)先級比冒號的高,所以在下面的例子中,要么所有的 print() 方法都會被執(zhí)行,要么所有方法都不會被執(zhí)行。

    if x < y < z: print(x); print(y); print(z)

總結(jié):

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 結(jié)尾的語句可能后綴一個 DEDENT。同時需要注意的是,可選的續(xù)行子句通常以某個不能開始一個語句的關(guān)鍵字開頭,因此這里沒有歧義(‘不定義的 else ’的問題已經(jīng)由 Python 根據(jù)對嵌套的語句的縮進(jìn)要求解決掉了)。

為了能夠敘述清楚,以下章節(jié)中的每個子句的語法規(guī)則格式都被分行說明。

if 語句

if 語句用于條件性執(zhí)行:

if_stmt ::=  "if" expression ":" suite
             ( "elif" expression ":" suite )*
             ["else" ":" suite]

通過依次計算每個表達(dá)式的值,直到找到表達(dá)式為 true 的值時,它會準(zhǔn)確地選擇執(zhí)行相應(yīng)一個語句序列(對真和假的定義參見 Boolean operations 章節(jié));然后該語句序列被執(zhí)行( if 語句的其它部分不會被執(zhí)行或計算)。如果表達(dá)式的值都為 false,并且存在 else 子句,則 else 子句被執(zhí)行。

while 語句

只要條件表達(dá)式的值為 true,while語句會重復(fù)執(zhí)行某個代碼段:

while_stmt ::=  "while" expression ":" suite
                ["else" ":" suite]

在上例中,會重復(fù)計算表達(dá)式(expression)的值,并且如果表達(dá)式為真就執(zhí)行第一個語句序列(suit);如果為假(可能是在執(zhí)行第一次計算時)就會執(zhí)行 else 子句(如果給出的話),并退出循環(huán)。

第一個語句序列中的 break 語句可以實現(xiàn)不執(zhí)行 else 子句而直接退出循環(huán)。第一個語句序列中的 continue 子句可以跳過該子句的其余部分,直接進(jìn)入下次表達(dá)式的計算。

for 語句

for 語句用于迭代有序序列或其他可迭代對象的元素(比如字符串,數(shù)組或列表)。

for_stmt ::=  "for" target_list "in" expression_list ":" suite
              ["else" ":" suite]

表達(dá)式列表(expression list)僅被計算一次,它應(yīng)該生成一個可迭代的對象。為 expression_list 的結(jié)果創(chuàng)建一個迭代器。對于迭代器中的每一個元素,語句序列都會以迭代器返回的結(jié)果為序執(zhí)行一次。每個元素使用標(biāo)準(zhǔn)的賦值規(guī)則(詳見 Assignment statements )依次賦給循環(huán)控制對象表, 然后執(zhí)行語句序列。元素迭代完后(當(dāng)?shù)蛄袨榭栈虻鲯伋?StopIteration 異常),執(zhí)行語句序列中的 else 子句(如果存在)然后循環(huán)終止。

第一個語句序列中的 break 語句可以實現(xiàn)不執(zhí)行 else 子句而終止循環(huán)。在第一個語句序列中的 continue 語句可以跳過該子句的其余部分,直接進(jìn)行下個元素的迭代計算,或者當(dāng)?shù)瓿珊筮M(jìn)入 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)結(jié)束時循環(huán)控制對象列表中的名字并未刪除,但是如果序列為空,它在循環(huán)中根本不會被賦值。提示:內(nèi)建函數(shù) range() 返回一個整數(shù)列表,可以用于模擬 Pascal 語言中的 for i := a to b do 的行為;例如 list(range(3)) 返回列表 [0,1,2]。

注意:如果序列對象在循環(huán)過程中被修改(只有可變類型會出現(xiàn)這種情況,例如列表),這里有一些需要注意的地方。有一個內(nèi)部計數(shù)器用于跟蹤下一輪循環(huán)使用的元素,并且每迭代一次便增加一次。當(dāng)這個計數(shù)器的值達(dá)到了序列的長度時循環(huán)終止。這就意味著如果從語句序列中刪除當(dāng)前(或前一個元素)元素,下一個元素會被跳過而不被執(zhí)行(因為當(dāng)前索引值的元素已經(jīng)處理過了)。另一方面,如果在當(dāng)前元素前插入一個元素,下一輪循環(huán)時當(dāng)前元素會被再次重復(fù)處理。這會導(dǎo)致難以察覺的錯誤,但可以通過使用含有整個有序類型對象的片段而生成的臨時拷貝避免這個問題,例如:

    for x in a[:]:
        if x < 0: a.remove(x)

try 語句

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í)行。當(dāng) try 子句中有異常捕獲時,就會開始查找異常處理器。它會依次查找異常處理子句,直到找到能夠匹配該異常的子句。如果存在未指定異常的 except 語句,則必須放在最后,它會匹配任何異常。當(dāng) except 子句中聲明了異常類型時,該類型表達(dá)式的值會被計算. 如果結(jié)果對象與該異常匹配, 那么該子句就匹配了這個異常。只有滿足以下條件才認(rèn)為一個對象匹配某個異常:1、該對象是異常對象本身或其基類;2、該對象是一個數(shù)組,包含了一個與該異常兼容的對象。

如果沒有 except 子句能夠匹配異常,將會在調(diào)用棧 [1] 的外圍代碼中繼續(xù)查找異常處理器。

如果在 except 子句頭部計算表達(dá)式時就引發(fā)了異常, 原來的異常處理器查找工作就會被中斷, 并在外層代碼及調(diào)用棧搜索新的異常處理器(就好像處理整個 try 語句發(fā)生了異常一樣)。

當(dāng)找到了一個匹配的 except 子句時,異常被賦值給 except 子句中 as 關(guān)鍵字后指定的對象(如果存在),并且執(zhí)行該 except 語句序列。所有 except 子句都必須包含可執(zhí)行的代碼塊。當(dāng)該代碼塊執(zhí)行結(jié)束后, 會轉(zhuǎn)到整個 try 語句之后繼續(xù)正常執(zhí)行(這意味著, 如果有兩個嵌套的異常處理器用于捕獲同一個異常, 并且異常由內(nèi)層的處理器處理, 那么外層處理器就不會響應(yīng)這個異常)。

當(dāng)使用 as target 關(guān)鍵詞給異常賦值時,該 target 會在 except 子句結(jié)束后被銷毀。例如:

    except E as N:
    foo

可以解釋為:

    except E as N:
    try:
        foo
    finally:
        del N

這就意味著必須給異常指派一個不同的名稱,以保證在 except 子句結(jié)束后可以索引到它。這些異常最后會被清除,因為這些異常與堆棧信息會綁定在一起,他們與棧幀形成了循環(huán)引用。直到下次垃圾回收發(fā)生前,所有這些棧中的局部變量都是存活的。

except 子句被執(zhí)行前,該異常的詳細(xì)信息存儲在 sys 模塊中,可以通過 sys.exc_info().sys.exc_info() 方法讀取,該方法返回由異常類、異常實例以及能夠標(biāo)識程序中異常發(fā)生位置的堆棧信息(詳見 The standard type hierarchy)組成的三元組組成。當(dāng)返回發(fā)生異常的函數(shù)時,sys.exc_info() 的值會恢復(fù)調(diào)用之前的值。

當(dāng)程序控制器從 try 子句中執(zhí)行結(jié)束后,可以執(zhí)行可選的 else 子句。[2]在 else 子句中引發(fā)的異常不會在前面的 except 子句中得到處理。

如果存在 finally 塊,它是一個資源“清理”處理器。當(dāng)執(zhí)行try 代碼塊,包括 exceptelse 過程中有異常發(fā)生且未被處理時,這些異常就會被臨時保存下來。finally 塊會被執(zhí)行,如果有臨時保存的異常的話,該異常會在 finally 塊執(zhí)行結(jié)束后被重新拋出。如果 finally 塊產(chǎn)生了另一個異常,已被保存的異常會作為新異常的上下文保存下來。如果 finally 塊中執(zhí)行了 returnbreak 語句,則忽略已保存的異常:

    >>> 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 子句(該問題是由當(dāng)前實現(xiàn)導(dǎo)致的——這個限制可能會在后續(xù)的版本中去掉)。

函數(shù)的返回值由程序最后的 return 語句的執(zhí)行結(jié)果決定。由于 finally 塊一定會被執(zhí)行,所以finally 塊中的 return 語句一定是最后才會被執(zhí)行的。

    >>> def foo():
    ...     try:
    ... return 'try'
    ... finally:
    ... return 'finally'
    ...
    >>> foo()
    'finally'

更多關(guān)于異常的信息可以查看 Exceptions 章節(jié),關(guān)于使用 raise 語句拋出異常的詳細(xì)信息可以從 The raise statement 部分找到。

with 語句

with 語句使用上下文管理器中定義的方法將執(zhí)行的代碼塊包裹起來(詳見 With Statement Context Managers)。為了便于重用,允許使用傳統(tǒng)的 try...except...finally 異常處理模式對該語句進(jìn)行封裝。

with_stmt ::=  "with" with_item ("," with_item)* ":" suite
with_item ::=  expression ["as" target]

使用 with 語句執(zhí)行一個“元素”的處理過程如下:

1、根據(jù)上下文表達(dá)式(with_item 中給出的表達(dá)式)的值得到一個上下文管理器。

2、加載上下文管理器的 __exit__()方法,后續(xù)使用。

3、調(diào)用上下文管理器的 __enter__()`方法。

4、如果 with 語句中包含一個對象,則將 __enter__() 方法的返回值賦值給該對象。

注意:with 語句能夠保證只要 __enter__() 方法能夠正確返回,則 __exit__()) 方法一定會被調(diào)用。因此,如果在給對象列表賦值過程中發(fā)生了錯誤,對該錯誤的處理方式與語句序列中的錯誤處理方式是一致的。見步驟6。

5、執(zhí)行語句序列。

6、上下文管理器的 __exit__() 方法被調(diào)用。如果異常導(dǎo)致必須退出語句序列,其類型、值以及堆棧信息會作為參數(shù)傳遞給 __exit__()。否則,會給該方法傳遞三個 None 參數(shù)。

如果由于異常導(dǎo)致語句退出,且 __exit__() 的返回值為 false,該異常會被再次拋出。如果返回值為 true,則異常被抑制并且會繼續(xù)執(zhí)行 with 語句之后的代碼。

如果由于其他非異常原因?qū)е抡Z句退出,則__exit__()的返回值被忽略,且會從退出的正常位置繼續(xù)執(zhí)行。

上下文管理器會像處理多個嵌套 with 語句一樣處理多個元素:

    with A() as a, B() as b:
    suite

等價于

    with A() as a:
    with B() as b:
    suite

3.1 版本中新特性:支持多上下文表達(dá)式。

參見:
PEP 0343 - “with” 語句
詳細(xì)介紹了Python 中 with 語句的特性,知識背景以及一些參考實例。

函數(shù)定義

函數(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í)行語句。它在當(dāng)前局部命名空間中將函數(shù)名稱與函數(shù)對象(函數(shù)的可執(zhí)行代碼的組合)捆綁在一起。該函數(shù)對象包括著一個全局命名空間的引用,以便在調(diào)用時使用。

函數(shù)定義不執(zhí)行函數(shù)體;只有當(dāng)函數(shù)被調(diào)用時才會執(zhí)行函數(shù)體。[3]

函數(shù)的定義可能被若干 decorator 修飾表達(dá)式修飾。當(dāng)函數(shù)被定義時在函數(shù)定義的范圍內(nèi)計算修飾表達(dá)式的值。其結(jié)果必須是一個回調(diào),該回調(diào)作為函數(shù)對象的唯一參數(shù)被調(diào)用。返回值綁定函數(shù)名稱,而不是綁定函數(shù)對象。多裝飾被應(yīng)用于嵌套方式。例如下方的代碼:

    @f1(arg)
    @f2
    def func(): pass

等價于

    def func(): pass
    func = f1(arg)(f2(func))

當(dāng)一個或多個 parameters(參數(shù))包含形參 = 表達(dá)式時,這樣的函數(shù)就稱為具有“默認(rèn)參數(shù)值”的函數(shù)。在調(diào)用有默認(rèn)參數(shù)值參數(shù)的函數(shù)時,其對應(yīng)的參數(shù)就可以忽略,這種情況下默認(rèn)值會用于替代該參數(shù)。如果一個參數(shù)有默認(rèn)值,該參數(shù)之后,*之前的所有參數(shù)都必須有默認(rèn)值——這是一個在語法中未表述的句法限制。

默認(rèn)參數(shù)值在函數(shù)定義被執(zhí)行時從左至右計算。這意味著在函數(shù)定義時,該表達(dá)式僅被執(zhí)行一次,且在每次調(diào)用時都使用相同的“預(yù)計算”值。這在理解默認(rèn)參數(shù)值是一個像列表或者字典這樣的可變對象時尤其值得注意。如果函數(shù)修改了這個對象(例如給一個列表中追加了一個元素),默認(rèn)值也隨之被修改。這顯然不是我們期望的。一個避免這個問題的方法就是使用 None 作為默認(rèn)值,并在函數(shù)體中做顯式測試,例如:

    def whats_on_the_telly(penguin=None):
    if penguin is None:
    penguin = []
    penguin.append("property of the zoo")
    return penguin

函數(shù)調(diào)用語義的詳細(xì)介紹請參見 Call 章節(jié)。通常一個函數(shù)調(diào)用會給所有出現(xiàn)在參數(shù)列表中的參數(shù)賦值,要么通過位置參數(shù),或者通過關(guān)鍵字參數(shù),或者通過默認(rèn)值。如果參數(shù)列表中包含 *identifier 形式,它會被初始化為一個數(shù)組用于接收所有額外的位置參數(shù),其默認(rèn)為空數(shù)組。如果參數(shù)列表中包含 **identifier 形式,它會被初始化為一個新的字典用于接收所有二外的關(guān)鍵字參數(shù),其默認(rèn)值為一個空字典。**identifier 后的參數(shù)為 keyword-only 參數(shù),只能通過關(guān)鍵字參數(shù)傳值。

參數(shù)列表可能包含“參數(shù)名稱后緊跟 : expression ”形式的注解。所有參數(shù)都有注解,甚至 *identifier**identifier 形式的參數(shù)也有注解。函數(shù)參數(shù)列表后可能包含 -> expression 形式的“return”注解。這些注解可以是任意合法的 Python 表達(dá)式,并且會在函數(shù)定義執(zhí)行時計算這些表達(dá)式的值。注解的計算順序可能會與其在源代碼中出現(xiàn)的書序不同。注解的出現(xiàn)不會改變一個函數(shù)的語義。函數(shù)對象的 annotations _ _屬性中的參數(shù)名稱可以作為字典的 key,其對應(yīng)的 value 為注解的值。

也可以創(chuàng)建能直接在表達(dá)式中使用的匿名函數(shù)(未與名字綁定的函數(shù))。這是通過 lambda 表達(dá)式實現(xiàn)的,詳見 Lambdas 章節(jié)。注意 lambda 僅僅是一個簡單函數(shù)的縮寫形式;以“def”定義的函數(shù)可以被傳遞或賦予一個新的名字,就像以 lambda 表達(dá)式定義的函數(shù)一樣。以“def”形式定義的函數(shù)功能要更強(qiáng)大些,因為它允許執(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í)行的語句。繼承關(guān)系表通常會給出一個基類列表(獲取更多高級應(yīng)用可以參見 Customizing class creation),因此繼承關(guān)系列表中的每個元素都生成一個允許子類化的類對象。在繼承關(guān)系列表中未包含的類默認(rèn)繼承自 object 類;因此,

    class Foo:
    pass

等價于

    class Foo(object):
    pass

然后類的語句序列在一個新的堆棧結(jié)構(gòu)中(見 Naming and binding)使用新創(chuàng)建的局部命名空間以及原始的全局命名空間執(zhí)行。(通常情況下,該套件包含的大多是函數(shù)定義。)當(dāng)該類的語句序列執(zhí)行結(jié)束后就丟棄其執(zhí)行堆棧,但是它的局部命名空間會被保存下來。[4]然后使用其繼承關(guān)系表創(chuàng)建基類,將保存下來的命名空間作為屬性字典創(chuàng)建新的類對象。在原始的命名空間中,類的名稱會被綁定在這個類對象上。

類的創(chuàng)建可以使用 metaclasses 大量定制。

類中也可以加修飾符:就像修飾函數(shù)一樣,

    @f1(arg)
    @f2
    class Foo: pass

等價于

    class Foo: pass
    Foo = f1(arg)(f2(Foo))

類修飾符表達(dá)式的值計算方式與函數(shù)修飾符相同。其結(jié)果必須是一個類對象,并將該類對象與一個類名綁定。

程序員注意:類定義中定義的變量都是類的屬性;類的實例共享這些屬性。實例屬性可以通過 self.name = value 的方式賦值。類屬性及實例屬性都可以通過 self.name 獲取,并且如果使用這種方式屬性的話,如果實例屬性與類屬性由相同的名稱,那么實例屬性會覆蓋類屬性。類屬性可以作為實例屬性的默認(rèn)值,但是使用可變值可能會導(dǎo)致不可預(yù)期的結(jié)果。Descriptors 可以用于創(chuàng)建包含不同實現(xiàn)細(xì)節(jié)的實例變量。

參見: PEP 3115 - Python 3 中的 Metaclasses PEP 3129 - 類修飾符

腳注

[1] 該異常會傳遞到調(diào)用堆棧中,除非在 finally 代碼塊中碰巧拋出了另一個異常。這個新異常會導(dǎo)致舊異常丟失。

[2] 在遇到異?;蛘邎?zhí)行 [return](),[continue]() 或 [break]() 語句的時,程序控制器會離開 except 塊。

[3] 一個字符串文本作為函數(shù)體的第一段語句出現(xiàn)時會被轉(zhuǎn)換為函數(shù)的__doc__屬性以及函數(shù)的 [docstring](https://docs.python.org/3/glossary.html#term-docstring)。

[4] 一個字符串文本作為類中的第一段語句出現(xiàn)時會被轉(zhuǎn)換為命名空間的 __doc__元素以及類的docstring

上一篇:數(shù)據(jù)模型下一篇:詞法分析