1. 程式人生 > 實用技巧 >《Cython標準庫》2. cpython.bytearray

《Cython標準庫》2. cpython.bytearray

楔子

Cython裡面還有一個包,叫cpython,顯然這是和Python底層的資料型別相關的,我們這次學習的是cpython下面的bytearray。

關於bytearray,可能用的不是很多,一般用的都是bytes,下面我們來介紹一下bytearray。

"""
我們說C中的char對應一個Python的bytes(或者int),一個char *也對應Python中的bytes
而Python的bytearray則對應C中的 char s[]

類比一下字串的話, bytes和bytearray的關係就類似於'abc'和['a', 'b', 'c']
"""
# 建立一個bytearray的幾種方式

# 1. 傳入一個int組成的列表, 此時要求裡面int範圍是0~255
print(bytearray([97, 98, 99]))  # bytearray(b'abc')

# 2. 傳入一個指定了encoding的字串, 此時字串可以包含中文
print(bytearray("古明地覺", encoding="utf-8"))  # bytearray(b'\xe5\x8f\xa4\xe6\x98\x8e\xe5\x9c\xb0\xe8\xa7\x89')

# 3. 或者直接傳入一個bytes
print(bytearray("古明地覺".encode("utf-8")))  # bytearray(b'\xe5\x8f\xa4\xe6\x98\x8e\xe5\x9c\xb0\xe8\xa7\x89')

# 4. 傳入一個整型, 建立對應長度的空陣列, bytearray(5)等價於char s[5]
print(bytearray(5))  # bytearray(b'\x00\x00\x00\x00\x00')

# 5. 什麼都不傳遞, 建立一個空位元組陣列
print(bytearray())  # bytearray(b'')


# bytearray內部的元素可以修改
by_arr = bytearray(b"abc")
# bytearray, 顯然接收的是byte, 但是我們不可以直接傳遞byte, 而是要傳遞其對應的ASCII碼
by_arr[0] = ord("x")
print(by_arr)  # bytearray(b'xbc')

# 也可以通過切片賦值, 長度可以不一致
by_arr[:] = [99, 100, 111, 102]
print(by_arr)  # bytearray(b'cdof')

下面來看看CPython提供的關於bytearray的一些api。

cpython.bytearray

PyByteArray_Check, 函式原型: bint PyByteArray_Check(object o)

判斷一個物件的型別是不是bytearray,返回一個布林值。

from cpython.bytearray cimport PyByteArray_Check


def foo(obj):
    return PyByteArray_Check(obj)


print(foo("satori"))  # False
print(foo(b"satori"))  # False
print(foo(bytearray()))  # True

另外,CPython提供的一些api都是非常有規律的,比如:

  • PyLong_Check: 檢測一個物件是不是int
  • PyUnicode_Check: 檢測一個物件是不是str
  • PyTuple_Check: 檢測一個物件是不是元組
  • PyList_Check: 檢測一個物件是不是列表
  • PyDict_Check: 檢測一個物件是不是字典
  • PySet_Check: 檢測一個物件是不是集合

除此之外,還有很多其它的,總之Python/C api非常的有規律,我們後面會慢慢遇到,我們舉例演示一下。

# 它們都在cpython這個包下面
# 比如整型的操作為long.pxd中, 字串在unicode.pxd中, 字典在dict.pxd中等等

from cpython.bool cimport PyBool_Check
print(PyBool_Check(True), PyBool_Check(123))  # True False


from cpython.long cimport PyLong_Check
print(PyLong_Check(123), PyLong_Check("123"))  # True False

from cpython.unicode cimport PyUnicode_Check
print(PyUnicode_Check("111"), PyUnicode_Check(b"111"))  # True False

from cpython.set cimport PySet_Check, PyFrozenSet_Check
print(PySet_Check({1, 2, 3}), PySet_Check({}))  # True False
print(PyFrozenSet_Check(frozenset({1, 2, 3})), PyFrozenSet_Check({1, 2, 3}))  # True False

其它的型別可以自己嘗試一下,它們都遵循:PyXxx_Check這個規律,Xxx分別表示Python在底層對應的型別。另外,Python在底層的型別的名稱都遵循大駝峰命名法。

PyByteArray_CheckExact, 函式原型: bint PyByteArray_CheckExact(object o)

檢測一個物件的型別是不是bytearray,它和PyByteArray_Check之間的區別就在於,如果一個物件的型別是bytearray的子類,那麼PyByteArray_Check也會返回True;而PyByteArray_CheckExact則需要物件的型別必須是bytearray,才會返回True。

from cpython.bytearray cimport PyByteArray_Check, PyByteArray_CheckExact


cdef class MyByteArray(bytearray):
    pass


print(PyByteArray_Check(bytearray()), PyByteArray_Check(MyByteArray()))  # True True
print(PyByteArray_CheckExact(bytearray()), PyByteArray_CheckExact(MyByteArray()))  # True False

PyByteArray_FromObject, 函式原型: bytearray PyByteArray_FromObject(object o)

這也是Python/C api的一種,PyType1_FromType2:根據Type2物件轉換得到Type1物件;此外還有PyType1_AsType2:根據Type1物件轉換得到Type2物件。Type1和Type2都是Python在底層對應的型別,都遵循大駝峰命名法。

顯然這裡是根據object物件o得到一個bytearray,但要求o必須實現緩衝協議,否則轉化是會失敗的。

from cpython.bytearray cimport PyByteArray_FromObject
import numpy as np

# 我們說Python3中bytes、bytearray、numpy.ndarray都實現了緩衝區協議,注意:字串沒有實現
# 這裡傳遞一個字串是會報錯的
print(PyByteArray_FromObject(b"abcde"))  # bytearray(b'abcde')
print(PyByteArray_FromObject(np.array([99, 100, 101])))  # bytearray(b'c\x00\x00\x00d\x00\x00\x00e\x00\x00\x00')
print(PyByteArray_FromObject(np.array([99, 100, 101], dtype="uint8")))  # bytearray(b'cde')

PyByteArray_FromStringAndSize, 函式原型: bytearray PyByteArray_FromStringAndSize(char *string, Py_ssize_t len)

這裡表示根據字串轉成bytearray,並且可以指定長度。另外我們看到又出現了Python/C api的一個命名規則,如果轉化的時候指定長度,那麼就是_FromType2AndSize

from cpython.bytearray cimport PyByteArray_FromStringAndSize


print(PyByteArray_FromStringAndSize("abcde", 3))  # bytearray(b'abc')

PyByteArray_Concat, 函式原型: bytearray PyByteArray_Concat(object a, object b)

將兩個bytearray合併,得到一個新的bytearray,這裡又涉及到一個命名規則,我們知道在Python中呼叫的所有方法在底層都會一一對應,即使是加減乘除這些操作,在底層Python也給抽象成了一個方法。而這裡命名規則就是PyType_Function,比如列表獲取內部元素是通過__getitem__實現的,那麼在底層就對應PyList_GetItem,方法名同樣是按照大駝峰命名法。

from cpython.bytearray cimport PyByteArray_Concat
import numpy as np

# 雖說是bytearray,但是引數型別是object,所以實現了緩衝區協議的bytes和ndarray也是可以的
print(PyByteArray_Concat(b"hello ", b"satori"))  # bytearray(b'hello satori')
print(PyByteArray_Concat(np.array([99, 100], dtype="uint8"), 
                         np.array([111, 112], dtype="uint8")))  # bytearray(b'cdop')

PyByteArray_Size, 函式原型: Py_ssize_t PyByteArray_Size(object bytearray)

獲取一個bytearray的長度

from cpython.bytearray cimport PyByteArray_Size

print(PyByteArray_Size(b"satori"))  # 6

PyByteArray_AsString, 函式原型: char* PyByteArray_AsString(object bytearray)

將一個bytearray轉成string,注意:這裡的String指的是C中char *,Python中的str對應的是Unicode。

from cpython.bytearray cimport PyByteArray_AsString

# 這裡一定要寫bytearray,如果是一個bytes,那麼直譯器會異常退出
print(PyByteArray_AsString(bytearray(b"satori")))  # b'satori'

事實上,雖說裡面很多函式可以接收bytes,但是我們還是傳遞bytearray比較好,如果真想傳遞bytes,那麼從cpython.bytes裡面匯入就行了。

PyByteArray_Resize, 函式原型: int PyByteArray_Resize(object bytearray, Py_ssize_t len)

改變一個bytearray的容量

from cpython.bytearray cimport PyByteArray_Resize

b = bytearray(b"satori")
PyByteArray_Resize(b, 8)
# 顯然這裡的長度指的就是內部可見字元的數量
print(b)  # bytearray(b'satori\x00\x00')

PyByteArray_Resize(b, 4)
print(b)  # bytearray(b'sato')

"""
我們看到擴容的話,使用\x00填充
縮容的話,直接截斷
"""

最後還有一個PyByteArray_AS_STRING(PyByteArray_AsString的一個巨集)和一個PyByteArray_GET_SIZE(PyByteArray_Size的一個巨集),可以自己試一下,使用起來沒有區別。

小結

以上就是CPython的一些底層操作,總之這裡面的引數可以接收多個型別,但是我們型別最好還是要傳遞準確,否則可能造成直譯器異常崩潰。比如你從cpytho.long裡面匯入,那麼就傳遞整型即可,從cpython.list裡面匯入,那麼就傳遞列表即可。儘管列表和元組有很相似的地方,比如都支援索引取值,但從cpython.list裡面匯入的函式,還是不要傳遞一個元組,反之亦然。

總之,cpython下面的pxd都是用Python的型別名作為檔名的,所以從哪個檔案匯入的,就只傳遞哪種型別,確保不會出現問題。

另外,我們還說了CPython中關於型別、例項物件、以及函式的命名的一些操作,它們具有很強的規律性。