數據人:不懂業務,分析就僅僅只是提數

數據人:不懂業務,分析就僅僅只是提數

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 位小數的精度。

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 語句語法已經有了一些建議,但尚未就是否以及如何進行範圍測試達成共識。

對於需要從大量可能性中進行選擇的情況,可以創建一個字典,將 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 表示法 -- 來檢索一個鍵。


分享到:


相關文章: