6. 表示式

本章將解釋 Python 中表達式元素的含義。

句法要點: 在本章及後續章節中,將使用擴充套件 BNF 正規化來描述句法而非詞法。 當(一個備選項的)句法規則具有以下形式時

name: othername

並且未給出語義,這種形式的 name 的語義與 othername 相同。

6.1. 算術轉換

當一個算術運算子的描述中使用了“數值引數會被轉換為一個共同的實數型別”這個短語時,這表示該運算子對於內建型別的實現將按以下方式運作

  • 如果兩個引數都是複數,則不進行轉換;

  • 如果任一引數是複數或浮點數,則另一個引數將被轉換為浮點數;

  • 否則,兩者都必須是整數,且無需轉換。

某些運算子還有一些額外的規則(例如,一個字串作為 '%' 運算子的左引數)。 擴充套件必須定義它們自己的轉換行為。

6.2. 原子

原子是表示式最基本的元素。最簡單的原子是識別符號或字面值。包含在圓括號、方括號或花括號中的形式在句法上也歸類為原子。原子的句法是

atom:      identifier | literal | enclosure
enclosure: parenth_form | list_display | dict_display | set_display
           | generator_expression | yield_atom

6.2.1. 識別符號(名稱)

作為原子出現的識別符號是一個名稱。詞法定義請參閱 名稱(識別符號和關鍵字),命名和繫結的文件請參閱 命名與繫結

當名稱被繫結到一個物件時,對原子的求值會產生該物件。當一個名稱未被繫結時,嘗試對它求值將引發 NameError 異常。

6.2.1.1. 私有名稱改寫

當一個在類定義中出現的識別符號以兩個或多個下劃線字元開頭且不以兩個或多個下劃線結尾時,它被認為是該類的 私有名稱

參見

請參閱 類規範

更準確地說,私有名稱在為其生成程式碼之前會被轉換為更長的形式。如果轉換後的名稱長度超過 255 個字元,可能會發生由具體實現定義的截斷。

轉換獨立於使用該識別符號的句法上下文,但只有以下私有識別符號會被改寫

  • 任何用作被賦值、讀取的變數名,或任何被訪問的屬性名。

    然而,巢狀函式、類和類型別名的 __name__ 屬性不會被改寫。

  • 匯入的模組名,例如,import __spam 中的 __spam。如果該模組是包的一部分(即其名稱包含點),則該名稱會被改寫,例如,import __foo.bar 中的 __foo 不會被改寫。

  • 匯入的成員名,例如,from spam import __f 中的 __f

轉換規則定義如下

  • 類名(去除前導下劃線並插入一個前導下劃線)被插入到識別符號前面。例如,在名為 Foo_Foo__Foo 的類中出現的識別符號 __spam 會被轉換為 _Foo__spam

  • 如果類名僅由下劃線組成,則不進行轉換。例如,在名為 ___ 的類中出現的識別符號 __spam 會保持原樣。

6.2.2. 字面值

Python 支援字串和位元組串字面值以及各種數字字面值:

literal: strings | NUMBER

對一個字面值求值會產生一個具有給定值的給定型別的物件(字串、位元組串、整數、浮點數、複數)。在浮點數和虛數(複數)字面值的情況下,該值可能是近似值。詳情請參閱 字面值 一節。關於 strings 的詳情請參閱 字串字面值的拼接 一節。

所有字面值都對應於不可變的資料型別,因此物件的標識比其值次要。對具有相同值的字面值進行多次求值(無論是在程式文字中的同一次出現還是不同次出現)可能會得到相同的物件或具有相同值的不同物件。

6.2.2.1. 字串字面值的拼接

多個相鄰的字串或位元組串字面值(由空白符分隔),可能使用不同的引號約定,是允許的,它們的含義與它們的拼接相同:

>>> "hello" 'world'
"helloworld"

形式上:

strings: ( STRING | fstring)+ | tstring+

此特性在句法層面定義,因此僅適用於字面值。要在執行時拼接字串表示式,可以使用 '+' 運算子:

>>> greeting = "Hello"
>>> space = " "
>>> name = "Blaise"
>>> print(greeting + space + name)   # not: print(greeting space name)
Hello Blaise

字面值拼接可以自由混合使用原始字串、三引號字串和格式化字串字面值。例如:

>>> "Hello" r', ' f"{name}!"
"Hello, Blaise!"

此特性可以用來減少所需的反斜槓數量,方便地將長字串跨多行分割,甚至為字串的某些部分添加註釋。例如:

re.compile("[A-Za-z_]"       # letter or underscore
           "[A-Za-z0-9_]*"   # letter, digit or underscore
          )

但是,位元組串字面值只能與其他位元組串字面值組合,不能與任何型別的字串字面值組合。同樣,模板字串字面值也只能與其他模板字串字面值組合:

>>> t"Hello" t"{name}!"
Template(strings=('Hello', '!'), interpolations=(...))

6.2.3. 帶圓括號的形式

帶圓括號的形式是一個可選的表示式列表,包含在圓括號內:

parenth_form: "(" [starred_expression] ")"

帶圓括號的表示式列表產生該表示式列表所產生的結果:如果列表中至少包含一個逗號,則它產生一個元組;否則,它產生構成該表示式列表的單個表示式。

一對空圓括號產生一個空元組物件。由於元組是不可變的,因此適用與字面值相同的規則(即,兩次出現的空元組可能產生也可能不產生同一個物件)。

注意,元組是由逗號構成的,而不是圓括號。唯一的例外是空元組,這時圓括號必須的 — 在表示式中允許不帶圓括號的“無”會導致歧義,並讓常見的拼寫錯誤不被捕獲。

6.2.4. 列表、集合與字典的顯示

為了構造列表、集合或字典,Python 提供了稱為“顯示”的特殊語法,每種都有兩種形式:

  • 要麼顯式列出容器內容,

  • 要麼透過一組迴圈和過濾指令來計算,這被稱為 推導式

推導式的通用句法元素是:

comprehension: assignment_expression comp_for
comp_for:      ["async"] "for" target_list "in" or_test [comp_iter]
comp_iter:     comp_for | comp_if
comp_if:       "if" or_test [comp_iter]

推導式由一個單獨的表示式,後跟至少一個 for 子句和零個或多個 forif 子句組成。在這種情況下,新容器的元素是透過從左到右將每個 forif 子句視為一個程式碼塊進行巢狀,並在每次到達最內層塊時對錶達式求值以產生一個元素而生成的。

但是,除了最左邊的 for 子句中的可迭代表達式外,推導式是在一個單獨的隱式巢狀作用域中執行的。這確保了在目標列表中賦值的名稱不會“洩漏”到外層作用域中。

最左側 for 子句中的可迭代物件表示式會在外層作用域被直接求值,然後作為引數被傳給隱式巢狀作用域。 後續的 for 子句以及最左側 for 子句中的任何篩選條件都不能在外層作用域中被求值,因為它們可能依賴於從最左側可迭代物件中獲得的值。 例如: [x*y for x in range(10) for y in range(x, x+10)]

為確保推導式總是能得到適當型別的容器,在隱式巢狀的作用域中禁止使用 yieldyield from 表示式。

從 Python 3.6 開始,在一個 async def 函式中,可以使用 async for 子句來迭代一個 非同步迭代器async def 函式中的推導式可以在開頭的表示式之後跟隨 forasync for 子句,可以包含額外的 forasync for 子句,還可以使用 await 表示式。

如果一個推導式包含 async for 子句,或者包含 await 表示式或其他非同步推導式(除了最左邊 for 子句中的可迭代表達式之外),則它被稱為 非同步推導式。非同步推導式可能會暫停其所在的協程函式的執行。另請參閱 PEP 530

在 3.6 版本加入: 引入了非同步推導式。

在 3.8 版更改: 在隱式巢狀作用域中禁止使用 yieldyield from

在 3.11 版更改: 現在允許在非同步函式內的推導式中使用非同步推導式。外部推導式會隱式地變為非同步。

6.2.5. 列表顯示

列表顯示是一個可能為空的表示式系列,用方括號括起來:

list_display: "[" [flexible_expression_list | comprehension] "]"

列表顯示會產生一個新的列表物件,其內容由表示式列表或推導式指定。當提供一個逗號分隔的表示式列表時,其元素會從左到右求值,並按該順序放入列表物件中。當提供一個推導式時,列表由推導式產生的結果元素構成。

6.2.6. 集合顯示

集合顯示由花括號表示,並透過缺少分隔鍵和值的冒號來與字典顯示區分開來:

set_display: "{" (flexible_expression_list | comprehension) "}"

集合顯示會產生一個新的可變集合物件,其內容由表示式序列或推導式指定。當提供一個逗號分隔的表示式列表時,其元素會從左到右求值並新增到集合物件中。當提供一個推導式時,集合由推導式產生的結果元素構成。

空集合不能用 {} 來構造;這個字面值構造的是一個空字典。

6.2.7. 字典顯示

字典顯示是一個可能為空的字典項(鍵/值對)系列,用花括號括起來:

dict_display:       "{" [dict_item_list | dict_comprehension] "}"
dict_item_list:     dict_item ("," dict_item)* [","]
dict_item:          expression ":" expression | "**" or_expr
dict_comprehension: expression ":" expression comp_for

字典顯示會產生一個新的字典物件。

如果給出一個逗號分隔的字典項序列,它們會從左到右被求值以定義字典的條目:每個鍵物件被用作字典的鍵,以儲存相應的值。這意味著你可以在字典項列表中多次指定同一個鍵,該鍵在最終字典中的值將是最後給定的那個。

雙星號 ** 表示 字典解包。其運算元必須是一個 對映。每個對映項都會被新增到新字典中。後面的值會替換由較早的字典項和較早的字典解包所設定的值。

在 3.5 版本加入: 字典顯示中的解包,最初由 PEP 448 提出。

字典推導式,與列表和集合推導式不同,需要兩個用冒號分隔的表示式,後跟通常的“for”和“if”子句。當推導式執行時,產生的鍵和值元素會按其產生的順序插入到新字典中。

鍵值型別的限制在前面的 標準型別層級結構 部分有列出。(總結一下,鍵型別應該是 可雜湊的,這排除了所有可變物件。)重複鍵之間的衝突不會被檢測到;給定鍵值儲存的最後一個值(在顯示中字面上最右邊的)會生效。

在 3.8 版更改: 在 Python 3.8 之前,在字典推導式中,鍵和值的求值順序沒有明確定義。在 CPython 中,值在鍵之前求值。從 3.8 開始,鍵在值之前求值,正如 PEP 572 所提議的。

6.2.8. 生成器表示式

生成器表示式是一種緊湊的生成器表示法,用圓括號括起來:

generator_expression: "(" expression comp_for ")"

生成器表示式會產生一個新的生成器物件。其語法與推導式相同,只是它用圓括號而不是方括號或花括號括起來。

生成器表示式中使用的變數在生成器物件的 __next__() 方法被呼叫時進行延遲求值(與普通生成器的方式相同)。但是,最左邊的 for 子句中的可迭代表達式會立即被求值,併為該可迭代物件立即建立 迭代器,因此在建立迭代器時產生的錯誤會在生成器表示式定義的地方被丟擲,而不是在檢索第一個值的地方。後續的 for 子句和最左邊 for 子句中的任何過濾條件不能在外層作用域中求值,因為它們可能依賴於從最左邊可迭代物件獲得的值。例如:(x*y for x in range(10) for y in range(x, x+10))

在只有一個引數的呼叫中,圓括號可以省略。詳情請參見 呼叫 一節。

為了避免干擾生成器表示式本身的預期操作,在隱式定義的生成器中禁止使用 yieldyield from 表示式。

如果生成器表示式包含 async for 子句或 await 表示式,則它被稱為 非同步生成器表示式。非同步生成器表示式返回一個新的非同步生成器物件,它是一個非同步迭代器(參見 非同步迭代器)。

在 3.6 版本加入: 引入了非同步生成器表示式。

在 3.7 版更改: 在 Python 3.7 之前,非同步生成器表示式只能出現在 async def 協程中。從 3.7 開始,任何函式都可以使用非同步生成器表示式。

在 3.8 版更改: 在隱式巢狀作用域中禁止使用 yieldyield from

6.2.9. yield 表示式

yield_atom:       "(" yield_expression ")"
yield_from:       "yield" "from" expression
yield_expression: "yield" yield_list | yield_from

yield 表示式用於定義 生成器 函式或 非同步生成器 函式,因此只能在函式定義的函式體中使用。在函式體中使用 yield 表示式會使該函式成為一個生成器函式,而在 async def 函式的函式體中使用它會使該協程函式成為一個非同步生成器函式。例如:

def gen():  # defines a generator function
    yield 123

async def agen(): # defines an asynchronous generator function
    yield 123

由於它們對所在作用域有副作用,yield 表示式不允許作為用於實現推導式和生成器表示式的隱式定義作用域的一部分。

在 3.8 版更改: 在用於實現推導式和生成器表示式的隱式巢狀作用域中禁止使用 Yield 表示式。

生成器函式在下面描述,而非同步生成器函式在 非同步生成器函式 一節中單獨描述。

當一個生成器函式被呼叫時,它返回一個稱為生成器的迭代器。該生成器隨後控制生成器函式的執行。執行在生成器的一個方法被呼叫時開始。此時,執行進行到第一個 yield 表示式,在那裡再次暫停,將 yield_list 的值返回給生成器的呼叫者,如果 yield_list 被省略,則返回 None。所謂的暫停,是指所有區域性狀態都被保留,包括區域性變數的當前繫結、指令指標、內部求值棧以及任何異常處理的狀態。當透過呼叫生成器的一個方法恢復執行時,函式可以像 yield 表示式只是另一個外部呼叫一樣繼續執行。恢復後 yield 表示式的值取決於恢復執行的方法。如果使用 __next__()(通常透過 for 迴圈或內建函式 next()),則結果是 None。否則,如果使用 send(),則結果將是傳入該方法的值。

所有這些使得生成器函式與協程非常相似;它們多次 yield,它們有多個入口點,並且它們的執行可以被暫停。唯一的區別是,生成器函式無法控制它 yield 之後執行應該在哪裡繼續;控制權總是轉移給生成器的呼叫者。

Yield 表示式允許在 try 結構的任何地方使用。如果生成器在最終確定之前(透過達到零引用計數或被垃圾回收)沒有被恢復,生成器-迭代器的 close() 方法將被呼叫,允許任何待處理的 finally 子句執行。

當使用 yield from <expr> 時,提供的表示式必須是一個可迭代物件。透過迭代該可迭代物件產生的值會直接傳遞給當前生成器方法的呼叫者。透過 send() 傳入的任何值以及透過 throw() 傳入的任何異常都會被傳遞給底層的迭代器,如果它有相應的方法。如果不是這種情況,那麼 send() 將引發 AttributeErrorTypeError,而 throw() 將立即引發傳入的異常。

當底層迭代器完成時,引發的 StopIteration 例項的 value 屬性成為 yield 表示式的值。它可以在引發 StopIteration 時顯式設定,或者當子迭代器是生成器時自動設定(透過從子生成器返回值)。

在 3.3 版更改: 添加了 yield from <expr> 來將控制流委託給子迭代器。

當 yield 表示式是賦值語句右側的唯一表達式時,可以省略括號。

參見

PEP 255 - 簡單生成器

向 Python 中新增生成器和 yield 語句的提案。

PEP 342 - 透過增強生成器實現協程

增強生成器的 API 和語法的提案,使其可用作簡單的協程。

PEP 380 - 委託給子生成器的語法

引入 yield_from 語法的提案,使委託給子生成器變得容易。

PEP 525 - 非同步生成器

該提案在 PEP 492 的基礎上進行了擴充套件,為協程函式添加了生成器功能。

6.2.9.1. 生成器-迭代器的方法

本小節描述了生成器迭代器的方法。它們可以用來控制生成器函式的執行。

注意,當生成器已經在執行時呼叫下面任何一個生成器方法,都會引發一個 ValueError 異常。

generator.__next__()

開始執行一個生成器函式或在上次執行的 yield 表示式處恢復它。當一個生成器函式透過 __next__() 方法恢復時,當前的 yield 表示式總是求值為 None。然後執行繼續到下一個 yield 表示式,在那裡生成器再次被暫停,並且 yield_list 的值被返回給 __next__() 的呼叫者。如果生成器在沒有 yield 另一個值的情況下退出,則會引發一個 StopIteration 異常。

此方法通常是隱式呼叫的,例如透過 for 迴圈,或透過內建的 next() 函式。

generator.send(value)

恢復執行並向生成器函式“傳送”一個值。value 引數成為當前 yield 表示式的結果。 send() 方法返回生成器產生的下一個值,或者如果生成器在沒有產生另一個值的情況下退出,則引發 StopIteration。當呼叫 send() 來啟動生成器時,必須以 None 作為引數呼叫,因為沒有可以接收值的 yield 表示式。

generator.throw(value)
generator.throw(type[, value[, traceback]])

在生成器暫停的地方引發一個異常,並返回生成器函式產生的下一個值。如果生成器在沒有產生另一個值的情況下退出,則會引發一個 StopIteration 異常。如果生成器函式沒有捕獲傳入的異常,或者引發了不同的異常,那麼該異常會傳播給呼叫者。

在典型用法中,這是用一個單獨的異常例項來呼叫的,類似於 raise 關鍵字的使用方式。

然而,為了向後相容,第二種簽名形式是被支援的,它遵循了舊版本 Python 的慣例。type 引數應該是一個異常類,而 value 應該是一個異常例項。如果未提供 value,則會呼叫 type 建構函式來獲取一個例項。如果提供了 traceback,它會被設定到異常上,否則儲存在 value 中的任何現有 __traceback__ 屬性可能會被清除。

在 3.12 版更改: 第二個簽名 (type[, value[, traceback]]) 已被棄用,並可能在未來的 Python 版本中移除。

generator.close()

在生成器函式暫停的地方引發一個 GeneratorExit 異常(等同於呼叫 throw(GeneratorExit))。該異常由生成器暫停的 yield 表示式引發。如果生成器函式捕獲了該異常並返回一個值,則該值會從 close() 返回。如果生成器函式已經關閉,或者引發了 GeneratorExit(透過不捕獲該異常),close() 會返回 None。如果生成器產生了一個值,則會引發一個 RuntimeError。如果生成器引發了任何其他異常,它會傳播給呼叫者。如果生成器由於異常或正常退出已經退出,close() 會返回 None 並且沒有其他效果。

在 3.13 版更改: 如果一個生成器在關閉時返回一個值,該值會由 close() 返回。

6.2.9.2. 示例

這裡有一個簡單的例子,演示了生成器和生成器函式的行為:

>>> def echo(value=None):
...     print("Execution starts when 'next()' is called for the first time.")
...     try:
...         while True:
...             try:
...                 value = (yield value)
...             except Exception as e:
...                 value = e
...     finally:
...         print("Don't forget to clean up when 'close()' is called.")
...
>>> generator = echo(1)
>>> print(next(generator))
Execution starts when 'next()' is called for the first time.
1
>>> print(next(generator))
None
>>> print(generator.send(2))
2
>>> generator.throw(TypeError, "spam")
TypeError('spam',)
>>> generator.close()
Don't forget to clean up when 'close()' is called.

有關使用 yield from 的示例,請參閱“Python 新特性”中的 PEP 380: 用於委託給子生成器的語法

6.2.9.3. 非同步生成器函式

在一個使用 async def 定義的函式或方法中出現 yield 表示式,會進一步將該函式定義為一個 非同步生成器 函式。

當一個非同步生成器函式被呼叫時,它返回一個稱為非同步生成器物件的非同步迭代器。該物件隨後控制生成器函式的執行。一個非同步生成器物件通常在協程函式中的 async for 語句中使用,類似於生成器物件在 for 語句中的使用方式。

呼叫非同步生成器的一個方法會返回一個 可等待 物件,當這個物件被等待時,執行就開始。此時,執行會進行到第一個 yield 表示式,在那裡再次被暫停,並將 yield_list 的值返回給等待的協程。與生成器一樣,暫停意味著所有區域性狀態都被保留,包括區域性變數的當前繫結、指令指標、內部求值棧以及任何異常處理的狀態。當透過等待非同步生成器方法返回的下一個物件來恢復執行時,函式可以像 yield 表示式只是另一個外部呼叫一樣繼續執行。恢復後 yield 表示式的值取決於恢復執行的方法。如果使用 __anext__(),則結果是 None。否則,如果使用 asend(),則結果將是傳入該方法的值。

如果一個非同步生成器由於 break、呼叫者任務被取消或其他異常而提前退出,生成器的非同步清理程式碼將會執行,並可能在意外的上下文中引發異常或訪問上下文變數——可能是在它所依賴的任務生命週期結束之後,或者在事件迴圈關閉期間呼叫非同步生成器垃圾回收鉤子時。為了防止這種情況,呼叫者必須透過呼叫 aclose() 方法來顯式關閉非同步生成器,以最終確定生成器並最終將其與事件迴圈分離。

在非同步生成器函式中,yield 表示式允許在 try 結構的任何地方使用。但是,如果一個非同步生成器在最終確定之前(透過達到零引用計數或被垃圾回收)沒有被恢復,那麼 try 結構內的 yield 表示式可能會導致待處理的 finally 子句執行失敗。在這種情況下,執行非同步生成器的事件迴圈或排程器有責任呼叫非同步生成器-迭代器的 aclose() 方法並執行產生的協程物件,從而允許任何待處理的 finally 子句執行。

為了在事件迴圈終止時處理最終化,事件迴圈應定義一個 終結器 函式,該函式接受一個非同步生成器迭代器,並大概呼叫 aclose() 並執行協程。這個 終結器 可以透過呼叫 sys.set_asyncgen_hooks() 來註冊。當第一次迭代時,非同步生成器迭代器將儲存註冊的 終結器,以便在最終化時呼叫。有關 終結器 方法的參考示例,請參見 Lib/asyncio/base_events.pyasyncio.Loop.shutdown_asyncgens 的實現。

在非同步生成器函式中使用表示式 yield from <expr> 是一個語法錯誤。

6.2.9.4. 非同步生成器-迭代器的方法

本小節描述了非同步生成器迭代器的方法,這些方法用於控制生成器函式的執行。

async agen.__anext__()

返回一個可等待物件,當它執行時,會開始執行非同步生成器或在上次執行的 yield 表示式處恢復它。當一個非同步生成器函式透過 __anext__() 方法恢復時,當前的 yield 表示式在返回的可等待物件中總是求值為 None,當它執行時將繼續到下一個 yield 表示式。yield 表示式的 yield_list 的值是完成的協程引發的 StopIteration 異常的值。如果非同步生成器在沒有產生另一個值的情況下退出,該可等待物件會引發一個 StopAsyncIteration 異常,表示非同步迭代已完成。

此方法通常由 async for 迴圈隱式呼叫。

async agen.asend(value)

返回一個可等待物件,當它執行時,會恢復非同步生成器的執行。與生成器的 send() 方法一樣,這會向非同步生成器函式“傳送”一個值,並且 value 引數成為當前 yield 表示式的結果。asend() 方法返回的可等待物件將返回生成器產生的下一個值作為引發的 StopIteration 的值,或者如果非同步生成器在沒有產生另一個值的情況下退出,則引發 StopAsyncIteration。當呼叫 asend() 來啟動非同步生成器時,必須以 None 作為引數呼叫,因為沒有可以接收值的 yield 表示式。

async agen.athrow(value)
async agen.athrow(type[, value[, traceback]])

返回一個可等待物件,該物件在非同步生成器暫停的地方引發一個型別為 type 的異常,並返回生成器函式產生的下一個值作為引發的 StopIteration 異常的值。如果非同步生成器在沒有產生另一個值的情況下退出,該可等待物件會引發一個 StopAsyncIteration 異常。如果生成器函式沒有捕獲傳入的異常,或者引發了不同的異常,那麼當可等待物件執行時,該異常會傳播給可等待物件的呼叫者。

在 3.12 版更改: 第二個簽名 (type[, value[, traceback]]) 已被棄用,並可能在未來的 Python 版本中移除。

async agen.aclose()

返回一個可等待物件,當它執行時,會在非同步生成器函式暫停的地方丟擲一個 GeneratorExit。如果非同步生成器函式隨後正常退出、已經關閉或引發了 GeneratorExit(透過不捕獲該異常),那麼返回的可等待物件將引發一個 StopIteration 異常。之後對非同步生成器的後續呼叫返回的任何可等待物件都將引發一個 StopAsyncIteration 異常。如果非同步生成器產生了一個值,該可等待物件會引發一個 RuntimeError。如果非同步生成器引發了任何其他異常,它會傳播給可等待物件的呼叫者。如果非同步生成器由於異常或正常退出已經退出,那麼對 aclose() 的進一步呼叫將返回一個什麼也不做的可等待物件。

6.3. 原語

原語代表了語言中繫結最緊密的操作。它們的語法是:

primary: atom | attributeref | subscription | slicing | call

6.3.1. 屬性引用

屬性引用是一個原語後跟一個句點和一個名稱:

attributeref: primary "." identifier

原語必須求值為一個支援屬性引用的型別的物件,大多數物件都支援。然後,這個物件被要求產生名稱為該識別符號的屬性。產生的型別和值由物件決定。對同一個屬性引用的多次求值可能會產生不同的物件。

這個過程可以透過重寫 __getattribute__() 方法或 __getattr__() 方法來定製。 __getattribute__() 方法首先被呼叫,它要麼返回一個值,要麼在屬性不可用時引發 AttributeError

如果引發了 AttributeError 並且物件有一個 __getattr__() 方法,那麼該方法將作為備用方法被呼叫。

6.3.2. 訂閱

容器類 例項的訂閱通常會從容器中選擇一個元素。對 泛型類 的訂閱通常會返回一個 泛型別名 物件。

subscription: primary "[" flexible_expression_list "]"

當一個物件被訂閱時,直譯器將對原語和表示式列表進行求值。

原語必須求值為一個支援訂閱的物件。一個物件可以透過定義 __getitem__()__class_getitem__() 中的一個或兩個來支援訂閱。當原語被訂閱時,表示式列表的求值結果將被傳遞給這些方法之一。關於何時呼叫 __class_getitem__ 而不是 __getitem__ 的更多細節,請參見 __class_getitem__ 與 __getitem__

如果表示式列表包含至少一個逗號,或者任何表示式帶星號,表示式列表將求值為一個包含表示式列表項的 tuple。否則,表示式列表將求值為該列表唯一成員的值。

在 3.11 版更改: 表示式列表中的表示式可以帶星號。請參見 PEP 646

對於內建物件,有兩種型別的物件透過 __getitem__() 支援訂閱:

  1. 對映。如果原語是一個 對映,表示式列表必須求值為一個物件,其值是該對映的鍵之一,訂閱會選擇對映中與該鍵對應的值。內建對映類的一個例子是 dict 類。

  2. 序列。如果原語是一個 序列,表示式列表必須求值為一個 int 或一個 slice(如下一節所討論)。內建序列類的例子包括 strlisttuple 類。

正式語法沒有為 序列 中的負數索引做出特殊規定。然而,內建序列都提供了一個 __getitem__() 方法,透過將序列的長度加到索引上來解釋負數索引,因此,例如 x[-1] 選擇 x 的最後一項。結果值必須是一個小於序列中項數的非負整數,訂閱會選擇索引為該值的項(從零開始計數)。由於對負數索引和切片的支援發生在物件的 __getitem__() 方法中,重寫此方法的子類將需要明確新增該支援。

string 是一種特殊的序列,其項是 *字元*。字元不是一個獨立的資料型別,而是一個長度正好為一的字串。

6.3.3. 切片

切片選擇序列物件(例如,字串、元組或列表)中的一個專案範圍。切片可以用作表示式或賦值或 del 語句的目標。切片的語法:

slicing:      primary "[" slice_list "]"
slice_list:   slice_item ("," slice_item)* [","]
slice_item:   expression | proper_slice
proper_slice: [lower_bound] ":" [upper_bound] [ ":" [stride] ]
lower_bound:  expression
upper_bound:  expression
stride:       expression

這裡的正式語法存在歧義:任何看起來像表示式列表的東西也看起來像切片列表,因此任何訂閱都可以被解釋為切片。為了不使語法進一步複雜化,透過定義在這種情況下,將訂閱的解釋優先於切片的解釋來消除歧義(如果切片列表不包含任何真正的切片,就是這種情況)。

切片的語義如下。原語被索引(使用與普通訂閱相同的 __getitem__() 方法),鍵是從切片列表構造的,如下所示。如果切片列表包含至少一個逗號,則鍵是包含切片項轉換結果的元組;否則,單個切片項的轉換結果就是鍵。一個表示式的切片項的轉換是該表示式。一個真正的切片的轉換是一個切片物件(參見 標準型別層級 部分),其 startstopstep 屬性分別是作為下界、上界和步長給出的表示式的值,對缺失的表示式用 None 替代。

6.3.4. 呼叫

呼叫是呼叫一個可呼叫物件(例如,一個 函式),並帶有一個可能為空的 引數 序列:

call:                 primary "(" [argument_list [","] | comprehension] ")"
argument_list:        positional_arguments ["," starred_and_keywords]
                        ["," keywords_arguments]
                      | starred_and_keywords ["," keywords_arguments]
                      | keywords_arguments
positional_arguments: positional_item ("," positional_item)*
positional_item:      assignment_expression | "*" expression
starred_and_keywords: ("*" expression | keyword_item)
                      ("," "*" expression | "," keyword_item)*
keywords_arguments:   (keyword_item | "**" expression)
                      ("," keyword_item | "," "**" expression)*
keyword_item:         identifier "=" expression

在位置引數和關鍵字引數之後可以有一個可選的尾隨逗號,但這不影響語義。

原語必須求值為一個可呼叫物件(使用者定義的函式、內建函式、內建物件的方法、類物件、類例項的方法,以及所有具有 __call__() 方法的物件都是可呼叫的)。所有引數表示式在嘗試呼叫之前都會被求值。關於形式 引數 列表的語法,請參考 函式定義 部分。

如果存在關鍵字引數,它們首先被轉換為位置引數,如下所示。首先,為形式引數建立一個未填充的槽位列表。如果有 N 個位置引數,它們被放置在前 N 個槽位中。接下來,對於每個關鍵字引數,使用識別符號來確定相應的槽位(如果識別符號與第一個形式引數名相同,則使用第一個槽位,以此類推)。如果槽位已經被填充,則會引發一個 TypeError 異常。否則,引數被放置在槽位中,填充它(即使表示式是 None,它也會填充槽位)。當所有引數都處理完畢後,仍然未填充的槽位將用函式定義中相應的預設值填充。(預設值在函式定義時計算一次;因此,一個可變物件如列表或字典用作預設值,將被所有未為相應槽位指定引數值的呼叫所共享;這通常應該避免。)如果存在任何未指定預設值的未填充槽位,則會引發一個 TypeError 異常。否則,已填充槽位的列表將用作呼叫的引數列表。

CPython 實現細節: 一個實現可能提供一些內建函式,其位置引數沒有名稱,即使它們為了文件目的被“命名”了,因此不能透過關鍵字提供。在 CPython 中,對於使用 PyArg_ParseTuple() 解析其引數的 C 實現函式就是這種情況。

如果位置引數的數量多於形式引數槽位的數量,會引發一個 TypeError 異常,除非存在一個使用 *identifier 語法的形式引數;在這種情況下,該形式引數會接收一個包含多餘位置引數的元組(如果沒有多餘的位置引數,則為空元組)。

如果任何關鍵字引數不對應於形式引數名稱,會引發一個 TypeError 異常,除非存在一個使用 **identifier 語法的形式引數;在這種情況下,該形式引數會接收一個包含多餘關鍵字引數的字典(使用關鍵字作為鍵,引數值作為相應的值),或者如果沒有任何多餘的關鍵字引數,則為一個(新的)空字典。

如果語法 *expression 出現在函式呼叫中,expression 必須求值為一個 可迭代物件。這些可迭代物件中的元素被視為額外的定位引數。對於呼叫 f(x1, x2, *y, x3, x4),如果 *y* 求值為序列 *y1*, ..., *yM*,這等同於一個有 M+4 個定位引數 *x1*, *x2*, *y1*, ..., *yM*, *x3*, *x4* 的呼叫。

這樣做的一個後果是,雖然 *expression 語法可以出現在顯式關鍵字引數*之後*,但它在關鍵字引數(以及任何 **expression 引數——見下文)*之前*處理。所以:

>>> def f(a, b):
...     print(a, b)
...
>>> f(b=1, *(2,))
2 1
>>> f(a=1, *(2,))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got multiple values for keyword argument 'a'
>>> f(1, *(2,))
1 2

在同一次呼叫中同時使用關鍵字引數和 *expression 語法是不尋常的,所以在實踐中這種混淆不常出現。

如果語法 **expression 出現在函式呼叫中,expression 必須求值為一個 對映,其內容被視為額外的關鍵字引數。如果一個與鍵匹配的引數已經(透過顯式關鍵字引數或從另一次解包中)被賦值,則會引發一個 TypeError 異常。

當使用 **expression 時,此對映中的每個鍵都必須是字串。來自對映的每個值都會被賦給名稱與鍵相等且能接受關鍵字引數的第一個形參。鍵名不必是 Python 識別符號(例如 "max-temp °F" 也是可接受的,但它不會匹配任何可能被宣告的形參)。如果沒有匹配的形參,鍵值對就會被 ** 形參收集(如果存在),否則將引發 TypeError 異常。

使用 *identifier**identifier 語法的形參不能用作位置引數槽或關鍵字引數名稱。

在 3.5 版本發生變更: 函式呼叫現在接受任意數量的 *** 解包。位置引數可以跟在可迭代物件解包 (*) 之後,關鍵字引數可以跟在字典解包 (**) 之後。最初由 PEP 448 提出。

呼叫總是會返回某個值,也可能是 None,除非它引發了異常。這個值的計算方式取決於可呼叫物件的型別。

如果它是——

使用者自定義函式

函式的程式碼塊會被執行,並向其傳入引數列表。程式碼塊要做的第一件事是把形參繫結到傳入的引數;相關描述見 函式定義 一節。當代碼塊執行 return 語句時,將指定函式呼叫的返回值。如果程式碼塊執行到末尾而沒有執行 return 語句,則返回值為 None

內建函式或方法

結果由直譯器決定;內建函式和方法的描述請見 內建函式

類物件

返回該類的一個新例項。

類例項方法

會呼叫相應的使用者自定義函式,其引數列表比呼叫的引數列表多一個:該例項會成為第一個引數。

類例項

該類必須定義 __call__() 方法;其效果等同於呼叫了該方法。

6.4. Await 表示式

可等待 物件上暫停 協程 的執行。只能在 協程函式 內部使用。

await_expr: "await" primary

在 3.5 版本加入。

6.5. 乘方運算子

乘方運算子與其左側的一元運算子繫結得更緊密,但與其右側的一元運算子繫結得更鬆散。其語法為:

power: (await_expr | primary) ["**" u_expr]

因此,在一個未加括號的乘方和一元運算子序列中,運算子從右至左求值(這並不限制運算元的求值順序):-1**2 的結果為 -1

乘方運算子的語義與內建的 pow() 函式(當使用兩個引數呼叫時)相同:它會得到左側引數的右側引數次冪。數值引數會先被轉換為通用型別,結果也是該型別。

對於整數運算元,結果的型別與運算元相同,除非第二個引數為負數;在這種情況下,所有引數都會被轉換為浮點數,並返回一個浮點數結果。例如,10**2 返回 100,但 10**-2 返回 0.01

0.0 進行負數次冪運算會導致 ZeroDivisionError。將負數進行分數次冪運算會得到一個 complex 複數。(在早期版本中,這會引發 ValueError。)

此操作可透過特殊的 __pow__()__rpow__() 方法進行自定義。

6.6. 一元算術和位運算

所有一元算術和位運算子都具有相同的優先順序:

u_expr: power | "-" u_expr | "+" u_expr | "~" u_expr

一元 - (負號) 運算子會返回其數值引數的相反數;此操作可透過 __neg__() 特殊方法進行過載。

一元 + (正號) 運算子會返回其數值引數本身;此操作可透過 __pos__() 特殊方法進行過載。

一元 ~ (取反) 運算子會返回其整數引數的按位取反結果。 x 的按位取反被定義為 -(x+1)。它只適用於整數或過載了 __invert__() 特殊方法的自定義物件。

在這三種情況下,如果引數的型別不正確,則會引發 TypeError 異常。

6.7. 二元算術運算

二元算術運算具有常規的優先順序。請注意,其中某些運算也適用於特定的非數字型別。除了乘方運算子,只有兩個優先順序,一個是乘法類運算子,另一個是加法類運算子:

m_expr: u_expr | m_expr "*" u_expr | m_expr "@" m_expr |
        m_expr "//" u_expr | m_expr "/" u_expr |
        m_expr "%" u_expr
a_expr: m_expr | a_expr "+" m_expr | a_expr "-" m_expr

* (乘法) 運算子得出其引數的乘積。引數要麼都必須是數字,要麼其中一個引數必須是整數,另一個必須是序列。在前一種情況下,數字會先被轉換為通用的實數型別,然後相乘。在後一種情況下,將執行序列重複;負的重複因子會產生一個空序列。

此操作可透過特殊的 __mul__()__rmul__() 方法進行自定義。

在 3.14 版本發生變更: 如果只有一個運算元是複數,則另一個運算元將被轉換為浮點數。

@ (at) 運算子旨在用於矩陣乘法。沒有任何內建的 Python 型別實現此運算子。

此操作可透過特殊的 __matmul__()__rmatmul__() 方法進行自定義。

在 3.5 版本加入。

/ (除法) 和 // (整除) 運算子返回其引數的商。數值引數會先被轉換為通用型別。整數除法會產生一個浮點數,而整數整除則會得到一個整數;其結果是對數學除法的結果應用 'floor' (向下取整) 函式。除以零會引發 ZeroDivisionError 異常。

除法運算可透過特殊的 __truediv__()__rtruediv__() 方法進行自定義。整除運算可透過特殊的 __floordiv__()__rfloordiv__() 方法進行自定義。

% (取模) 運算子返回第一個引數除以第二個引數的餘數。數值引數會先被轉換為通用型別。右側引數為零會引發 ZeroDivisionError 異常。引數可以是浮點數,例如 3.14%0.7 等於 0.34 (因為 3.14 等於 4*0.7 + 0.34)。取模運算子的結果符號總是與第二個運算元相同(或為零);結果的絕對值嚴格小於第二個運算元的絕對值 [1]

整除和取模運算子由以下恆等式關聯:x == (x//y)*y + (x%y)。整除和取模也與內建函式 divmod() 相關:divmod(x, y) == (x//y, x%y)[2]

除了對數字執行取模運算外,% 運算子還被字串物件過載,用於執行舊式字串格式化(也稱為插值)。字串格式化的語法在 Python 庫參考的 printf 風格的字串格式化 一節中有詳細描述。

取模運算可透過特殊的 __mod__()__rmod__() 方法進行自定義。

整除運算子、取模運算子和 divmod() 函式未對複數定義。如果需要,可使用 abs() 函式將其轉換為浮點數。

+ (加法) 運算子得出其引數的和。引數要麼都必須是數字,要麼都必須是相同型別的序列。在前一種情況下,數字會先被轉換為通用的實數型別,然後相加。在後一種情況下,序列會被拼接。

此操作可透過特殊的 __add__()__radd__() 方法進行自定義。

在 3.14 版本發生變更: 如果只有一個運算元是複數,則另一個運算元將被轉換為浮點數。

- (減法) 運算子得出其引數的差。數值引數會先被轉換為通用的實數型別。

此操作可透過特殊的 __sub__()__rsub__() 方法進行自定義。

在 3.14 版本發生變更: 如果只有一個運算元是複數,則另一個運算元將被轉換為浮點數。

6.8. 移位運算

移位運算的優先順序低於算術運算:

shift_expr: a_expr | shift_expr ("<<" | ">>") a_expr

這些運算子接受整數作為引數。它們將第一個引數向左或向右移動由第二個引數給定的位數。

左移運算可透過特殊的 __lshift__()__rlshift__() 方法進行自定義。右移運算可透過特殊的 __rshift__()__rrshift__() 方法進行自定義。

右移 n 位被定義為整除 pow(2,n)。左移 n 位被定義為乘以 pow(2,n)

6.9. 二元位運算

三種位運算子各自具有不同的優先順序:

and_expr: shift_expr | and_expr "&" shift_expr
xor_expr: and_expr | xor_expr "^" and_expr
or_expr:  xor_expr | or_expr "|" xor_expr

& 運算子返回其引數的按位與結果,引數必須是整數,或者其中之一是重寫了 __and__()__rand__() 特殊方法的自定義物件。

^ 運算子返回其引數的按位異或(XOR)結果,引數必須是整數,或者其中之一是重寫了 __xor__()__rxor__() 特殊方法的自定義物件。

| 運算子返回其引數的按位(或)或結果,引數必須是整數,或者其中之一是重寫了 __or__()__ror__() 特殊方法的自定義物件。

6.10. 比較運算

與 C 語言不同,Python 中所有比較運算都具有相同的優先順序,該優先順序低於任何算術、移位或位運算。同樣與 C 語言不同的是,像 a < b < c 這樣的表示式具有數學中常規的解釋:

comparison:    or_expr (comp_operator or_expr)*
comp_operator: "<" | ">" | "==" | ">=" | "<=" | "!="
               | "is" ["not"] | ["not"] "in"

比較運算會產生布爾值:TrueFalse。自定義的富比較方法可以返回非布林值。在這種情況下,Python 會在布林上下文中對該值呼叫 bool()

比較運算可以任意串連,例如,x < y <= z 等價於 x < y and y <= z,區別在於 y 只被求值一次(但兩種情況下,當 x < y 為假時,z 完全不會被求值)。

形式上,如果 a, b, c, ..., y, z 是表示式,op1, op2, ..., opN 是比較運算子,那麼 a op1 b op2 c ... y opN z 等價於 a op1 b and b op2 c and ... y opN z,區別在於每個表示式最多隻被求值一次。

注意,a op1 b op2 c 並不意味著 ac 之間有任何型別的比較,因此,例如 x < y > z 是完全合法的(儘管可能不太美觀)。

6.10.1. 值比較

運算子 <, >, ==, >=, <=, 和 != 比較兩個物件的值。這兩個物件不需要是相同的型別。

物件、值與型別 一章指出,物件具有一個值(除了型別和標識)。物件的值在 Python 中是一個相當抽象的概念:例如,沒有一個規範的方法來訪問一個物件的值。此外,也沒有要求一個物件的值應該以特定的方式構造,例如由其所有資料屬性組成。比較運算子實現了關於物件值的特定概念。可以認為它們是透過其比較實現間接地定義了物件的值。

因為所有型別都是 object 的(直接或間接)子型別,它們都繼承了 object 的預設比較行為。型別可以透過實現富比較方法來自定義它們的比較行為,例如 __lt__(),這在 基本自定義 中有描述。

相等性比較(==!=)的預設行為是基於物件的標識。因此,具有相同標識的例項的相等性比較結果為相等,而具有不同標識的例項的相等性比較結果為不相等。這種預設行為的一個動機是希望所有物件都是自反的(即 x is y 意味著 x == y)。

預設的順序比較(<, >, <=, 和 >=)未被提供;嘗試進行此類比較會引發 TypeError。這種預設行為的一個動機是缺乏與相等性類似的恆等性。

預設的相等性比較行為——即具有不同標識的例項總是不相等的——可能與那些對物件值和基於值的相等性有合理定義的型別所需要的行為相反。這類型別需要自定義它們的比較行為,事實上,許多內建型別已經這樣做了。

下面的列表描述了最重要的內建型別的比較行為。

  • 內建數值型別(數值型別 --- int, float, complex)以及標準庫型別 fractions.Fractiondecimal.Decimal 的數字可以在其型別內部和跨型別進行比較,但有一個限制,即複數不支援順序比較。在所涉型別的限制範圍內,它們的比較在數學上(演算法上)是正確的,不會損失精度。

    非數字值 float('NaN')decimal.Decimal('NaN') 是特殊的。任何數字與非數字值的有序比較都為假。一個反直覺的推論是,非數字值不等於它們自己。例如,如果 x = float('NaN'),則 3 < xx < 3x == x 均為假,而 x != x 為真。這種行為符合 IEEE 754 標準。

  • NoneNotImplemented 是單例物件。PEP 8 建議對單例物件的比較應始終使用 isis not,而不是相等運算子。

  • 二進位制序列(bytesbytearray 的例項)可以在其型別內部和跨型別進行比較。它們使用其元素的數值進行字典序比較。

  • 字串(str 的例項)使用其字元的 Unicode 數值碼點(內建函式 ord() 的結果)進行字典序比較。[3]

    字串和二進位制序列不能直接比較。

  • 序列(tuplelistrange 的例項)只能在各自的型別內部進行比較,但有一個限制,即 range 不支援順序比較。這些型別之間的相等性比較結果為不相等,而跨型別的順序比較會引發 TypeError

    序列使用對應元素的比較進行字典序比較。內建容器通常假定相同的物件彼此相等。這使它們可以跳過對相同物件的相等性測試,以提高效能並維護其內部不變數。

    內建集合之間的字典序比較工作方式如下:

    • 要使兩個集合比較為相等,它們必須是相同型別,具有相同長度,並且每對對應元素必須比較為相等(例如,[1,2] == (1,2) 為假,因為型別不同)。

    • 支援順序比較的集合的排序與其第一個不相等元素的排序相同(例如,[1,2,x] <= [1,2,y]x <= y 的值相同)。如果不存在對應的元素,則較短的集合排在前面(例如,[1,2] < [1,2,3] 為真)。

  • 對映(dict 的例項)當且僅當它們具有相等的 (鍵, 值) 對時才比較為相等。鍵和值的相等性比較強制執行自反性。

    順序比較(<><=>=)會引發 TypeError

  • 集合(setfrozenset 的例項)可以在其型別內部和跨型別進行比較。

    它們定義順序比較運算子來表示子集和超集測試。這些關係不定義全序關係(例如,兩個集合 {1,2}{2,3} 既不相等,也不是彼此的子集或超集)。因此,集合不適合作為依賴於全序的函式的引數(例如,min()max()sorted() 在給定一個集合列表作為輸入時會產生未定義的結果)。

    集合的比較會強制其元素的自反性。

  • 大多數其他內建型別沒有實現比較方法,因此它們繼承了預設的比較行為。

自定義比較行為的使用者定義類應儘可能遵循一些一致性規則:

  • 相等性比較應該是自反的。換句話說,相同的物件應該比較為相等:

    x is y 意味著 x == y

  • 比較應該是對稱的。換句話說,以下表達式應該具有相同的結果:

    x == yy == x

    x != yy != x

    x < yy > x

    x <= yy >= x

  • 比較應該是可傳遞的。以下(非詳盡的)示例說明了這一點:

    x > y and y > z 意味著 x > z

    x < y and y <= z 意味著 x < z

  • 反向比較應該導致布林否定。換句話說,以下表達式應該具有相同的結果:

    x == ynot x != y

    x < ynot x >= y (對於全序)

    x > ynot x <= y (對於全序)

    最後兩個表示式適用於全序集合(例如,序列,但不適用於集合或對映)。另請參見 total_ordering() 裝飾器。

  • hash() 的結果應該與相等性保持一致。相等的物件應該具有相同的雜湊值,或者被標記為不可雜湊。

Python 不強制執行這些一致性規則。事實上,非數字值就是不遵循這些規則的一個例子。

6.10.2. 成員檢測運算

運算子 innot in 用於檢測成員關係。x in sxs 的一個成員時結果為 True,否則為 Falsex not in s 返回 x in s 的邏輯非結果。所有內建序列和集合型別都支援此運算,字典也支援,對於字典,in 檢測字典是否擁有給定的鍵。對於容器型別如 list、tuple、set、frozenset、dict 或 collections.deque,表示式 x in y 等價於 any(x is e or x == e for e in y)

對於 string 和 bytes 型別,x in yTrue 當且僅當 xy 的一個子串。一個等價的測試是 y.find(x) != -1。空字串總是被認為是任何其他字串的子串,因此 "" in "abc" 將返回 True

對於定義了 __contains__() 方法的使用者自定義類,x in yy.__contains__(x) 返回真值時返回 True,否則返回 False

對於未定義 __contains__() 但定義了 __iter__() 的使用者自定義類,x in yTrue,如果能在迭代 y 的過程中找到某個值 z 使得表示式 x is z or x == z 為真。如果在迭代過程中引發了異常,就好像是 in 引發了那個異常。

最後,會嘗試舊式的迭代協議:如果一個類定義了 __getitem__()x in yTrue 當且僅當存在一個非負整數索引 i 使得 x is y[i] or x == y[i],並且沒有更小的整數索引引發 IndexError 異常。(如果引發了任何其他異常,就好像是 in 引發了那個異常)。

運算子 not in 被定義為具有與 in 相反的真值。

6.10.3. 標識比較

運算子 isis not 用於測試物件的標識:x is y 為真當且僅當 xy 是同一個物件。物件的標識是使用 id() 函式確定的。x is not y 則返回相反的真值。[4]

6.11. 布林運算

or_test:  and_test | or_test "or" and_test
and_test: not_test | and_test "and" not_test
not_test: comparison | "not" not_test

在布林運算的語境中,以及當表示式被控制流語句使用時,以下值被解釋為假:FalseNone、所有型別的數值零,以及空字串和空容器(包括字串、元組、列表、字典、集合和凍結集合)。所有其他值都被解釋為真。使用者定義的物件可以透過提供 __bool__() 方法來自定義它們的真值。

運算子 not 在其引數為假時返回 True,否則返回 False

表示式 x and y 首先對 x 進行求值;如果 x 為假,則返回其值;否則,對 y 進行求值並返回結果值。

表示式 x or y 首先對 x 進行求值;如果 x 為真,則返回其值;否則,對 y 進行求值並返回結果值。

請注意,andor 都不將其返回值和型別限制為 FalseTrue,而是返回最後一個被求值的引數。這有時很有用,例如,如果 s 是一個字串,如果它為空,則應替換為預設值,那麼表示式 s or 'foo' 就能得到期望的值。因為 not 必須建立一個新值,所以它會返回一個布林值,而不管其引數的型別如何(例如,not 'foo' 產生 False 而不是 '')。

6.12. 賦值表示式

assignment_expression: [identifier ":="] expression

賦值表示式(有時也稱為“命名錶達式”或“海象運算子”)將一個 表示式 賦給一個 識別符號,同時還返回該 表示式 的值。

一個常見的用例是處理匹配的正則表示式時:

if matching := pattern.search(data):
    do_something(matching)

或者,在分塊處理檔案流時:

while chunk := file.read(9000):
    process(chunk)

當作為表示式語句使用,以及在切片、條件、lambda、關鍵字引數和推導式-if表示式中以及在 assertwithassignment 語句中作為子表示式使用時,賦值表示式必須用括號括起來。在所有其他可以使用的地方,括號都不是必需的,包括在 ifwhile 語句中。

在 3.8 版本加入: 關於賦值表示式的更多細節,請參見 PEP 572

6.13. 條件表示式

conditional_expression: or_test ["if" or_test "else" expression]
expression:             conditional_expression | lambda_expr

條件表示式(有時稱為“三元運算子”)是 if-else 語句的替代方案。由於它是一個表示式,它會返回一個值,並且可以作為子表示式出現。

表示式 x if C else y 首先對條件 C(而不是 x)進行求值。如果 C 為真,則對 x 進行求值並返回其值;否則,對 y 進行求值並返回其值。

關於條件表示式的更多細節,請參見 PEP 308

6.14. Lambda 表示式

lambda_expr: "lambda" [parameter_list] ":" expression

Lambda 表示式(有時稱為 lambda 形式)用於建立匿名函式。表示式 lambda parameters: expression 會產生一個函式物件。這個未命名的物件的行為類似於用以下方式定義的函式物件:

def <lambda>(parameters):
    return expression

關於引數列表的語法,請參見 函式定義 一節。請注意,用 lambda 表示式建立的函式不能包含語句或註解。

6.15. 表示式列表

starred_expression:       "*" or_expr | expression
flexible_expression:      assignment_expression | starred_expression
flexible_expression_list: flexible_expression ("," flexible_expression)* [","]
starred_expression_list:  starred_expression ("," starred_expression)* [","]
expression_list:          expression ("," expression)* [","]
yield_list:               expression_list | starred_expression "," [starred_expression_list]

除非作為列表或集合顯示的一部分,否則包含至少一個逗號的表示式列表會產生一個元組。元組的長度是列表中表達式的數量。表示式從左到右求值。

一個星號 * 表示可迭代物件解包。其運算元必須是一個可迭代物件。該可迭代物件被展開為一個元素序列,這些元素在解包的位置被包含到新的元組、列表或集合中。

在 3.5 版本加入: 表示式列表中的可迭代物件解包,最初由 PEP 448 提出。

在 3.11 版本加入: 表示式列表中的任何項都可以加星號。參見 PEP 646

僅在建立單元素元組時需要一個末尾的逗號,例如 1,;在所有其他情況下,它是可選的。沒有末尾逗號的單個表示式不會建立元組,而是返回該表示式的值。(要建立空元組,請使用一對空括號:()。)

6.16. 求值順序

Python 從左到右對錶達式進行求值。請注意,在求值賦值語句時,右側會在左側之前被求值。

在下面的行中,表示式將按照其後綴的算術順序進行求值:

expr1, expr2, expr3, expr4
(expr1, expr2, expr3, expr4)
{expr1: expr2, expr3: expr4}
expr1 + expr2 * (expr3 - expr4)
expr1(expr2, expr3, *expr4, **expr5)
expr3, expr4 = expr1, expr2

6.17. 運算子優先順序

下表總結了 Python 中的運算子優先順序,從最高優先順序(最緊密繫結)到最低優先順序(最鬆散繫結)。同一框中的運算子具有相同的優先順序。除非明確給出語法,否則運算子是二元的。同一框中的運算子從左到右分組(除了乘方和條件表示式,它們從右到左分組)。

請注意,比較、成員檢測和標識檢測都具有相同的優先順序,並具有從左到右的鏈式特性,如比較運算一節所述。

運算子

描述

(表示式...),

[表示式...], {鍵: 值...}, {表示式...}

繫結或帶括號的表示式、列表顯示、字典顯示、集合顯示

x[index], x[index:index], x(arguments...), x.attribute

訂閱、切片、呼叫、屬性引用

await x

Await 表示式

**

乘方 [5]

+x, -x, ~x

正、負、按位非

*, @, /, //, %

乘法、矩陣乘法、除法、整除、取餘 [6]

+, -

加法和減法

<<, >>

移位

&

按位與

^

按位異或

|

按位或

in, not in, is, is not, <, <=, >, >=, !=, ==

比較,包括成員檢測和標識檢測

not x

布林非

布林與

布林或

ifelse

條件表示式

lambda

Lambda 表示式

:=

賦值表示式

腳註