《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中關於型別、例項物件、以及函式的命名的一些操作,它們具有很強的規律性。