設計和歷史常見問題¶
為什麼 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 操作,這些操作又依賴於處理器中的硬體實現來執行浮點操作。這意味著就浮點操作而言,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.x
或 self.meth()
絕對清楚地表明正在使用例項變數或方法,即使你沒有記住類定義。在 C++ 中,你可以透過缺少區域性變數宣告(假設全域性變數很少見或易於識別)來判斷——但在 Python 中,沒有區域性變數宣告,所以你必須檢視類定義才能確定。一些 C++ 和 Java 編碼標準要求例項屬性具有 m_
字首,因此這種顯式性在這些語言中也很有用。
其次,這意味著如果你想明確引用或呼叫特定類的方法,則不需要特殊的語法。在 C++ 中,如果你想使用基類中被派生類重寫的方法,你必須使用 ::
運算子——在 Python 中你可以寫 baseclass.methodname(self, <argument list>)
。這對於 __init__()
方法特別有用,通常在派生類方法想要擴充套件同名基類方法因此必須以某種方式呼叫基類方法的情況下。
最後,對於例項變數,它解決了賦值的語法問題:由於 Python 中的區域性變數(根據定義!)是在函式體中賦值(並且沒有顯式宣告為全域性變數)的變數,因此必須有某種方法告訴直譯器賦值 intended to assign to an instance variable instead of to a local variable,並且最好是語法的(出於效率原因)。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 的容易程度,以及使用原始面向物件表示法做同樣事情的笨拙程度。
(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
塊效率極高。實際捕獲異常是昂貴的。在 Python 2.0 之前的版本中,通常使用此習語
try:
value = mydict[key]
except KeyError:
mydict[key] = getvalue(key)
value = mydict[key]
這隻有在你期望 dict 幾乎總是包含該鍵時才有意義。如果不是這種情況,你的程式碼會像這樣
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
的序列。
對於需要從大量可能性中進行選擇的情況,你可以建立一個將案例值對映到要呼叫的函式的字典。例如
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_
。如果沒有此類字首,如果值來自不可信的源,攻擊者將能夠呼叫你物件上的任何方法。
模仿帶有穿透的 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
模組提供了執行垃圾回收、獲取除錯統計資訊和調整回收器引數的函式。
然而,其他實現(例如 Jython 或 PyPy)可以依賴不同的機制,例如成熟的垃圾回收器。如果你的 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 中替換標準的 malloc()
和 free()
與 GC 庫提供的版本是沒問題的,但嵌入 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 與第一行中的不同。換句話說,字典鍵應該使用==
進行比較,而不是使用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,例如 Iterable
、Container
和 MutableMapping
。
對於 Python,透過對元件進行適當的測試紀律可以獲得介面規範的許多優點。
模組的良好測試套件既可以提供迴歸測試,又可以作為模組介面規範和一組示例。許多 Python 模組可以作為指令碼執行以提供簡單的“自檢”。即使是使用複雜外部介面的模組,也通常可以使用外部介面的簡單“存根”模擬進行隔離測試。doctest
和 unittest
模組或第三方測試框架可以用於構建詳盡的測試套件,以執行模組中的每一行程式碼。
適當的測試規範有助於在 Python 中構建大型複雜應用程式,就像介面規範一樣。事實上,它可能更好,因為介面規範無法測試程式的某些屬性。例如,list.append()
方法預期會將新元素新增到某個內部列表的末尾;介面規範無法測試你的 list.append()
實現是否會正確執行此操作,但在測試套件中檢查此屬性是微不足道的。
編寫測試套件非常有幫助,你可能希望設計你的程式碼使其易於測試。一種越來越流行的技術,測試驅動開發,要求在編寫任何實際程式碼之前先編寫部分測試套件。當然,Python 允許你馬虎並且根本不編寫測試用例。
為什麼沒有 goto?¶
在 20 世紀 70 年代,人們意識到不受限制的 goto 可能導致難以理解和修改的“義大利麵條式”程式碼。在高階語言中,只要有分支(在 Python 中,使用 if
語句和 or
、and
和 if
/else
表示式)和迴圈(使用 while
和 for
語句,可能包含 continue
和 break
),它也是不必要的。
你還可以使用異常來提供一個“結構化 goto”,即使跨函式呼叫也有效。許多人認為異常可以方便地模擬 C、Fortran 和其他語言中 go
或 goto
構造的所有合理用途。例如
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 語句?¶
出於技術原因,直接用作上下文管理器的生成器無法正常工作。當生成器最常被用作迭代器執行到完成時,不需要關閉。當需要關閉時,將其包裝為 contextlib.closing(generator)
在 with
語句中。
為什麼 if/while/def/class 語句需要冒號?¶
冒號主要用於增強可讀性(實驗性 ABC 語言的結果之一)。考慮這個
if a == b
print(a)
對比
if a == b:
print(a)
請注意,第二個更容易閱讀。進一步注意,冒號如何分隔此常見問題解答中的示例;這是英語中的標準用法。
另一個次要原因是,冒號使帶有語法高亮的編輯器更容易;它們可以查詢冒號來決定何時需要增加縮排,而不必對程式文字進行更復雜的解析。
為什麼 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”。始終新增逗號可以避免這種錯誤源。
允許尾隨逗號也可能使程式化程式碼生成更容易。