設計和歷史常見(jiàn)問(wèn)題?

目錄

為什么Python使用縮進(jìn)來(lái)分組語(yǔ)句??

Guido van Rossum 認為使用縮進(jìn)進(jìn)行分組非常優(yōu)雅,并且大大提高了普通Python程序的清晰度。大多數人在一段時(shí)間后就學(xué)會(huì )并喜歡上這個(gè)功能。

由于沒(méi)有開(kāi)始/結束括號,因此解析器感知的分組與人類(lèi)讀者之間不會(huì )存在分歧。偶爾C程序員會(huì )遇到像這樣的代碼片段:

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

如果條件為真,則只執行 x++ 語(yǔ)句,但縮進(jìn)會(huì )使你認為情況并非如此。 即使是經(jīng)驗豐富的 C 程序員有時(shí)也會(huì )長(cháng)久地盯著(zhù)它發(fā)呆,不明白為什么在 x > y``時(shí) ``y 也會(huì )減少。

因為沒(méi)有開(kāi)始/結束花括號,所以 Python 更不容易發(fā)生編碼風(fēng)格沖突。 在 C 中有許多不同的放置花括號的方式。 在習慣了閱讀和編寫(xiě)某種特定風(fēng)格的代碼之后,當閱讀(或被要求編寫(xiě))另一種風(fēng)格的代碼時(shí)通常都會(huì )令人感覺(jué)有點(diǎn)不舒服)。

許多編碼風(fēng)格將開(kāi)始/結束括號單獨放在一行上。這使得程序相當長(cháng),浪費了寶貴的屏幕空間,使得更難以對程序進(jìn)行全面的了解。理想情況下,函數應該適合一個(gè)屏幕(例如,20--30行)。 20行Python可以完成比20行C更多的工作。這不僅僅是由于缺少開(kāi)始/結束括號 -- 缺少聲明和高級數據類(lèi)型也是其中的原因 -- 但縮進(jìn)基于語(yǔ)法肯定有幫助。

為什么簡(jiǎn)單的算術(shù)運算得到奇怪的結果??

請看下一個(gè)問(wèn)題。

為什么浮點(diǎn)計算不準確??

用戶(hù)經(jīng)常對這樣的結果感到驚訝:

>>>
>>> 1.2 - 1.0
0.19999999999999996

并且認為這是 Python中的一個(gè) bug。其實(shí)不是這樣。這與 Python 關(guān)系不大,而與底層平臺如何處理浮點(diǎn)數字關(guān)系更大。

CPython 中的 float 類(lèi)型使用C語(yǔ)言的 double 類(lèi)型進(jìn)行存儲。 float 對象的值是以固定的精度(通常為 53 位)存儲的二進(jìn)制浮點(diǎn)數,由于 Python 使用 C 操作,而后者依賴(lài)于處理器中的硬件實(shí)現來(lái)執行浮點(diǎn)運算。 這意味著(zhù)就浮點(diǎn)運算而言,Python 的行為類(lèi)似于許多流行的語(yǔ)言,包括 C 和 Java。

許多可以輕松地用十進(jìn)制表示的數字不能用二進(jìn)制浮點(diǎn)表示。例如,在輸入以下語(yǔ)句后:

>>>
>>> x = 1.2

x 存儲的值是與十進(jìn)制的值 1.2 (非常接近) 的近似值,但不完全等于它。 在典型的機器上,實(shí)際存儲的值是:

1.0011001100110011001100110011001100110011001100110011 (binary)

它對應于十進(jìn)制數值:

1.1999999999999999555910790149937383830547332763671875 (decimal)

典型的 53 位精度為 Python 浮點(diǎn)數提供了 15-16 位小數的精度。

要獲得更完整的解釋?zhuān)垍㈤?Python 教程中的 浮點(diǎn)算術(shù) 一章。

為什么Python字符串是不可變的??

有幾個(gè)優(yōu)點(diǎn)。

一個(gè)是性能:知道字符串是不可變的,意味著(zhù)我們可以在創(chuàng )建時(shí)為它分配空間,并且存儲需求是固定不變的。這也是元組和列表之間區別的原因之一。

另一個(gè)優(yōu)點(diǎn)是,Python 中的字符串被視為與數字一樣“基本”。 任何動(dòng)作都不會(huì )將值 8 更改為其他值,在 Python 中,任何動(dòng)作都不會(huì )將字符串 "8" 更改為其他值。

為什么必須在方法定義和調用中顯式使用“self”??

這個(gè)想法借鑒了 Modula-3 語(yǔ)言。 出于多種原因它被證明是非常有用的。

首先,更明顯的顯示出,使用的是方法或實(shí)例屬性而不是局部變量。 閱讀 self.xself.meth() 可以清楚地表明,即使您不知道類(lèi)的定義,也會(huì )使用實(shí)例變量或方法。在 C++ 中,可以通過(guò)缺少局部變量聲明來(lái)判斷(假設全局變量很少見(jiàn)或容易識別) —— 但是在 Python 中沒(méi)有局部變量聲明,所以必須查找類(lèi)定義才能確定。 一些 C++ 和 Java 編碼標準要求實(shí)例屬性具有 m_ 前綴,因此這種顯式性在這些語(yǔ)言中仍然有用。

其次,這意味著(zhù)如果要顯式引用或從特定類(lèi)調用該方法,不需要特殊語(yǔ)法。 在 C++ 中,如果你想使用在派生類(lèi)中重寫(xiě)基類(lèi)中的方法,你必須使用 :: 運算符 -- 在 Python 中你可以編寫(xiě) baseclass.methodname(self, <argument list>)。 這對于 __init__() 方法非常有用,特別是在派生類(lèi)方法想要擴展同名的基類(lèi)方法,而必須以某種方式調用基類(lèi)方法時(shí)。

最后,它解決了變量賦值的語(yǔ)法問(wèn)題:為了 Python 中的局部變量(根據定義?。┰诤瘮刁w中賦值的那些變量(并且沒(méi)有明確聲明為全局)賦值,就必須以某種方式告訴解釋器一個(gè)賦值是為了分配一個(gè)實(shí)例變量而不是一個(gè)局部變量,它最好是通過(guò)語(yǔ)法實(shí)現的(出于效率原因)。 C++ 通過(guò)聲明來(lái)做到這一點(diǎn),但是 Python 沒(méi)有聲明,僅僅為了這個(gè)目的而引入它們會(huì )很可惜。 使用顯式的 self.var 很好地解決了這個(gè)問(wèn)題。 類(lèi)似地,對于使用實(shí)例變量,必須編寫(xiě) self.var 意味著(zhù)對方法內部的非限定名稱(chēng)的引用不必搜索實(shí)例的目錄。 換句話(huà)說(shuō),局部變量和實(shí)例變量存在于兩個(gè)不同的命名空間中,您需要告訴 Python 使用哪個(gè)命名空間。

為什么不能在表達式中賦值??

自 Python 3.8 開(kāi)始,你能做到的!

賦值表達式使用海象運算符 := 在表達式中為變量賦值:

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

請參閱 PEP 572 了解詳情。

為什么Python對某些功能(例如list.index())使用方法來(lái)實(shí)現,而其他功能(例如len(List))使用函數實(shí)現??

正如Guido所說(shuō):

(a) 對于某些操作,前綴表示法比后綴更容易閱讀 -- 前綴(和中綴?。┻\算在數學(xué)中有著(zhù)悠久的傳統,就像在視覺(jué)上幫助數學(xué)家思考問(wèn)題的記法。比較一下我們將 x*(a+b) 這樣的公式改寫(xiě)為 x*a+x*b 的容易程度,以及使用原始OO符號做相同事情的笨拙程度。

(b) 當讀到寫(xiě)有len(X)的代碼時(shí),就知道它要求的是某件東西的長(cháng)度。這告訴我們兩件事:結果是一個(gè)整數,參數是某種容器。相反,當閱讀x.len()時(shí),必須已經(jīng)知道x是某種實(shí)現接口的容器,或者是從具有標準len()的類(lèi)繼承的容器。當沒(méi)有實(shí)現映射的類(lèi)有g(shù)et()或key()方法,或者不是文件的類(lèi)有write()方法時(shí),我們偶爾會(huì )感到困惑。

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

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

從 Python 1.6 開(kāi)始,字符串變得更像其他標準類(lèi)型,當添加方法時(shí),這些方法提供的功能與始終使用 String 模塊的函數時(shí)提供的功能相同。這些新方法中的大多數已被廣泛接受,但似乎讓一些程序員感到不舒服的一種方法是:

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

結果如下:

"1, 2, 4, 8, 16"

反對這種用法有兩個(gè)常見(jiàn)的論點(diǎn)。

第一條是這樣的:“使用字符串文本(String Constant)的方法看起來(lái)真的很難看”,答案是也許吧,但是字符串文本只是一個(gè)固定值。如果在綁定到字符串的名稱(chēng)上允許使用這些方法,則沒(méi)有邏輯上的理由使其在文字上不可用。

第二個(gè)異議通常是這樣的:“我實(shí)際上是在告訴序列使用字符串常量將其成員連接在一起”。遺憾的是并非如此。出于某種原因,把 split() 作為一個(gè)字符串方法似乎要容易得多,因為在這種情況下,很容易看到:

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

是對字符串文本的指令,用于返回由給定分隔符分隔的子字符串(或在默認情況下,返回任意空格)。

join() 是字符串方法,因為在使用該方法時(shí),您告訴分隔符字符串去迭代一個(gè)字符串序列,并在相鄰元素之間插入自身。此方法的參數可以是任何遵循序列規則的對象,包括您自己定義的任何新的類(lèi)。對于字節和字節數組對象也有類(lèi)似的方法。

異常有多快??

如果沒(méi)有引發(fā)異常,則try/except塊的效率極高。實(shí)際上捕獲異常是昂貴的。在2.0之前的Python版本中,通常使用這個(gè)習慣用法:

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

只有當你期望dict在任何時(shí)候都有key時(shí),這才有意義。如果不是這樣的話(huà),你就是應該這樣編碼:

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

對于這種特定的情況,您還可以使用 value = dict.setdefault(key, getvalue(key)),但前提是調用 getvalue() 足夠便宜,因為在所有情況下都會(huì )對其進(jìn)行評估。

為什么Python中沒(méi)有switch或case語(yǔ)句??

你可以足夠方便地使用一串 if... elif... elif... else 來(lái)做到這一點(diǎn)。 對于字面值或某一命名空間內的常量,你還可以 使用 match ... case 語(yǔ)句。

對于需要從大量可能性中進(jìn)行選擇的情況,可以創(chuàng )建一個(gè)字典,將case 值映射到要調用的函數。例如:

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

func = functions[value]
func()

對于對象調用方法,可以通過(guò)使用 getattr() 內置檢索具有特定名稱(chēng)的方法來(lái)進(jìn)一步簡(jiǎn)化:

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

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

建議對方法名使用前綴,例如本例中的 visit_ 。如果沒(méi)有這樣的前綴,如果值來(lái)自不受信任的源,攻擊者將能夠調用對象上的任何方法。

難道不能在解釋器中模擬線(xiàn)程,而非得依賴(lài)特定于操作系統的線(xiàn)程實(shí)現嗎??

答案1: 不幸的是,解釋器為每個(gè)Python堆棧幀推送至少一個(gè)C堆棧幀。此外,擴展可以隨時(shí)回調Python。因此,一個(gè)完整的線(xiàn)程實(shí)現需要對C的線(xiàn)程支持。

答案2: 幸運的是, Stackless Python 有一個(gè)完全重新設計的解釋器循環(huán),可以避免C堆棧。

為什么lambda表達式不能包含語(yǔ)句??

Python的 lambda表達式不能包含語(yǔ)句,因為Python的語(yǔ)法框架不能處理嵌套在表達式內部的語(yǔ)句。然而,在Python中,這并不是一個(gè)嚴重的問(wèn)題。與其他語(yǔ)言中添加功能的lambda表單不同,Python的 lambdas只是一種速記符號,如果您懶得定義函數的話(huà)。

函數已經(jīng)是Python中的第一類(lèi)對象,可以在本地范圍內聲明。 因此,使用lambda而不是本地定義的函數的唯一優(yōu)點(diǎn)是你不需要為函數創(chuàng )建一個(gè)名稱(chēng) -- 這只是一個(gè)分配了函數對象(與lambda表達式生成的對象類(lèi)型完全相同)的局部變量!

可以將Python編譯為機器代碼,C或其他語(yǔ)言嗎??

Cython 將帶有可選注釋的Python修改版本編譯到C擴展中。 Nuitka 是一個(gè)將Python編譯成 C++ 代碼的新興編譯器,旨在支持完整的Python語(yǔ)言。要編譯成Java,可以考慮 VOC 。

Python如何管理內存??

Python 內存管理的細節取決于實(shí)現。 Python 的標準實(shí)現 CPython 使用引用計數來(lái)檢測不可訪(fǎng)問(wèn)的對象,并使用另一種機制來(lái)收集引用循環(huán),定期執行循環(huán)檢測算法來(lái)查找不可訪(fǎng)問(wèn)的循環(huán)并刪除所涉及的對象。 gc 模塊提供了執行垃圾回收、獲取調試統計信息和優(yōu)化收集器參數的函數。

但是,其他實(shí)現(如 JythonPyPy ),)可以依賴(lài)不同的機制,如完全的垃圾回收器 。如果你的Python代碼依賴(lài)于引用計數實(shí)現的行為,則這種差異可能會(huì )導致一些微妙的移植問(wèn)題。

在一些Python實(shí)現中,以下代碼(在CPython中工作的很好)可能會(huì )耗盡文件描述符:

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

實(shí)際上,使用CPython的引用計數和析構函數方案, 每個(gè)新賦值的 f 都會(huì )關(guān)閉前一個(gè)文件。然而,對于傳統的GC,這些文件對象只能以不同的時(shí)間間隔(可能很長(cháng)的時(shí)間間隔)被收集(和關(guān)閉)。

如果要編寫(xiě)可用于任何python實(shí)現的代碼,則應顯式關(guān)閉該文件或使用 with 語(yǔ)句;無(wú)論內存管理方案如何,這都有效:

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

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

首先,這不是C標準特性,因此不能移植。(是的,我們知道Boehm GC庫。它包含了 大多數 常見(jiàn)平臺(但不是所有平臺)的匯編代碼,盡管它基本上是透明的,但也不是完全透明的; 要讓Python使用它,需要使用補丁。)

當Python嵌入到其他應用程序中時(shí),傳統的GC也成為一個(gè)問(wèn)題。在獨立的Python中,可以用GC庫提供的版本替換標準的malloc()和free(),嵌入Python的應用程序可能希望用 它自己 替代malloc()和free(),而可能不需要Python的?,F在,CPython可以正確地實(shí)現malloc()和free()。

CPython退出時(shí)為什么不釋放所有內存??

當Python退出時(shí),從全局命名空間或Python模塊引用的對象并不總是被釋放。 如果存在循環(huán)引用,則可能發(fā)生這種情況 C庫分配的某些內存也是不可能釋放的(例如像Purify這樣的工具會(huì )抱怨這些內容)。 但是,Python在退出時(shí)清理內存并嘗試銷(xiāo)毀每個(gè)對象。

如果要強制 Python 在釋放時(shí)刪除某些內容,請使用 atexit 模塊運行一個(gè)函數,強制刪除這些內容。

為什么有單獨的元組和列表數據類(lèi)型??

雖然列表和元組在許多方面是相似的,但它們的使用方式通常是完全不同的??梢哉J為元組類(lèi)似于Pascal記錄或C結構;它們是相關(guān)數據的小集合,可以是不同類(lèi)型的數據,可以作為一個(gè)組進(jìn)行操作。例如,笛卡爾坐標適當地表示為兩個(gè)或三個(gè)數字的元組。

另一方面,列表更像其他語(yǔ)言中的數組。它們傾向于持有不同數量的對象,所有對象都具有相同的類(lèi)型,并且逐個(gè)操作。例如, os.listdir('.') 返回表示當前目錄中的文件的字符串列表。如果向目錄中添加了一兩個(gè)文件,對此輸出進(jìn)行操作的函數通常不會(huì )中斷。

元組是不可變的,這意味著(zhù)一旦創(chuàng )建了元組,就不能用新值替換它的任何元素。列表是可變的,這意味著(zhù)您始終可以更改列表的元素。只有不變元素可以用作字典的key,因此只能將元組和非列表用作key。

列表是如何在CPython中實(shí)現的??

CPython的列表實(shí)際上是可變長(cháng)度的數組,而不是lisp風(fēng)格的鏈表。該實(shí)現使用對其他對象的引用的連續數組,并在列表頭結構中保留指向該數組和數組長(cháng)度的指針。

這使得索引列表 a[i] 的操作成本與列表的大小或索引的值無(wú)關(guān)。

當添加或插入項時(shí),將調整引用數組的大小。并采用了一些巧妙的方法來(lái)提高重復添加項的性能; 當數組必須增長(cháng)時(shí),會(huì )分配一些額外的空間,以便在接下來(lái)的幾次中不需要實(shí)際調整大小。

字典是如何在CPython中實(shí)現的??

CPython的字典實(shí)現為可調整大小的哈希表。與B-樹(shù)相比,這在大多數情況下為查找(目前最常見(jiàn)的操作)提供了更好的性能,并且實(shí)現更簡(jiǎn)單。

字典的工作方式是使用 hash() 內置函數計算字典中存儲的每個(gè)鍵的hash代碼。hash代碼根據鍵和每個(gè)進(jìn)程的種子而變化很大;例如,"Python" 的hash值為-539294296,而"python"(一個(gè)按位不同的字符串)的hash值為1142331976。然后,hash代碼用于計算內部數組中將存儲該值的位置。假設您存儲的鍵都具有不同的hash值,這意味著(zhù)字典需要恒定的時(shí)間 -- O(1),用Big-O表示法 -- 來(lái)檢索一個(gè)鍵。

為什么字典key必須是不可變的??

字典的哈希表實(shí)現使用從鍵值計算的哈希值來(lái)查找鍵。如果鍵是可變對象,則其值可能會(huì )發(fā)生變化,因此其哈希值也會(huì )發(fā)生變化。但是,由于無(wú)論誰(shuí)更改鍵對象都無(wú)法判斷它是否被用作字典鍵值,因此無(wú)法在字典中修改條目。然后,當你嘗試在字典中查找相同的對象時(shí),將無(wú)法找到它,因為其哈希值不同。如果你嘗試查找舊值,也不會(huì )找到它,因為在該哈希表中找到的對象的值會(huì )有所不同。

如果你想要一個(gè)用列表索引的字典,只需先將列表轉換為元組;用函數 tuple(L) 創(chuàng )建一個(gè)元組,其條目與列表 L 相同。 元組是不可變的,因此可以用作字典鍵。

已經(jīng)提出的一些不可接受的解決方案:

  • 哈希按其地址(對象ID)列出。這不起作用,因為如果你構造一個(gè)具有相同值的新列表,它將無(wú)法找到;例如:

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

    會(huì )引發(fā)一個(gè) KeyError 異常,因為第二行中使用的 [1, 2] 的 id 與第一行中的 id 不同。換句話(huà)說(shuō),應該使用 == 來(lái)比較字典鍵,而不是使用 is 。

  • 使用列表作為鍵時(shí)進(jìn)行復制。這沒(méi)有用的,因為作為可變對象的列表可以包含對自身的引用,然后復制代碼將進(jìn)入無(wú)限循環(huán)。

  • 允許列表作為鍵,但告訴用戶(hù)不要修改它們。當你意外忘記或修改列表時(shí),這將產(chǎn)生程序中的一類(lèi)難以跟蹤的錯誤。它還使一個(gè)重要的字典不變量無(wú)效: d.keys() 中的每個(gè)值都可用作字典的鍵。

  • 將列表用作字典鍵后,應標記為其只讀。問(wèn)題是,它不僅僅是可以改變其值的頂級對象;你可以使用包含列表作為鍵的元組。將任何內容作為鍵關(guān)聯(lián)到字典中都需要將從那里可到達的所有對象標記為只讀 —— 并且自引用對象可能會(huì )導致無(wú)限循環(huán)。

如果需要,可以使用以下方法來(lái)解決這個(gè)問(wèn)題,但使用它需要你自擔風(fēng)險:你可以將一個(gè)可變結構包裝在一個(gè)類(lèi)實(shí)例中,該實(shí)例同時(shí)具有 __eq__()__hash__() 方法。然后,你必須確保駐留在字典(或其他基于 hash 的結構)中的所有此類(lèi)包裝器對象的哈希值在對象位于字典(或其他結構)中時(shí)保持固定。:

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

注意,哈希計算由于列表的某些成員可能不可用以及算術(shù)溢出的可能性而變得復雜。

此外,必須始終如此,如果 o1 == o2 (即 o1.__eq__(o2) is True )則 hash(o1) == hash(o2)``(即 ``o1.__hash__() == o2.__hash__() ),無(wú)論對象是否在字典中。 如果你不能滿(mǎn)足這些限制,字典和其他基于 hash 的結構將會(huì )出錯。

對于 ListWrapper ,只要包裝器對象在字典中,包裝列表就不能更改以避免異常。除非你準備好認真考慮需求以及不正確地滿(mǎn)足這些需求的后果,否則不要這樣做。請留意。

為什么 list.sort() 沒(méi)有返回排序列表??

在性能很重要的情況下,僅僅為了排序而復制一份列表將是一種浪費。因此, list.sort() 對列表進(jìn)行了適當的排序。為了提醒您這一事實(shí),它不會(huì )返回已排序的列表。這樣,當您需要排序的副本,但也需要保留未排序的版本時(shí),就不會(huì )意外地覆蓋列表。

如果要返回新列表,請使用內置 sorted() 函數。此函數從提供的可迭代列表中創(chuàng )建新列表,對其進(jìn)行排序并返回。例如,下面是如何迭代遍歷字典并按keys排序:

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

如何在Python中指定和實(shí)施接口規范??

由C++和Java等語(yǔ)言提供的模塊接口規范描述了模塊的方法和函數的原型。許多人認為接口規范的編譯時(shí)強制執行有助于構建大型程序。

Python 2.6添加了一個(gè) abc 模塊,允許定義抽象基類(lèi) (ABCs)。然后可以使用 isinstance()issubclass() 來(lái)檢查實(shí)例或類(lèi)是否實(shí)現了特定的ABC。 collections.abc 模塊定義了一組有用的ABCs 例如 Iterable , Container , 和 MutableMapping

對于 Python,接口規范的許多好處可以通過(guò)組件的適當測試規程來(lái)獲得。

一個(gè)好的模塊測試套件既可以提供回歸測試,也可以作為模塊接口規范和一組示例。許多Python模塊可以作為腳本運行,以提供簡(jiǎn)單的“自我測試”。即使是使用復雜外部接口的模塊,也常??梢允褂猛獠拷涌诘暮?jiǎn)單“樁代碼(stub)”模擬進(jìn)行隔離測試??梢允褂?doctestunittest 模塊或第三方測試框架來(lái)構造詳盡的測試套件,以運行模塊中的每一行代碼。

適當的測試規程可以幫助在Python中構建大型的、復雜的應用程序以及接口規范。事實(shí)上,它可能會(huì )更好,因為接口規范不能測試程序的某些屬性。例如, append() 方法將向一些內部列表的末尾添加新元素;接口規范不能測試您的 append() 實(shí)現是否能夠正確執行此操作,但是在測試套件中檢查這個(gè)屬性是很簡(jiǎn)單的。

編寫(xiě)測試套件非常有用,并且你可能希望將你的代碼設計為易于測試。 一種日益流行的技術(shù)是面向測試的開(kāi)發(fā),它要求在編寫(xiě)任何實(shí)際代碼之前首先編寫(xiě)測試套件的各個(gè)部分。 當然 Python 也允許你采用更粗率的方式,不必編寫(xiě)任何測試用例。

為什么沒(méi)有g(shù)oto??

在 1970 年代人們了解到不受限制的 goto 可能導致混亂得像“意大利面”那樣難以理解和修改的代碼。 在高級語(yǔ)言中,它也是不必要的,只需有實(shí)現分支 (在 Python 中是使用 if 語(yǔ)句以及 or, andif-else 表達式) 和循環(huán) (使用 whilefor 語(yǔ)句,并可能包含 continuebreak) 的手段就足夠了。

人們還可以使用異常捕獲來(lái)提供甚至能跨函數調用的“結構化 goto”。 許多人認為異??梢苑奖愕啬M C, Fortran 和其他語(yǔ)言中所有合理使用的“go”或“goto”構造。 例如:

class label(Exception): pass  # declare a label

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

但是不允許你跳到循環(huán)的中間,這通常被認為是濫用goto。謹慎使用。

為什么原始字符串(r-strings)不能以反斜杠結尾??

更準確地說(shuō),它們不能以奇數個(gè)反斜杠結束:結尾處的不成對反斜杠會(huì )轉義結束引號字符,留下未結束的字符串。

原始字符串的設計是為了方便想要執行自己的反斜杠轉義處理的處理器(主要是正則表達式引擎)創(chuàng )建輸入。此類(lèi)處理器將不匹配的尾隨反斜杠視為錯誤,因此原始字符串不允許這樣做。反過(guò)來(lái),允許通過(guò)使用引號字符轉義反斜杠轉義字符串。當r-string用于它們的預期目的時(shí),這些規則工作的很好。

如果您正在嘗試構建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沒(méi)有屬性賦值的“with”語(yǔ)句??

Python 具有 'with' 語(yǔ)句,它能將一個(gè)代碼塊的執行包裝起來(lái),在進(jìn)入和退出代碼塊時(shí)調用特定的代碼。 有些語(yǔ)言具有這樣的結構:

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

在Python中,這樣的結構是不明確的。

其他語(yǔ)言,如ObjectPascal、Delphi和C++ 使用靜態(tài)類(lèi)型,因此可以毫不含糊地知道分配給什么成員。這是靜態(tài)類(lèi)型的要點(diǎn) -- 編譯器 總是 在編譯時(shí)知道每個(gè)變量的作用域。

Python使用動(dòng)態(tài)類(lèi)型。事先不可能知道在運行時(shí)引用哪個(gè)屬性??梢詣?dòng)態(tài)地在對象中添加或刪除成員屬性。這使得無(wú)法通過(guò)簡(jiǎn)單的閱讀就知道引用的是什么屬性:局部屬性、全局屬性還是成員屬性?

例如,采用以下不完整的代碼段:

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

該代碼段假設 "a" 必須有一個(gè)名為 "x" 的成員屬性。然而,Python中并沒(méi)有告訴解釋器這一點(diǎn)。假設 "a" 是整數,會(huì )發(fā)生什么?如果有一個(gè)名為 "x" 的全局變量,它是否會(huì )在with塊中使用?如您所見(jiàn),Python的動(dòng)態(tài)特性使得這樣的選擇更加困難。

然而,Python 可以通過(guò)賦值輕松實(shí)現 "with" 和類(lèi)似語(yǔ)言特性(減少代碼量)的主要好處。代替:

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

寫(xiě)成這樣:

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

這也具有提高執行速度的副作用,因為Python在運行時(shí)解析名稱(chēng)綁定,而第二個(gè)版本只需要執行一次解析。

生成器為什么不支持 with 語(yǔ)句??

For technical reasons, a generator used directly as a context manager would not work correctly. When, as is most common, a generator is used as an iterator run to completion, no closing is needed. When it is, wrap it as "contextlib.closing(generator)" in the 'with' statement.

為什么 if/while/def/class語(yǔ)句需要冒號??

冒號主要用于增強可讀性(ABC語(yǔ)言實(shí)驗的結果之一)??紤]一下這個(gè):

if a == b
    print(a)

if a == b:
    print(a)

注意第二種方法稍微容易一些。請進(jìn)一步注意,在這個(gè)FAQ解答的示例中,冒號是如何設置的;這是英語(yǔ)中的標準用法。

另一個(gè)次要原因是冒號使帶有語(yǔ)法突出顯示的編輯器更容易工作;他們可以尋找冒號來(lái)決定何時(shí)需要增加縮進(jìn),而不必對程序文本進(jìn)行更精細的解析。

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

Python 允許您在列表,元組和字典的末尾添加一個(gè)尾隨逗號:

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

有幾個(gè)理由允許這樣做。

如果列表,元組或字典的字面值分布在多行中,則更容易添加更多元素,因為不必記住在上一行中添加逗號。這些行也可以重新排序,而不會(huì )產(chǎn)生語(yǔ)法錯誤。

不小心省略逗號會(huì )導致難以診斷的錯誤。例如:

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

這個(gè)列表看起來(lái)有四個(gè)元素,但實(shí)際上包含三個(gè) : "fee", "fiefoo" 和 "fum" ??偸羌由隙禾柨梢员苊膺@個(gè)錯誤的來(lái)源。

允許尾隨逗號也可以使編程代碼更容易生成。