《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中关于类型、实例对象、以及函数的命名的一些操作,它们具有很强的规律性。

猜你喜欢

转载自www.cnblogs.com/traditional/p/13365239.html
今日推荐