|
01. 為什么使用縮進(jìn)來分組語句? Guido van Rossum 認(rèn)為使用縮進(jìn)進(jìn)行分組非常優(yōu)雅,并且大大提高了普通 Python 程序的清晰度。大多數(shù)人在一段時(shí)間后就學(xué)會(huì)并喜歡上這個(gè)功能。 由于沒有開始/結(jié)束括號,因此解析器感知的分組與人類讀者之間不會(huì)存在分歧。偶爾 C 程序員會(huì)遇到像這樣的代碼片段: if (x <= y) 如果條件為真,則只執(zhí)行 x++ 語句,但縮進(jìn)會(huì)使你認(rèn)為情況并非如此。即使是經(jīng)驗(yàn)豐富的 C 程序員有時(shí)會(huì)長時(shí)間盯著它,想知道為什么即使 x > y , y 也在減少。 因?yàn)闆]有開始/結(jié)束括號,所以 Python 不太容易發(fā)生編碼式?jīng)_突。在 C 中,括號可以放到許多不同的位置。如果您習(xí)慣于閱讀和編寫使用一種風(fēng)格的代碼,那么在閱讀(或被要求編寫)另一種風(fēng)格時(shí),您至少會(huì)感到有些不安。 許多編碼風(fēng)格將開始/結(jié)束括號單獨(dú)放在一行上。這使得程序相當(dāng)長,浪費(fèi)了寶貴的屏幕空間,使得更難以對程序進(jìn)行全面的了解。理想情況下,函數(shù)應(yīng)該適合一個(gè)屏幕(例如,20--30 行)。20 行 Python 可以完成比 20 行 C 更多的工作。這不僅僅是由于缺少開始/結(jié)束括號 -- 缺少聲明和高級數(shù)據(jù)類型也是其中的原因 -- 但縮進(jìn)基于語法肯定有幫助。 02. 為什么簡單的算術(shù)運(yùn)算得到奇怪的結(jié)果? 請看下一個(gè)問題。 03. 為什么浮點(diǎn)計(jì)算不準(zhǔn)確? 用戶經(jīng)常對這樣的結(jié)果感到驚訝: >>> 1.2 - 1.0 并且認(rèn)為這是 Python 中的一個(gè) bug。其實(shí)不是這樣。這與 Python 關(guān)系不大,而與底層平臺(tái)如何處理浮點(diǎn)數(shù)字關(guān)系更大。 CPython 中的 float 類型使用 C 語言的 double 類型進(jìn)行存儲(chǔ)。float對象的值是以固定的精度(通常為 53 位)存儲(chǔ)的二進(jìn)制浮點(diǎn)數(shù),由于 Python 使用 C 操作,而后者依賴于處理器中的硬件實(shí)現(xiàn)來執(zhí)行浮點(diǎn)運(yùn)算。這意味著就浮點(diǎn)運(yùn)算而言,Python 的行為類似于許多流行的語言,包括 C 和 Java。 許多可以輕松地用十進(jìn)制表示的數(shù)字不能用二進(jìn)制浮點(diǎn)表示。例如,在輸入以下語句后: >>> x = 1.2 為 x 存儲(chǔ)的值是與十進(jìn)制的值 1.2 (非常接近) 的近似值,但不完全等于它。在典型的機(jī)器上,實(shí)際存儲(chǔ)的值是: 1.0011001100110011001100110011001100110011001100110011 (binary) 它對應(yīng)于十進(jìn)制數(shù)值: 1.1999999999999999555910790149937383830547332763671875 (decimal) 典型的 53 位精度為 Python 浮點(diǎn)數(shù)提供了 15-16 位小數(shù)的精度。 要獲得更完整的解釋,請參閱 Python 教程中的 浮點(diǎn)算術(shù) 一章。 04. 為什么 Python 字符串是不可變的? 有幾個(gè)優(yōu)點(diǎn)。 一個(gè)是性能:知道字符串是不可變的,意味著我們可以在創(chuàng)建時(shí)為它分配空間,并且存儲(chǔ)需求是固定不變的。這也是元組和列表之間區(qū)別的原因之一。 另一個(gè)優(yōu)點(diǎn)是,Python 中的字符串被視為與數(shù)字一樣“基本”。任何動(dòng)作都不會(huì)將值 8 更改為其他值,在 Python 中,任何動(dòng)作都不會(huì)將字符串 "8" 更改為其他值。 05. 為什么必須在方法定義和調(diào)用中顯式使用“self”? 這個(gè)想法借鑒了 Modula-3 語言。出于多種原因它被證明是非常有用的。 首先,更明顯的顯示出,使用的是方法或?qū)嵗龑傩远皇蔷植孔兞?。閱讀 self.x或 self.meth() 可以清楚地表明,即使您不知道類的定義,也會(huì)使用實(shí)例變量或方法。在 C++ 中,可以通過缺少局部變量聲明來判斷(假設(shè)全局變量很少見或容易識(shí)別) —— 但是在 Python 中沒有局部變量聲明,所以必須查找類定義才能確定。一些 C++ 和 Java 編碼標(biāo)準(zhǔn)要求實(shí)例屬性具有 m_ 前綴,因此這種顯式性在這些語言中仍然有用。 其次,這意味著如果要顯式引用或從特定類調(diào)用該方法,不需要特殊語法。在 C++ 中,如果你想使用在派生類中重寫基類中的方法,你必須使用 :: 運(yùn)算符 -- 在 Python 中你可以編寫 baseclass.methodname(self, <argumentlist>)。這對于 __init__() 方法非常有用,特別是在派生類方法想要擴(kuò)展同名的基類方法,而必須以某種方式調(diào)用基類方法時(shí)。 最后,它解決了變量賦值的語法問題:為了 Python 中的局部變量(根據(jù)定義?。┰诤瘮?shù)體中賦值的那些變量(并且沒有明確聲明為全局)賦值,就必須以某種方式告訴解釋器一個(gè)賦值是為了分配一個(gè)實(shí)例變量而不是一個(gè)局部變量,它最好是通過語法實(shí)現(xiàn)的(出于效率原因)。C++ 通過聲明來做到這一點(diǎn),但是 Python 沒有聲明,僅僅為了這個(gè)目的而引入它們會(huì)很可惜。使用顯式的 self.var 很好地解決了這個(gè)問題。類似地,對于使用實(shí)例變量,必須編寫 self.var 意味著對方法內(nèi)部的非限定名稱的引用不必搜索實(shí)例的目錄。換句話說,局部變量和實(shí)例變量存在于兩個(gè)不同的命名空間中,您需要告訴 Python 使用哪個(gè)命名空間。 06. 為什么不能在表達(dá)式中賦值? 許多習(xí)慣于 C 或 Perl 的人抱怨,他們想要使用 C 的這個(gè)特性: while (line = readline(f)) {但在 Python 中被強(qiáng)制寫成這樣: while True: 不允許在 Python 表達(dá)式中賦值的原因是這些其他語言中常見的、很難發(fā)現(xiàn)的錯(cuò)誤,是由這個(gè)結(jié)構(gòu)引起的: if (x = 0) {錯(cuò)誤是一個(gè)簡單的錯(cuò)字:x = 0 ,將 0 賦給變量 x ,而比較 x == 0 肯定是可以預(yù)期的。 已經(jīng)有許多替代方案提案。大多數(shù)是為了少打一些字的黑客方案,但使用任意或隱含的語法或關(guān)鍵詞,并不符合語言變更提案的簡單標(biāo)準(zhǔn):它應(yīng)該直觀地向尚未被介紹到這一概念的人類讀者提供正確的含義。 一個(gè)有趣的現(xiàn)象是,大多數(shù)有經(jīng)驗(yàn)的 Python 程序員都認(rèn)識(shí)到 while True 的習(xí)慣用法,也不太在意是否能在表達(dá)式構(gòu)造中賦值; 只有新人表達(dá)了強(qiáng)烈的愿望希望將其添加到語言中。 有一種替代的拼寫方式看起來很有吸引力,但通常不如"while True"解決方案可靠: line = f.readline() 問題在于,如果你改變主意(例如你想把它改成 sys.stdin.readline() ),如何知道下一行。你必須記住改變程序中的兩個(gè)地方 -- 第二次出現(xiàn)隱藏在循環(huán)的底部。 最好的方法是使用迭代器,這樣能通過 for 語句來循環(huán)遍歷對象。例如 file objects 支持迭代器協(xié)議,因此可以簡單地寫成: for line in f: 07 為什么 Python 對某些功能(例如 list.index())使用方法來實(shí)現(xiàn),而其他功能(例如 len(List))使用函數(shù)實(shí)現(xiàn)? 正如 Guido 所說:
08. 為什么 join()是一個(gè)字符串方法而不是列表或元組方法? 從 Python 1.6 開始,字符串變得更像其他標(biāo)準(zhǔn)類型,當(dāng)添加方法時(shí),這些方法提供的功能與始終使用 String 模塊的函數(shù)時(shí)提供的功能相同。這些新方法中的大多數(shù)已被廣泛接受,但似乎讓一些程序員感到不舒服的一種方法是: ", ".join(['1', '2', '4', '8', '16']) 結(jié)果如下: "1, 2, 4, 8, 16" 反對這種用法有兩個(gè)常見的論點(diǎn)。 第一條是這樣的:“使用字符串文本(String Constant)的方法看起來真的很難看”,答案是也許吧,但是字符串文本只是一個(gè)固定值。如果在綁定到字符串的名稱上允許使用這些方法,則沒有邏輯上的理由使其在文字上不可用。 第二個(gè)異議通常是這樣的:“我實(shí)際上是在告訴序列使用字符串常量將其成員連接在一起”。遺憾的是并非如此。出于某種原因,把 split() 作為一個(gè)字符串方法似乎要容易得多,因?yàn)樵谶@種情況下,很容易看到: "1, 2, 4, 8, 16".split(", ")是對字符串文本的指令,用于返回由給定分隔符分隔的子字符串(或在默認(rèn)情況下,返回任意空格)。 join() 是字符串方法,因?yàn)樵谑褂迷摲椒〞r(shí),您告訴分隔符字符串去迭代一個(gè)字符串序列,并在相鄰元素之間插入自身。此方法的參數(shù)可以是任何遵循序列規(guī)則的對象,包括您自己定義的任何新的類。對于字節(jié)和字節(jié)數(shù)組對象也有類似的方法。 09. 異常有多快? 如果沒有引發(fā)異常,則 try/except 塊的效率極高。實(shí)際上捕獲異常是昂貴的。在 2.0 之前的 Python 版本中,通常使用這個(gè)習(xí)慣用法: try: 只有當(dāng)你期望 dict 在任何時(shí)候都有 key 時(shí),這才有意義。如果不是這樣的話,你就是應(yīng)該這樣編碼: if key in mydict: 對于這種特定的情況,您還可以使用 value = dict.setdefault(key, getvalue(key)),但前提是調(diào)用 getvalue()足夠便宜,因?yàn)樵谒星闆r下都會(huì)對其進(jìn)行評估。 10. 為什么 Python 中沒有 switch 或 case 語句? 你可以通過一系列 if... elif... elif... else.輕松完成這項(xiàng)工作。對于 switch 語句語法已經(jīng)有了一些建議,但尚未就是否以及如何進(jìn)行范圍測試達(dá)成共識(shí)。有關(guān)完整的詳細(xì)信息和當(dāng)前狀態(tài),請參閱 PEP 275 。 對于需要從大量可能性中進(jìn)行選擇的情況,可以創(chuàng)建一個(gè)字典,將 case 值映射到要調(diào)用的函數(shù)。例如: def function_1(...): 對于對象調(diào)用方法,可以通過使用 getattr() 內(nèi)置檢索具有特定名稱的方法來進(jìn)一步簡化: def visit_a(self, ...): 建議對方法名使用前綴,例如本例中的 visit_ 。如果沒有這樣的前綴,如果值來自不受信任的源,攻擊者將能夠調(diào)用對象上的任何方法。 11. 難道不能在解釋器中模擬線程,而非得依賴特定于操作系統(tǒng)的線程實(shí)現(xiàn)嗎? 答案 1:不幸的是,解釋器為每個(gè) Python 堆棧幀推送至少一個(gè) C 堆棧幀。此外,擴(kuò)展可以隨時(shí)回調(diào) Python。因此,一個(gè)完整的線程實(shí)現(xiàn)需要對 C 的線程支持。 答案 2:幸運(yùn)的是, Stackless Python 有一個(gè)完全重新設(shè)計(jì)的解釋器循環(huán),可以避免 C 堆棧。 12. 為什么 lambda 表達(dá)式不包含語句? Python 的 lambda 表達(dá)式不能包含語句,因?yàn)?Python 的語法框架不能處理嵌套在表達(dá)式內(nèi)部的語句。然而,在 Python 中,這并不是一個(gè)嚴(yán)重的問題。與其他語言中添加功能的 lambda 表單不同,Python 的 lambdas 只是一種速記符號,如果您懶得定義函數(shù)的話。 函數(shù)已經(jīng)是 Python 中的第一類對象,可以在本地范圍內(nèi)聲明。因此,使用 lambda 而不是本地定義的函數(shù)的唯一優(yōu)點(diǎn)是你不需要為函數(shù)創(chuàng)建一個(gè)名稱 -- 這只是一個(gè)分配了函數(shù)對象(與 lambda 表達(dá)式生成的對象類型完全相同)的局部變量! 13. 可以將 Python 編譯為機(jī)器代碼,C 或其他語言嗎? Cython 將帶有可選注釋的 Python 修改版本編譯到 C 擴(kuò)展中。Nuitka 是一個(gè)將 Python 編譯成 C++ 代碼的新興編譯器,旨在支持完整的 Python 語言。要編譯成 Java,可以考慮 VOC 。 14. Python 如何管理內(nèi)存? Python 內(nèi)存管理的細(xì)節(jié)取決于實(shí)現(xiàn)。Python 的標(biāo)準(zhǔn)實(shí)現(xiàn) CPython 使用引用計(jì)數(shù)來檢測不可訪問的對象,并使用另一種機(jī)制來收集引用循環(huán),定期執(zhí)行循環(huán)檢測算法來查找不可訪問的循環(huán)并刪除所涉及的對象。gc 模塊提供了執(zhí)行垃圾回收、獲取調(diào)試統(tǒng)計(jì)信息和優(yōu)化收集器參數(shù)的函數(shù)。 但是,其他實(shí)現(xiàn)(如 Jython 或 PyPy ),)可以依賴不同的機(jī)制,如完全的垃圾回收器 。如果你的 Python 代碼依賴于引用計(jì)數(shù)實(shí)現(xiàn)的行為,則這種差異可能會(huì)導(dǎo)致一些微妙的移植問題。 在一些 Python 實(shí)現(xiàn)中,以下代碼(在 CPython 中工作的很好)可能會(huì)耗盡文件描述符: for file in very_long_list_of_files: 實(shí)際上,使用 CPython 的引用計(jì)數(shù)和析構(gòu)函數(shù)方案, 每個(gè)新賦值的 f 都會(huì)關(guān)閉前一個(gè)文件。然而,對于傳統(tǒng)的 GC,這些文件對象只能以不同的時(shí)間間隔(可能很長的時(shí)間間隔)被收集(和關(guān)閉)。 如果要編寫可用于任何 python 實(shí)現(xiàn)的代碼,則應(yīng)顯式關(guān)閉該文件或使用 with語句;無論內(nèi)存管理方案如何,這都有效: for file in very_long_list_of_files: 篇幅有限,剩下的干貨集錦會(huì)在下一期為大家更新! |
|
|