python基本數據類型底層實現


一、前言

我們知道,python是一種動態語言,可以將任何類型的數據賦給任何變量,譬如:

# Python代碼
x = 4
x = "four"

這里已經將 x 變量的內容由整型轉變成了字符串,而同樣的操作在 C 語言中將會導致(取決於編譯器設置)編譯錯誤或其他未知的后果。

這種靈活性是使 Python 和其他動態類型的語言更易用的原因之一。理解這一特性如何工作是學習用 Python 有效且高效地分析數據的重要因素。但是這種類型靈活性也指出了一個事實:

Python 變量不僅是它們的值,還包括了關於值的類型的一些額外信息。

 

二、整型

標准的 Python 實現是用 C 語言編寫的。這意味着每一個 Python 對象都是一個偽 C 語言結構體,該結構體不僅包含其值,還有其他信息。例如,當我們在 Python 中定義一個整型,例如 x = 10000 時,x 並不是一個“原生”整型,而是一個指針,指向一個 C 語言的復合結構體,結構體里包含了一些值。查看 Python 3.4 的源代碼,可以發現整型(長整型)的定義,如下所示(C 語言的宏經過擴展之后):

struct _longobject {
    long ob_refcnt;
    PyTypeObject *ob_type;
    size_t ob_size;
    long ob_digit[1];
};

Python 3.4 中的一個整型實際上包括 4 個部分。

  • ob_refcnt 是一個引用計數,它幫助 Python 默默地處理內存的分配和回收。
  • ob_type 將變量的類型編碼。
  • ob_size 指定接下來的數據成員的大小。
  • ob_digit 包含我們希望 Python 變量表示的實際整型值。

這意味着與 C 語言這樣的編譯語言中的整型相比,在 Python 中存儲一個整型會有一些開銷,正如下圖所示:

image

這里 PyObject_HEAD 是結構體中包含引用計數、類型編碼和其他之前提到的內容的部分。

兩者的差異在於,C 語言整型本質上是對應某個內存位置的標簽,里面存儲的字節會編碼成整型。而 Python 的整型其實是一個指針,指向包含這個 Python 對象所有信息的某個內存位置,其中包括可以轉換成整型的字節。由於 Python 的整型結構體里面還包含了大量額外的信息,所以 Python 可以自由、動態地編碼。但是,Python 類型中的這些額外信息也會成為負擔,在多個對象組合的結構體中尤其明顯。

 

三、列表

設想如果使用一個包含很多 Python 對象的 Python 數據結構,會發生什么? Python 中的標准可變多元素容器是列表。可以用如下方式創建一個整型值列表:

In[1]: L = list(range(10))
       L
Out[1]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In[2]: type(L[0])
Out[2]: int

或者創建一個字符串列表:

In[3]: L2 = [str(c) for c in L]
       L2
Out[3]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
In[4]: type(L2[0])
Out[4]: str

因為 Python 的動態類型特性,甚至可以創建一個異構的列表:

In[5]: L3 = [True, "2", 3.0, 4]
       [type(item) for item in L3]
Out[5]: [bool, str, float, int]

但是想擁有這種靈活性也是要付出一定代價的:為了獲得這些靈活的類型,列表中的每一項必須包含各自的類型信息、引用計數和其他信息;也就是說,每一項都是一個完整的 Python 對象。來看一個特殊的例子,如果列表中的所有變量都是同一類型的,那么很多信息都會顯得多余——將數據存儲在固定類型的數組中應該會更高效。動態類型的列表和固定類型的(NumPy 式)數組間的區別如下圖所示。

image

在實現層面,數組基本上包含一個指向連續數據塊的指針。另一方面,Python 列表包含一個指向指針塊的指針,這其中的每一個指針對應一個完整的 Python 對象(如前面看到的 Python 整型)。另外,列表的優勢是靈活,因為每個列表元素是一個包含數據和類型信息的完整結構體,而且列表可以用任意類型的數據填充。固定類型的 NumPy 式數組缺乏這種靈活性,但是能更有效地存儲和操作數據。

 

四、固定類型數組

1. 使用array模塊創建數組

Python 提供了幾種將數據存儲在有效的、固定類型的數據緩存中的選項。內置的數組(array)模塊(在 Python 3.3 之后可用)可以用於創建統一類型的密集數組:

In[6]: import array
       L = list(range(10))
       A = array.array('i', L)
       A
Out[6]: array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

這里的 'i' 是一個數據類型碼,表示數據為整型。

更實用的是 NumPy 包中的 ndarray 對象。Python 的數組對象提供了數組型數據的有效存儲,而 NumPy 為該數據加上了高效的操作。稍后將介紹這些操作,這里先展示幾種創建 NumPy 數組的方法。

從用 np 別名導入 NumPy 的標准做法開始:

In[7]: import numpy as np

 

2. 從python列表創建NumPy 數組

首先,可以用 np.array 從 Python 列表創建數組:

In[8]: # 整型數組:
       np.array([1, 4, 2, 5, 3])
Out[8]: array([1, 4, 2, 5, 3])

請記住,不同於 Python 列表,NumPy 要求數組必須包含同一類型的數據。如果類型不匹配,NumPy 將會向上轉換(如果可行)。這里整型被轉換為浮點型:

In[9]: np.array([3.14, 4, 2, 3])
Out[9]: array([ 3.14,  4.  ,  2.  ,  3. ])

如果希望明確設置數組的數據類型,可以用 dtype 關鍵字:

In[10]: np.array([1, 2, 3, 4], dtype='float32')
Out[10]: array([ 1.,  2.,  3.,  4.], dtype=float32)

最后,不同於 Python 列表,NumPy 數組可以被指定為多維的。以下是用列表的列表初始化多維數組的一種方法:

In[11]: # 嵌套列表構成的多維數組
        np.array([range(i, i + 3) for i in [2, 4, 6]])
Out[11]: array([[2, 3, 4],
                [4, 5, 6],
                [6, 7, 8]])

內層的列表被當作二維數組的行。

 

3. 從頭創建NumPy 數組

面對大型數組的時候,用 NumPy 內置的方法從頭創建數組是一種更高效的方法。以下是幾個示例:

In[12]: # 創建一個長度為10的數組,數組的值都是0
        np.zeros(10, dtype=int)

Out[12]: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

In[13]: # 創建一個3×5的浮點型數組,數組的值都是1
        np.ones((3, 5), dtype=float)

Out[13]: array([[ 1.,  1.,  1.,  1.,  1.],
                [ 1.,  1.,  1.,  1.,  1.],
                [ 1.,  1.,  1.,  1.,  1.]])

In[14]: # 創建一個3×5的浮點型數組,數組的值都是3.14
        np.full((3, 5), 3.14)

Out[14]: array([[ 3.14,  3.14,  3.14,  3.14,  3.14],
                [ 3.14,  3.14,  3.14,  3.14,  3.14],
                [ 3.14,  3.14,  3.14,  3.14,  3.14]])

In[15]: # 創建一個3×5的浮點型數組,數組的值是一個線性序列
        # 從0開始,到20結束,步長為2
        # (它和內置的range()函數類似)
        np.arange(0, 20, 2)

Out[15]: array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])

In[16]: # 創建一個5個元素的數組,這5個數均勻地分配到0~1
        np.linspace(0, 1, 5)

Out[16]: array([ 0.  ,  0.25,  0.5 ,  0.75,  1. ])

In[17]: # 創建一個3×3的、在0~1均勻分布的隨機數組成的數組
        np.random.random((3, 3))

Out[17]: array([[ 0.99844933,  0.52183819,  0.22421193],
                [ 0.08007488,  0.45429293,  0.20941444],
                [ 0.14360941,  0.96910973,  0.946117 ]])


In[18]: # 創建一個3×3的、均值為0、方差為1的
        # 正態分布的隨機數數組
        np.random.normal(0, 1, (3, 3))

Out[18]: array([[ 1.51772646,  0.39614948, -0.10634696],
                [ 0.25671348,  0.00732722,  0.37783601],
                [ 0.68446945,  0.15926039, -0.70744073]])

In[19]: # 創建一個3×3的、[0, 10)區間的隨機整型數組
        np.random.randint(0, 10, (3, 3))

Out[19]: array([[2, 3, 4],
                [5, 7, 8],
                [0, 5, 0]])

In[20]: # 創建一個3×3的單位矩陣
        np.eye(3)

Out[20]: array([[ 1.,  0.,  0.],
                [ 0.,  1.,  0.],
                [ 0.,  0.,  1.]])

In[21]: # 創建一個由3個整型數組成的未初始化的數組
        # 數組的值是內存空間中的任意值
        np.empty(3)

Out[21]: array([ 1.,  1.,  1.])

 

五、NumPy標准數據類型

NumPy 數組包含同一類型的值,因此詳細了解這些數據類型及其限制是非常重要的。因為 NumPy 是在 C 語言的基礎上開發的,所以 C、Fortran 和其他類似語言的用戶會比較熟悉這些數據類型。

數據類型

描述

bool_

布爾值(真、True 或假、False),用一個字節存儲

int_

默認整型(類似於 C 語言中的 long,通常情況下是 int64 或 int32)

intc

同 C 語言的 int 相同(通常是 int32 或 int64)

intp

用作索引的整型(和 C 語言的 ssize_t 相同,通常情況下是 int32 或 int64)

int8

字節(byte,范圍從–128 到 127)

int16

整型(范圍從–32768 到 32767)

int32

整型(范圍從–2147483648 到 2147483647)

int64

整型(范圍從–9223372036854775808 到 9223372036854775807)

uint8

無符號整型(范圍從 0 到 255)

uint16

無符號整型(范圍從 0 到 65535)

uint32

無符號整型(范圍從 0 到 4294967295)

uint64

無符號整型(范圍從 0 到 18446744073709551615)

float_

float64 的簡化形式

float16

半精度浮點型:符號比特位,5 比特位指數(exponent),10 比特位尾數(mantissa)

float32

單精度浮點型:符號比特位,8 比特位指數,23 比特位尾數

float64

雙精度浮點型:符號比特位,11 比特位指數,52 比特位尾數

complex_

complex128 的簡化形式

complex64

復數,由兩個 32 位浮點數表示

complex128

復數,由兩個 64 位浮點數表示

上表列出了標准 NumPy 數據類型。請注意,當構建一個數組時,你可以用一個字符串參數來指定數據類型:

np.zeros(10, dtype='int16')

或者用相關的 NumPy 對象來指定:

np.zeros(10, dtype=np.int16)

還可以進行更高級的數據類型指定,例如指定高位字節數或低位字節數;更多的信息可以在 NumPy 文檔(http://numpy.org/)中查看。

 

六、參考

1. 《Python數據科學手冊》 [美] Jake VanderPlas [VanderPlas, Jake]

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM