為什麼使用縮排來分組語句?15個為什麼,幫助你更好的理解Python!

視學演算法發表於2020-04-06

點選上方視學演算法,選擇設為星標

為什麼使用縮排來分組語句?15個為什麼,幫助你更好的理解Python!


閱讀文字大概需要 6 分鐘。

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

Guido van Rossum 認為使用縮排進行分組非常優雅,並且大大提高了普通 Python 程式的清晰度。大多數人在一段時間後就學會並喜歡上這個功能。

由於沒有開始/結束括號,因此解析器感知的分組與人類讀者之間不會存在分歧。偶爾 C 程式設計師會遇到像這樣的程式碼片段:

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

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

因為沒有開始/結束括號,所以 Python 不太容易發生編碼式衝突。在 C 中,括號可以放到許多不同的位置。如果您習慣於閱讀和編寫使用一種風格的程式碼,那麼在閱讀(或被要求編寫)另一種風格時,您至少會感到有些不安。

許多編碼風格將開始/結束括號單獨放在一行上。這使得程式相當長,浪費了寶貴的螢幕空間,使得更難以對程式進行全面的瞭解。理想情況下,函式應該適合一個螢幕(例如,20--30 行)。20 行 Python 可以完成比 20 行 C 更多的工作。這不僅僅是由於缺少開始/結束括號 -- 缺少宣告和高階資料型別也是其中的原因 -- 但縮排基於語法肯定有幫助。

02. 為什麼簡單的算術運算得到奇怪的結果?

請看下一個問題。


03. 為什麼浮點計算不準確?

使用者經常對這樣的結果感到驚訝:

>>> 1.2 - 1.00.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 教程中的 浮點算術 一章。

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

有幾個優點。

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

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

05. 異常有多快?

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

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

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

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

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

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

你可以通過一系列 if... elif... elif... else.輕鬆完成這項工作。對於 switch 語句語法已經有了一些建議,但尚未就是否以及如何進行範圍測試達成共識。有關完整的詳細資訊和當前狀態,請參閱 PEP 275 

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

def function_1(...): ...
functions = {'a': function_1, 'b': function_2, 'c': self.method_1, ...}
func = functions[value]func()

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

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

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

07. 難道不能在直譯器中模擬執行緒,而非得依賴特定於作業系統的執行緒實現嗎?

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

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

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

Python 的 lambda 表示式不能包含語句,因為 Python 的語法框架不能處理巢狀在表示式內部的語句。然而,在 Python 中,這並不是一個嚴重的問題。與其他語言中新增功能的 lambda 表單不同,Python 的 lambdas 只是一種速記符號,如果您懶得定義函式的話。

函式已經是 Python 中的第一類物件,可以在本地範圍內宣告。因此,使用 lambda 而不是本地定義的函式的唯一優點是你不需要為函式建立一個名稱 -- 這只是一個分配了函式物件(與 lambda 表示式生成的物件型別完全相同)的區域性變數!

09. 可以將 Python 編譯為機器程式碼,C 或其他語言嗎?

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

10. 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)


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

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

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

12. CPython 退出時為什麼不釋放所有記憶體?

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

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

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

雖然列表和元組在許多方面是相似的,但它們的使用方式通常是完全不同的。可以認為元組類似於 Pascal 記錄或 C 結構;它們是相關資料的小集合,可以是不同型別的資料,可以作為一個組進行操作。例如,笛卡爾座標適當地表示為兩個或三個數字的元組。

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

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

14. 列表如何在 CPython 中實現?

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

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

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

15. 字典如何在 CPython 中實現?

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

字典的工作方式是使用 hash() 內建函式計算字典中儲存的每個鍵的 hash 程式碼。hash 程式碼根據鍵和每個程式的種子而變化很大;例如,"Python" 的 hash 值為-539294296,而"python"(一個按位不同的字串)的 hash 值為 1142331976。然後,hash 程式碼用於計算內部陣列中將儲存該值的位置。假設您儲存的鍵都具有不同的 hash 值,這意味著字典需要恆定的時間 -- O(1),用 Big-O 表示法 -- 來檢索一個鍵。

來源:Python官方文件、中國統計網

- END -

如果看到這裡,說明你喜歡這篇文章,請轉發、點贊掃描下方二維碼或者微信搜尋「perfect_iscas」,新增好友後即可獲得10套程式設計師全棧課程+1000套PPT和簡歷模板向我私聊「進群」二字即可進入高質量交流群。

掃描二維碼進群↓

為什麼使用縮排來分組語句?15個為什麼,幫助你更好的理解Python!

為什麼使用縮排來分組語句?15個為什麼,幫助你更好的理解Python!

為什麼使用縮排來分組語句?15個為什麼,幫助你更好的理解Python!

在看 為什麼使用縮排來分組語句?15個為什麼,幫助你更好的理解Python!

相關文章