設計與歷史常見問題

為什麼 Python 使用縮排來分組語句?

Guido van Rossum 認為使用縮排進行分組非常優雅,並且為普通 Python 程式的清晰度做出了很大貢獻。大多數人在一段時間後都會愛上這個功能。

由於沒有 begin/end 括號,因此解析器和人類讀者感知的分組之間不會存在分歧。有時 C 程式設計師會遇到如下程式碼片段

if (x <= y)
        x++;
        y--;
z++;

只有 x++ 語句在條件為真時執行,但縮排使許多人認為情況並非如此。即使是有經驗的 C 程式設計師有時也會長時間盯著它,想知道為什麼即使對於 x > y 也遞減了 y

由於沒有 begin/end 括號,Python 更不容易發生編碼風格衝突。在 C 中,有很多不同的方法來放置大括號。在習慣了使用特定樣式讀取和編寫程式碼後,當以不同的樣式讀取(或被要求編寫)時,會感到有些不安。

許多編碼風格將 begin/end 括號放在單獨的一行上。這使得程式變得更長,並浪費了寶貴的螢幕空間,從而更難獲得程式的良好概覽。理想情況下,一個函式應該適合一個螢幕(例如,20-30 行)。 20 行 Python 可以比 20 行 C 做更多的工作。這不僅僅是因為缺少 begin/end 括號——缺少宣告和高階資料型別也是原因——但基於縮排的語法當然有幫助。

為什麼我在簡單的算術運算中得到奇怪的結果?

請參閱下一個問題。

為什麼浮點計算如此不精確?

使用者經常會對如下結果感到驚訝

>>> 1.2 - 1.0
0.19999999999999996

並認為這是 Python 中的一個 bug。事實並非如此。這與 Python 關係不大,而與底層平臺如何處理浮點數關係更大。

CPython 中的 float 型別使用 C double 進行儲存。 float 物件的值以固定精度(通常為 53 位)的二進位制浮點數儲存,Python 使用 C 操作(而 C 操作又依賴於處理器中的硬體實現)來執行浮點運算。這意味著就浮點運算而言,Python 的行為與包括 C 和 Java 在內的許多流行語言的行為類似。

許多可以用十進位制表示法輕鬆寫出的數字不能用二進位制浮點數精確表示。例如,之後

>>> x = 1.2

x 儲存的值是十進位制值 1.2 的(非常好的)近似值,但並不完全等於它。在典型機器上,實際儲存的值是

1.0011001100110011001100110011001100110011001100110011 (binary)

這正好是

1.1999999999999999555910790149937383830547332763671875 (decimal)

53 位的典型精度為 Python 浮點數提供了 15-16 位十進位制數字的精度。

有關更完整的解釋,請參閱 Python 教程中的 浮點算術 章節。

為什麼 Python 字串是不可變的?

有幾個優點。

一個優點是效能:知道字串是不可變的意味著我們可以在建立時為其分配空間,並且儲存需求是固定的且不變的。這也是元組和列表之間區別的原因之一。

另一個優點是 Python 中的字串被認為是與數字一樣的“基本”型別。任何活動都不會將值 8 更改為其他任何值,在 Python 中,任何活動都不會將字串“eight”更改為其他任何值。

為什麼必須在方法定義和呼叫中顯式使用 'self'?

這個想法是從 Modula-3 中借鑑的。事實證明它非常有用,原因有很多。

首先,更明顯的是,您正在使用方法或例項屬性,而不是區域性變數。讀取 self.xself.meth() 可以清楚地表明正在使用例項變數或方法,即使您不熟悉類定義。在 C++ 中,您可以透過缺少區域性變數宣告來判斷(假設全域性變數很少或容易識別)——但在 Python 中,沒有區域性變數宣告,因此您必須查詢類定義才能確定。一些 C++ 和 Java 編碼標準要求例項屬性具有 m_ 字首,因此這種顯式性在這些語言中也很有用。

其次,這意味著如果您想從特定類顯式引用或呼叫該方法,則不需要任何特殊的語法。在 C++ 中,如果您想使用在派生類中被覆蓋的基類方法,則必須使用 :: 運算子——在 Python 中,您可以編寫 baseclass.methodname(self, <argument list>)。這對於 __init__() 方法特別有用,並且通常在派生類方法想要擴充套件同名的基類方法,因此必須以某種方式呼叫基類方法的情況下非常有用。

最後,對於例項變數,它解決了賦值的語法問題:由於 Python 中的區域性變數(根據定義!)是在函式體中被賦值的變數(並且沒有顯式宣告為全域性變數),因此必須有一種方法來告訴直譯器賦值的目的是賦值給例項變數而不是區域性變數,並且最好是語法的(為了效率)。C++ 透過宣告來實現這一點,但 Python 沒有宣告,並且僅僅為此目的引入宣告將是一種遺憾。使用顯式 self.var 可以很好地解決這個問題。類似地,對於使用例項變數,必須編寫 self.var 意味著方法內部對非限定名稱的引用不必搜尋例項的目錄。換句話說,區域性變數和例項變數位於兩個不同的名稱空間中,您需要告訴 Python 使用哪個名稱空間。

為什麼我不能在表示式中使用賦值?

從 Python 3.8 開始,您可以!

使用海象運算子 := 的賦值表示式會在表示式中賦值給變數

while chunk := fp.read(200):
   print(chunk)

有關更多資訊,請參見 PEP 572

為什麼 Python 對某些功能使用方法(例如 list.index()),而對其他功能使用函式(例如 len(list))?

正如 Guido 所說

(a) 對於某些操作,字首表示法比字尾表示法更易讀——字首(和中綴!)操作在數學中有著悠久的傳統,數學喜歡視覺效果有助於數學家思考問題的符號。將諸如 x*(a+b) 之類的公式輕鬆重寫為 x*a + x*b,並將其與使用原始 OO 表示法執行相同操作的笨拙性進行比較。

(b) 當我讀到程式碼 `len(x)` 時,我知道它是在請求某個事物的長度。這告訴我兩件事:結果是一個整數,並且引數是某種容器。相反,當我讀到 `x.len()` 時,我必須已經知道 `x` 是某種實現了介面或繼承自具有標準 `len()` 的類的容器。 看看我們有時會遇到的困惑,當一個沒有實現對映的類有一個 `get()` 或 `keys()` 方法,或者一個不是檔案的東西有一個 `write()` 方法時。

https://mail.python.org/pipermail/python-3000/2006-November/004643.html

為什麼 join() 是一個字串方法而不是列表或元組方法?

從 Python 1.6 開始,字串變得更像其他標準型別,當時添加了方法,這些方法提供了與使用字串模組的函式一直可用的相同功能。 大多數這些新方法已被廣泛接受,但似乎讓一些程式設計師感到不舒服的方法是

", ".join(['1', '2', '4', '8', '16'])

這會得到結果

"1, 2, 4, 8, 16"

關於這種用法,通常有兩種常見的反對意見。

第一個意見是:“使用字串字面量(字串常量)的方法看起來真的很醜”,對此的回答是,它可能看起來是這樣,但字串字面量只是一個固定值。 如果允許在繫結到字串的名稱上使用方法,那麼沒有邏輯上的理由使它們在字面量上不可用。

第二個反對意見通常是:“我實際上是在告訴一個序列,用一個字串常量將其成員連線在一起”。 可惜,你不是。 出於某種原因,似乎對於將 split() 作為字串方法沒有那麼大的困難,因為在這種情況下,很容易看出

"1, 2, 4, 8, 16".split(", ")

是對字串字面量的指令,要求返回由給定分隔符(或預設情況下,任意執行的空白字元)分隔的子字串。

join() 是一個字串方法,因為在使用它時,您是在告訴分隔符字串迭代字串序列,並在相鄰元素之間插入自身。 此方法可以與任何遵守序列物件規則的引數一起使用,包括您自己可能定義的任何新類。 位元組和位元組陣列物件也存在類似的方法。

異常有多快?

如果沒有引發異常,try/except 塊非常高效。 實際捕獲異常是昂貴的。 在 2.0 之前的 Python 版本中,通常使用這種習慣用法

try:
    value = mydict[key]
except KeyError:
    mydict[key] = getvalue(key)
    value = mydict[key]

只有當您期望字典幾乎總是具有該鍵時,這才有意義。 如果不是這種情況,您可以像這樣編碼

if key in mydict:
    value = mydict[key]
else:
    value = mydict[key] = getvalue(key)

對於這種情況,您還可以使用 value = dict.setdefault(key, getvalue(key)),但前提是 getvalue() 呼叫足夠便宜,因為它在所有情況下都會被求值。

為什麼 Python 中沒有 switch 或 case 語句?

通常,結構化的 switch 語句在表示式具有特定值或一組值時執行一個程式碼塊。 從 Python 3.10 開始,可以使用 match ... case 語句輕鬆匹配字面值或名稱空間中的常量。 較舊的替代方法是一系列的 if... elif... elif... else

對於需要從大量可能性中進行選擇的情況,您可以建立一個將 case 值對映到要呼叫的函式的字典。 例如

functions = {'a': function_1,
             'b': function_2,
             'c': self.method_1}

func = functions[value]
func()

對於呼叫物件的方法,您可以透過使用 getattr() 內建函式來檢索具有特定名稱的方法,從而進一步簡化

class MyVisitor:
    def visit_a(self):
        ...

    def dispatch(self, value):
        method_name = 'visit_' + str(value)
        method = getattr(self, method_name)
        method()

建議您為方法名稱使用字首,例如本例中的 visit_。 如果沒有這樣的字首,並且值來自不受信任的來源,則攻擊者將能夠呼叫您物件上的任何方法。

模仿具有 fallthrough 的 switch,就像 C 的 switch-case-default 一樣,是可能的,但難度更大,並且不太需要。

為什麼不能在直譯器中模擬執行緒,而不是依賴於特定於作業系統的執行緒實現?

答案 1:不幸的是,直譯器為每個 Python 堆疊幀至少推送一個 C 堆疊幀。 此外,擴充套件可以在幾乎隨機的時刻回撥到 Python。 因此,完整的執行緒實現需要對 C 的執行緒支援。

答案 2:幸運的是,有 Stackless Python,它具有完全重新設計的直譯器迴圈,可以避免 C 堆疊。

為什麼 lambda 表示式不能包含語句?

Python lambda 表示式不能包含語句,因為 Python 的語法框架無法處理巢狀在表示式中的語句。 但是,在 Python 中,這不是一個嚴重的問題。 與其他語言中的 lambda 形式(它們添加了功能)不同,Python lambda 只是一個簡寫符號,如果您懶得定義函式。

函式在 Python 中已經是第一類物件,並且可以在區域性作用域中宣告。 因此,使用 lambda 而不是區域性定義函式的唯一優點是您無需為函式發明一個名稱 - 但這只是一個區域性變數,函式物件(它與 lambda 表示式產生的物件完全相同)被分配給它!

可以將 Python 編譯為機器程式碼、C 或其他某種語言嗎?

Cython 將帶有可選註釋的修改版 Python 編譯為 C 擴充套件。Nuitka 是一個新興的 Python 到 C++ 程式碼的編譯器,旨在支援完整的 Python 語言。

Python 如何管理記憶體?

Python 記憶體管理的細節取決於實現。 Python 的標準實現 CPython 使用引用計數來檢測不可訪問的物件,並使用另一種機制來收集引用迴圈,定期執行迴圈檢測演算法,查詢不可訪問的迴圈並刪除涉及的物件。gc 模組提供了執行垃圾收集、獲取除錯統計資訊和調整收集器引數的函式。

但是,其他實現(例如 JythonPyPy)可以依賴於不同的機制,例如完全成熟的垃圾收集器。 如果您的 Python 程式碼依賴於引用計數實現的行為,則這種差異可能會導致一些細微的移植問題。

在某些 Python 實現中,以下程式碼(在 CPython 中沒問題)可能會耗盡檔案描述符

for file in very_long_list_of_files:
    f = open(file)
    c = f.read(1)

實際上,使用 CPython 的引用計數和析構器方案,每次向 f 進行新賦值都會關閉上一個檔案。 但是,使用傳統的 GC,這些檔案物件只會在不同的且可能較長的時間間隔內被收集(和關閉)。

如果您想編寫可以與任何 Python 實現一起使用的程式碼,您應該顯式關閉檔案或使用 with 語句;無論記憶體管理方案如何,這都將起作用

for file in very_long_list_of_files:
    with open(file) as f:
        c = f.read(1)

為什麼 CPython 不使用更傳統的垃圾收集方案?

首先,這不是 C 標準功能,因此不可移植。(是的,我們知道 Boehm GC 庫。它具有適用於大多數常見平臺的彙編程式碼片段,而不是全部,雖然它在很大程度上是透明的,但它並非完全透明;需要補丁才能使 Python 與之一起工作。)

當 Python 嵌入到其他應用程式中時,傳統的 GC 也會成為問題。 雖然在獨立的 Python 中,用 GC 庫提供的版本替換標準 malloc()free() 很好,但嵌入 Python 的應用程式可能希望擁有自己的 malloc()free() 的替代品,並且可能不想要 Python 的替代品。 目前,CPython 可以與任何正確實現 malloc()free() 的東西一起工作。

為什麼 CPython 退出時並非所有記憶體都被釋放?

當 Python 退出時,從 Python 模組的全域性名稱空間引用的物件並非總是被釋放。 如果存在迴圈引用,則可能會發生這種情況。 還有某些由 C 庫分配的記憶體無法釋放(例如,像 Purify 這樣的工具會抱怨這些記憶體)。 但是,Python 在退出時會積極清理記憶體,並嘗試銷燬每個物件。

如果您想強制 Python 在釋放時刪除某些內容,請使用 atexit 模組來執行一個強制這些刪除的函式。

為什麼有單獨的元組和列表資料型別?

列表和元組在許多方面都很相似,但通常在根本不同的方式中使用。元組可以被認為類似於 Pascal 的 records 或 C 的 structs;它們是相關資料的少量集合,這些資料可能具有不同的型別,並且作為一個組進行操作。例如,笛卡爾座標通常表示為包含兩個或三個數字的元組。

另一方面,列表更像其他語言中的陣列。它們傾向於儲存數量可變的物件,所有這些物件都具有相同的型別,並且逐個進行操作。例如,os.listdir('.') 返回一個字串列表,表示當前目錄中的檔案。如果向目錄中新增一兩個檔案,通常不會破壞對該輸出進行操作的函式。

元組是不可變的,這意味著一旦建立了元組,就無法將其任何元素替換為新值。列表是可變的,這意味著您始終可以更改列表的元素。只有不可變的元素可以用作字典的鍵,因此只有元組而不是列表可以用作鍵。

CPython 中列表是如何實現的?

CPython 的列表實際上是可變長度的陣列,而不是 Lisp 風格的連結串列。該實現使用指向其他物件的連續引用陣列,並在列表頭結構中保留指向此陣列的指標和陣列的長度。

這使得索引列表 a[i] 的操作成本與列表的大小或索引的值無關。

當追加或插入專案時,引用的陣列會調整大小。應用了一些巧妙的方法來提高重複追加專案的效能;當必須增長陣列時,會分配一些額外的空間,以便接下來的幾次不需要實際調整大小。

CPython 中字典是如何實現的?

CPython 的字典實現為可調整大小的雜湊表。與 B 樹相比,這在大多數情況下為查詢(迄今為止最常見的操作)提供了更好的效能,並且實現更簡單。

字典的工作原理是使用 hash() 內建函式計算儲存在字典中的每個鍵的雜湊碼。雜湊碼因鍵和每個程序的種子而異;例如,'Python' 可以雜湊到 -539294296,而 'python'(一個僅相差一位的字串)可以雜湊到 1142331976。然後,使用雜湊碼計算內部陣列中儲存值的位置。假設您儲存的鍵都具有不同的雜湊值,這意味著字典檢索鍵需要恆定的時間——O(1),用大 O 表示法表示。

為什麼字典鍵必須是不可變的?

字典的雜湊表實現使用從鍵值計算出的雜湊值來查詢鍵。如果鍵是可變物件,則其值可能會更改,因此其雜湊值也可能會更改。但是,由於更改鍵物件的人無法知道它被用作字典鍵,因此它無法在字典中移動條目。然後,當您嘗試在字典中查詢同一物件時,由於其雜湊值不同,因此將找不到它。如果您嘗試查詢舊值,也找不到它,因為在該雜湊桶中找到的物件的值將不同。

如果希望使用列表索引字典,只需先將列表轉換為元組即可;函式 tuple(L) 建立一個與列表 L 具有相同條目的元組。元組是不可變的,因此可以用作字典鍵。

一些已被提出的不可接受的解決方案

  • 透過地址(物件 ID)雜湊列表。這不起作用,因為如果構造一個具有相同值的新列表,則將找不到它;例如:

    mydict = {[1, 2]: '12'}
    print(mydict[[1, 2]])
    

    會引發 KeyError 異常,因為第二行中使用的 [1, 2] 的 id 與第一行中的 id 不同。換句話說,應該使用 == 而不是 is 來比較字典鍵。

  • 在將列表用作鍵時進行復制。這不起作用,因為列表作為可變物件,可能包含對其自身的引用,然後複製程式碼將陷入無限迴圈。

  • 允許將列表作為鍵,但告訴使用者不要修改它們。當您忘記或意外修改列表時,這將允許程式中出現一類難以跟蹤的錯誤。它還使字典的一個重要不變數失效:d.keys() 中的每個值都可用作字典的鍵。

  • 一旦列表用作字典鍵,就將其標記為只讀。問題在於,不僅頂層物件可能會更改其值;您可以使用包含列表的元組作為鍵。將任何內容作為鍵輸入字典將需要將所有可從該處訪問的物件標記為只讀——並且同樣,自引用物件可能導致無限迴圈。

如果需要,有一種技巧可以解決此問題,但請自行承擔風險:您可以將可變結構包裝在具有 __eq__()__hash__() 方法的類例項中。然後,您必須確保字典(或其他基於雜湊的結構)中所有此類包裝器物件的雜湊值在該物件位於字典(或其他結構)中時保持固定。

class ListWrapper:
    def __init__(self, the_list):
        self.the_list = the_list

    def __eq__(self, other):
        return self.the_list == other.the_list

    def __hash__(self):
        l = self.the_list
        result = 98767 - len(l)*555
        for i, el in enumerate(l):
            try:
                result = result + (hash(el) % 9999999) * 1001 + i
            except Exception:
                result = (result % 7777777) + i * 333
        return result

請注意,列表的某些成員可能不可雜湊,並且還可能發生算術溢位,這使得雜湊計算變得複雜。

此外,必須始終滿足以下條件:如果 o1 == o2(即 o1.__eq__(o2) is True),則 hash(o1) == hash(o2)(即,o1.__hash__() == o2.__hash__()),無論物件是否在字典中。如果您未能滿足這些限制,字典和其他基於雜湊的結構將會出現錯誤行為。

ListWrapper 的情況下,無論何時包裝器物件在字典中,都不能更改包裝的列表以避免異常。除非您準備好認真考慮要求以及未正確滿足要求造成的後果,否則不要這樣做。請注意您的安全。

為什麼 list.sort() 不返回排序後的列表?

在效能至關重要的情況下,僅為了對列表進行排序而複製列表會很浪費。因此,list.sort() 對列表進行原地排序。為了提醒您注意這一事實,它不返回排序後的列表。這樣,當您需要排序後的副本,但又需要保留未排序的版本時,您就不會被誤導而意外覆蓋列表。

如果希望返回一個新列表,請改用內建的 sorted() 函式。此函式從提供的可迭代物件建立一個新列表,對其進行排序並返回。例如,以下是如何按排序順序迭代字典的鍵

for key in sorted(mydict):
    ...  # do whatever with mydict[key]...

如何在 Python 中指定和強制執行介面規範?

由 C++ 和 Java 等語言提供的模組的介面規範描述了模組的方法和函式的原型。許多人認為,在大型程式的構建中,對介面規範進行編譯時強制執行是有幫助的。

Python 2.6 添加了一個 abc 模組,可讓您定義抽象基類 (ABC)。然後,您可以使用 isinstance()issubclass() 來檢查例項或類是否實現了特定的 ABC。collections.abc 模組定義了一組有用的 ABC,例如 IterableContainerMutableMapping

對於 Python 而言,透過對元件進行適當的測試規範,可以獲得介面規範的許多優點。

一個模組的良好測試套件既可以提供迴歸測試,又可以作為模組介面規範和一組示例。許多 Python 模組可以作為指令碼執行以提供簡單的“自檢”。即使是使用複雜外部介面的模組,通常也可以透過使用外部介面的簡單“樁”模擬來進行隔離測試。可以使用 doctestunittest 模組或第三方測試框架來構建詳盡的測試套件,以測試模組中的每一行程式碼。

適當的測試規範可以幫助使用 Python 構建大型複雜應用程式,就像使用介面規範一樣。事實上,它可能更好,因為介面規範無法測試程式的某些屬性。例如,list.append() 方法應該將新元素新增到某個內部列表的末尾;介面規範無法測試您的 list.append() 實現是否真的能正確地執行此操作,但在測試套件中檢查此屬性是很簡單的。

編寫測試套件非常有幫助,您可能希望設計程式碼以使其易於測試。一種越來越流行的技術,即測試驅動開發,要求首先編寫測試套件的部分內容,然後再編寫任何實際程式碼。當然,Python 允許您馬虎並不編寫測試用例。

為什麼沒有 goto 語句?

在 20 世紀 70 年代,人們意識到不受限制的 goto 可能導致難以理解和修改的混亂“義大利麵條”程式碼。在高階語言中,只要有分支(在 Python 中,使用 if 語句和 or, and, 和 if/else 表示式)和迴圈(使用 whilefor 語句,可能包含 continuebreak)的方式,它也是不需要的。

還可以使用異常來提供一個“結構化的 goto”,即使跨函式呼叫也能工作。許多人認為,異常可以方便地模擬 C、Fortran 和其他語言的 gogoto 結構的所有合理用法。例如

class label(Exception): pass  # declare a label

try:
    ...
    if condition: raise label()  # goto label
    ...
except label:  # where to goto
    pass
...

這不允許您跳到迴圈的中間,但這通常被認為是濫用 goto。謹慎使用。

為什麼原始字串 (r 字串) 不能以反斜槓結尾?

更準確地說,它們不能以奇數個反斜槓結尾:末尾不成對的反斜槓會轉義閉合引號字元,從而留下一個未終止的字串。

原始字串旨在簡化為需要進行自己的反斜槓轉義處理的處理器(主要是正則表示式引擎)建立輸入。此類處理器認為不匹配的尾隨反斜槓也是一個錯誤,因此原始字串禁止這樣做。作為回報,它們允許您透過使用反斜槓跳脫字元串引號字元來傳遞該字元。當 r 字串用於其預期目的時,這些規則效果很好。

如果您試圖構建 Windows 路徑名,請注意所有 Windows 系統呼叫也接受正斜槓

f = open("/mydir/file.txt")  # works fine!

如果您試圖為 DOS 命令構建路徑名,請嘗試例如以下之一

dir = r"\this\is\my\dos\dir" "\\"
dir = r"\this\is\my\dos\dir\ "[:-1]
dir = "\\this\\is\\my\\dos\\dir\\"

為什麼 Python 沒有用於屬性賦值的“with”語句?

Python 有一個 with 語句,它包裝一個塊的執行,在進入和退出該塊時呼叫程式碼。某些語言具有如下結構

with obj:
    a = 1               # equivalent to obj.a = 1
    total = total + 1   # obj.total = obj.total + 1

在 Python 中,這樣的結構會有歧義。

其他語言(例如 Object Pascal、Delphi 和 C++)使用靜態型別,因此可以明確地知道正在分配給哪個成員。這是靜態型別的主要觀點——編譯器在編譯時總是知道每個變數的作用域。

Python 使用動態型別。不可能預先知道在執行時將引用哪個屬性。成員屬性可以在執行時動態地新增到物件或從物件中刪除。這使得我們不可能從簡單的閱讀中知道正在引用哪個屬性:是本地屬性、全域性屬性還是成員屬性?

例如,以下面的不完整程式碼片段為例

def foo(a):
    with a:
        print(x)

該片段假設 a 必須具有一個名為 x 的成員屬性。但是,Python 中沒有任何內容告訴直譯器這一點。如果 a 是一個整數,那麼應該發生什麼?如果有一個名為 x 的全域性變數,它會在 with 塊內使用嗎?如您所見,Python 的動態特性使得此類選擇更加困難。

with 和類似語言特性(減少程式碼量)的主要好處,可以透過在 Python 中進行賦值輕鬆實現。而不是

function(args).mydict[index][index].a = 21
function(args).mydict[index][index].b = 42
function(args).mydict[index][index].c = 63

寫成這樣

ref = function(args).mydict[index][index]
ref.a = 21
ref.b = 42
ref.c = 63

這還具有提高執行速度的副作用,因為名稱繫結是在 Python 中在執行時解析的,而第二個版本只需要執行一次解析。

進一步減少程式碼量的類似提案(例如使用“前導點”)已被拒絕,而傾向於顯式性(請參閱 https://mail.python.org/pipermail/python-ideas/2016-May/040070.html)。

為什麼生成器不支援 with 語句?

出於技術原因,直接用作上下文管理器的生成器將無法正常工作。當一個生成器像最常見的用法那樣用作迭代器並執行完成時,不需要關閉。當需要關閉時,請在 with 語句中將其包裝為 contextlib.closing(generator)

為什麼 if/while/def/class 語句需要冒號?

冒號的主要作用是提高可讀性(ABC 語言實驗的結果之一)。考慮一下

if a == b
    print(a)

if a == b:
    print(a)

請注意,第二個更容易閱讀。還要注意,冒號如何在此 FAQ 答案中突出顯示示例;這是英語中的標準用法。

另一個次要原因是冒號使具有語法突出顯示的編輯器更容易;它們可以查詢冒號來決定何時需要增加縮排,而無需對程式文字進行更詳細的解析。

為什麼 Python 允許在列表和元組末尾使用逗號?

Python 允許您在列表、元組和字典的末尾新增尾隨逗號

[1, 2, 3,]
('a', 'b', 'c',)
d = {
    "A": [1, 5],
    "B": [6, 7],  # last trailing comma is optional but good style
}

允許這樣做有幾個原因。

當您有一個跨多行的列表、元組或字典的字面值時,新增更多元素會更容易,因為您不必記住在前一行新增逗號。這些行也可以重新排序,而不會產生語法錯誤。

意外省略逗號可能會導致難以診斷的錯誤。例如

x = [
  "fee",
  "fie"
  "foo",
  "fum"
]

此列表看起來有四個元素,但它實際上包含三個:“fee”、“fiefoo” 和 “fum”。始終新增逗號可避免這種錯誤來源。

允許尾隨逗號也可能使程式程式碼的生成更容易。