開發技術--Python核心知識A

開發|Python核心知識A

A篇,主要介紹Python基礎中列表,元祖,字典,集合,字符串,I/O,條件與循環,異常處理的底層知識與使用的注意事項。
但願你們對於Python會有新的收穫,本篇不一樣於以前的淺談Python基礎知識,會更加的深刻,可是在深刻的同時,涉及更多內容,須要自行看源碼,着重點我會標明。html

前言

目前全部的文章思想格式都是:知識+情感。
知識:對於全部的知識點的描述。力求不含任何的自我感情色彩。
情感:用我本身的方式,解讀知識點。力求通俗易懂,完美透析知識。

正文

因爲本文不是基礎篇,因此重要的名詞與知識點,若是不懂,就必定要多google.python

談談Jupyter Notebook

使用過jupyter的人都知道,我簡短作一下jupyter的介紹。git

Jupyter Notebook 官網: https://jupyter.org/github

** Jupyter 創始人: ** Fernando Pérez數據庫

Jupyter 單詞的解釋: Ju (Julia)、Py (Python)和 R 三種科學運算語言的計算工具平臺,因此將其命名爲 Ju-Py-te-R。json

Jupyter能幹什麼: 軟件代碼、計算輸出、解釋文檔、多媒體資源整合在一塊兒的多功能科學運算平臺c#

建議: jupyter屬於我定義的工具範疇,是加快學習的工具,因此須要掌握。
備註:關於jupyter在Python的安裝與使用,我在IPython介紹裏面已經寫過。數組

談談列表(list)

列表內能夠放置的內容: 任意數據類型。(有序)瀏覽器

特性: 列表是動態的,長度大小不固定,能夠隨意地增長、刪減或者改變元素(mutable)。緩存

list操做: 多使用append(),enumerate(),切片(注意步長的正負),使用[....]建立list(直接識別調用內置C函數),使用list()建立(使用函數方法,會建立stack,並進行操做的檢驗,expensive!)

時間複雜度:
增長 / 刪除的時間複雜度均爲 O(1)。
列表進行排序,而後使用二分查找,時間複雜度 O(logn) .
列表排序須要時間: O(nlogn)
遍歷列表的時間複雜度是: O(n)
兩層列表循環,在最差狀況下,須要 O(n^2) 的時間複雜度。

list使用場景: 存儲的數據和數量是不變

底層實現: 在cpython中,爲了減少每次增長 / 刪減操做時空間分配的開銷,Python 每次分配空間給列表的時候,都會額外多分配一些,這樣的機制(over-allocating)。

源碼角度:
瀏覽器輸入:
1)https://github.com/python/cpython/blob/949fe976d5c62ae63ed505ecf729f815d0baccfc/Include/listobject.h#L23
2)https://github.com/python/cpython/blob/3d75bd15ac82575967db367c517d7e6e703a6de3/Objects/listobject.c#L33
顯示list源碼關鍵內容以下:

#ifndef Py_LIMITED_API
typedef struct {
    PyObject_VAR_HEAD
    /* Vector of pointers to list elements.  list[0] is ob_item[0], etc. */
    PyObject **ob_item;

    /* ob_item contains space for 'allocated' elements.  The number
     * currently in use is ob_size.
     * Invariants:
     *     0 <= ob_size <= allocated
     *     len(list) == ob_size
     *     ob_item == NULL implies ob_size == allocated == 0
     * list.sort() temporarily sets allocated to -1 to detect mutations.
     *
     * Items must normally not be NULL, except during construction when
     * the list is not yet visible outside the function that builds it.
     */
    Py_ssize_t allocated;
} PyListObject;
#endif

源碼解讀:
列表(list)本質是 over-allocate 的數組(array)。(注意,數組就是數組,列表就是列表,數組不是列表,列表不是數組,不是一個概念。)

ob_item是一個指針列表,裏面的每個指針都是指向列表的元素。(這就是爲何列表可變)

allocated 表示的是當前列表確實被分配的空間大小。

ob_size 是列表的實際大小,實際大小是len(list) 得到結果,表示當前列表共存儲了多少個元素。

爲優化存儲結構,避免每一次更改元素都從新分配內存,列表的分配空間allocated 會大於 ob_size。即: allocated >= len(list) == ob_size。

當分配給列表的空間滿了的時候,就是allocated == len(list) 的狀況,此時會向操做系統申請更大的內存空間,並將已經存在的元素所有拷貝到新的內存中。

每個向操做系統申請內存的大小變化是:0, 4, 8, 16, 25, 35, 46, 58, 72, 88....

談談元祖(tuple)

元祖內能夠放置的內容: 任意數據類型。(有序)

特性: 元祖是靜態的,長度固定,沒法增長刪減或改變。

注意:
1) 只要對元祖修改,必然是開闢一塊新的內存,將原有數據從新寫入。
2)雖然屬於不可變數據類型,可是內部嵌套可變數據類型,可變數據類型仍屬於動態。

tuple使用場景: 存儲的數據和數量是變化的。

底層實現: Python內部對於靜態資源 ,存在資源緩存(resource caching)
解釋一下,資源緩存就是靜態數據當不被使用而且佔用內存空間不大的時候,Python會對這些 資源進行緩存處理,並不會因爲垃圾回收機制而將數據返回給操做系統。當下次建立靜態數據的時候,會直接使用已經緩存的內存空間,此時就節省了與操做系統交互的環節。
當tuple的大小不超過20,Python將其緩存子啊一個free list中,當下一次建立一樣的tuple的時候,就直接去緩存中取出來,提升運行效率。

源碼角度:
瀏覽器輸入:
1)https://github.com/python/cpython/blob/3d75bd15ac82575967db367c517d7e6e703a6de3/Include/tupleobject.h#L25
2)https://github.com/python/cpython/blob/3d75bd15ac82575967db367c517d7e6e703a6de3/Objects/tupleobject.c#L16
顯示tuple源碼關鍵內容以下:

#ifndef Py_LIMITED_API
typedef struct {
    PyObject_VAR_HEAD
    PyObject *ob_item[1];

    /* ob_item contains space for 'ob_size' elements.
     * Items must normally not be NULL, except during construction when
     * the tuple is not yet visible outside the function that builds it.
     */
} PyTupleObject;
#endif

源碼解讀:
tuple本質上也是一個數組(array),可是空間是固定的。而且tuple作了相應的優化(資源緩存),提高程序的效率。

談談字典(dict)

字典 :是一系列由鍵(key)和值(value)配對組成的元素的集合。
key必須是不可變數據類型。(只有不可變數據類型才能夠hash)

字典變成有序的啦!!!
在 Python3.7+,字典被肯定爲有序。
在 3.6 中,字典有序是一個 implementation detail,在 3.7 才正式成爲語言特性,所以 3.6 中沒法 100% 確保其有序性。
而 3.6 以前是無序的,其長度大小可變,元素能夠任意地刪減和改變。

字典的操做:
多使用{....}建立,少使用dict() 建立。
多使用.get()獲取值,少使用[...]獲取值。(get不報錯,並能夠本身指定沒找到的返回值)。
常使用pop(), items(), keys(), values()。

時間複雜度:
存儲,刪除,增長數據的時間複雜度是: O(1)

字典的存儲結構:
此時,須要回憶數據庫的表結構。
老版本 Python 的哈希表結構以下:(隨着數據量的增長,表會變的稀疏,空間利用率低)
它是一個 over-allocate 的 array,根據元素鍵(key)的哈希值,來計算其應該被插入位置的索引。

--+-------------------------------+
  | 哈希值 (hash)  鍵 (key)  值 (value)
--+-------------------------------+
0 |    hash0      key0    value0
--+-------------------------------+
1 |    hash1      key1    value1
--+-------------------------------+
2 |    hash2      key2    value2
--+-------------------------------+
. |           ...
__+_______________________________+

新版本哈希表,除了字典自己的結構,會把索引和哈希值、鍵、值單獨分開,看下面:(提升空間利用率)
它把存儲結構分紅了 Indices 和 Entries 這兩個 array,而’None‘表明這個位置分配了內存但沒有元素。

Indices
----------------------------------------------------
None | index | None | None | index | None | index ...
----------------------------------------------------
 
Entries
--------------------
hash0   key0  value0
---------------------
hash1   key1  value1
---------------------
hash2   key2  value2
---------------------
        ...
---------------------

字典的工做原理:
1)計算key的哈希值,hash(key)

2)mask = PyDicMinSize - 1 和哈希值作與操做,計算這個元素應該插入哈希表的位置 index = hash(key) & mask。

3)若是哈希表此處是空的,那麼這個元素就會被插入。

4)若是這個元素不是空的,分類討論:
a:比較兩個元素的哈希值和鍵是否相等,若是相等,代表這是同一個元素,若是值不一樣,增跟新字典的value,反之,啥都不錯。
b:比較兩個元素的哈希值和鍵是否相等,若是不相等,此時哈希衝突(hash collision 意思是兩個元素的鍵不相等,可是哈希值相等。)此時,須要一種解決衝突的策略。(最簡單是線性查找,即從這個位置開始,挨個日後尋找空位,找到就插進去。可是Python不使用這個,由於線性效率不高)

注意:哈希衝突狀況的發生,會下降字典的查找等操做速度。可是,哈希衝突機率極小,可是不表明不發生。
因此哈希表,一般會保證其至少留有** 1/3 的剩餘空間。隨着元素的不停插入,當剩餘空間小於 1/3 時,Python 會從新申請獲得更大的內存空間,擴充哈希表。此時,表內全部的元素位置都會被從新排放**。

談談集合(set)

集合: 沒有鍵和值的配對,是一系列無序的、惟一的元素組合。

集合本質: 是一個 哈希表。(不能索引)

集合操做:
不要使用pop()。(集合無序,pop誰???)

時間複雜度:
添加與查找的時間複雜度是 O(1)

列表的哈希表:徹底等同於字典的哈希表思路,只是沒有value而已。

談談字符串(str)

字符串: 是由獨立字符組成的一個序列,一般包含在單引號('')雙引號("")或者三引號之中(''' '''或""" """,二者同樣)。(不可變數據類型)

字符串操做:
字符串的算數運算: + 、*
多用 .join() 函數,實現字符串拼接
多使用 strip() ,split()

時間複雜度:
更改字符串的時間複雜度是O(n)

字符串的+操做:
從 Python2.5 開始,每次處理字符串的拼接操做時(str1 += str2),Python 首先會檢測 str1 還有沒有其餘的引用。
若是沒有的話,就會嘗試原地擴充字符串 buffer 的大小,而不是從新分配一塊內存來建立新的字符串並拷貝。
時間複雜度爲 O(n) ,因此就能夠直接使用+ 了嗎??

字符串的格式化
官方推薦使用: ''.format() ,是最新的字符串格式函數與規範
可使用以前的方法: % 進行格式化。%s 表示字符串型,%d 表示整型。

談談I/O

I/O表示的是輸入輸出,不知道你們會不會直接想到,input()與print(),文件操做

input() 函數暫停程序運行,同時等待鍵盤輸入;直到回車被按下,函數的參數即爲提示語,輸入的類型永遠是字符串型(str)
因此,針對輸入的str通常須要進行強制轉換爲 int 用 int(),轉爲浮點數用 float()。
Python 對 int 類型沒有最大限制,可是對 float 類型有精度限制。

注意:在生產環境中使用強制轉換時,請記得加上 try .....except(即錯誤和異常處理)。

文件操做:
生產級別的 Python 代碼,大部分 I/O 則來自於文件、網絡、其餘進程的消息等。
計算機內核(kernel)對文件的處理相對比較複雜,涉及到內核模式、虛擬文件系統、鎖和指針等一系列概念。建議全部的 I/O 都應該進行錯誤處理。

文件操做概覽:
1)open()函數
2)打開文件方式 r (默認),w, a .... (注意:只須要讀取文件,就不要請求寫入權限)
3)read() (注意:文件過大的處理方法,能夠給 read 指定參數 size ,用來表示讀取的最大長度。還能夠經過 readline() 函數,每次讀取一行)
4)write()
5)close()

with上下文管理:
自動判斷關閉打開的文件,代碼簡潔,不須要close()的出現。

JSON序列化:
JSON(JavaScript Object Notation)是一種輕量級的數據交換格式,它的設計意圖是把全部事情都用設計的字符串來表示,這樣既方便在互聯網上傳遞信息,也方便人進行閱讀(相比一些 binary 的協議)。

JSON操做
json.dumps() 這個函數,接受 Python 的基本數據類型,而後將其序列化爲 string;
而 json.loads() 這個函數,接受一個合法字符串,而後將其反序列化爲 Python 的基本數據類型。
注意: 進行異常捕獲。

JSON業務場景: 當開發一個第三方應用程序時,能夠經過 JSON 將用戶的我的配置輸出到文件,方便下次程序啓動時自動讀取。

擴展: 在 Google,有相似的JSON工具叫作 Protocol Buffer,已經開源。
它的優勢是生成優化後的二進制文件,所以性能更好。但與此同時,生成的二進制序列,是不能直接閱讀的。

談談條件與循環

條件: if 語句是能夠單獨使用的,但 elif、else 都必須和 if 成對使用

注意:除了 boolean 類型的數據,條件判斷最好是顯性的。

if num != 0:
        print(num)

循環:
分爲while循環和for循環。而且可使用break與continue.
要儘可能避免多層嵌套的狀況

range函數:直接由 C 語言寫的,調用它速度很是快。而且特是一個迭代器。

>>> d = range(10)
>>> d
range(0, 10)
>>> type(d)
<class 'range'>

三元運算
使用三元運算能夠簡潔明瞭,配合列表與字典的生成式,此時達到最高境界~(一行搞定千言萬語)

談談異常處理

錯誤至少包括兩種,一種是語法錯誤,另外一種則是異常

語法錯誤: 就是各類Error.....
官方解釋:https://docs.python.org/3/library/exceptions.html#bltin-exceptions

異常: try ... except ... finally.. 來解決異常。
注意:except block 只接受與它相匹配的異常類型並執行,若是程序拋出的異常並不匹配,那麼程序照樣會終止並退出。
萬能異常爸爸,Exception。Exception 是其餘全部非系統異常的基類,可以匹配任意非系統異常
finally 中,一般會放一些不管如何都要執行的語句。

自定義異常:
raise 主動拋出異常。

異常應用場景:
大型社交網站的後臺,須要針對用戶發送的請求返回相應記錄。用戶記錄每每儲存在 key-value 結構的數據庫中,每次有請求過來後,咱們拿到用戶的 ID,並用 ID 查詢數據庫中此人的記錄,就能返回相應的結果。

而數據庫返回的原始數據,每每是 json string 的形式,在 json.loads() 函數中,輸入的字符串若是不符合其規範,那麼便沒法解碼,就會拋出異常,所以須要加上異常處理。

注意: 正常的 flow-control 邏輯,不要使用異常處理,直接用條件語句解決就能夠了。
通常來講異常拋出,都會對其進行Log(通常每1000次log一次),輸出到real time的table和dashboard裏,這樣有利於以後的分析和改進。

except以後的delete操做:

"When an exception has been assigned using as target, it is cleared at the end of the except clause."
# 在異常處理的 except block 中,把異常賦予了一個變量,那麼這個變量會在 except block 執行結束時被刪除
# 在平時寫代碼時,必定要保證 except 中異常賦予的變量,在以後的語句中再也不被用到。

好比下面這個code block:
except E as N:
    foo

就等於
except E as N:
    try:
        foo
    finally:
        del N

結束語

本文涵蓋了我目前學習Python的基礎知識的全部深度知識點。但願你們學習的時候,能夠區分出什麼是深度與淺度,對深度加以理解。大不了就是github源碼來一下.... 祝,你們閱讀開心 C-:

相關文章
相關標籤/搜索