Cython处理C字符串的示例详解

作者:古明地觉 时间:2021-12-03 01:17:27 

楔子

在介绍数据类型的时候我们说过,Python 的数据类型相比 C 来说要更加的通用,但速度却远不及 C。如果你在使用 Cython 加速 Python 时遇到了瓶颈,但还希望更进一步,那么可以考虑将数据的类型替换成 C 的类型,特别是那些频繁出现的数据,比如整数、浮点数、字符串。

由于整数和浮点数默认使用的就是 C 的类型,于是我们可以从字符串入手。

创建 C 字符串

先来回顾一下如何在 Cython 中创建 C 字符串。

cdef char *s1 = b"abc"
print(s1)  # b'abc'

C 的数据和 Python 数据如果想互相转化,那么两者应该存在一个对应关系,像整数和浮点数就不必说了。但 C 的字符串本质上是一个字符数组,所以它和 Python 的 bytes 对象是对应的,我们可以将 b"abc" 直接赋值给 s1。并且在打印的时候,也会转成 Python 的 bytes 对象之后再打印。

或者还可以这么做:

cdef char s1[4]
s1[0], s1[1], s1[2] = 97, 98, 99

cdef bytes s2 = bytes([97, 98, 99])

print(s1)  # b'abc'
print(s2)  # b'abc'

直接声明一个字符数组,然后再给数组的每个元素赋值即可。

Python 的 bytes 对象也是一个字符数组,和 C 一样,数组的每个元素不能超过 255,所以两者存在对应关系。在赋值的时候,会相互转化,其它类型也是同理,举个例子:

# Python 整数和 C 整数是存在对应关系的
# 因为都是整数,所以可以相互赋值
py_num = 123
# 会根据 Python 的整数创建出 C 的整数,然后赋值给 c_num
cdef unsigned int c_num = py_num
# print 是 Python 的函数,它接收的一定是 PyObject *
# 所以在打印 C 的整数时,会转成 Python 的整数再进行打印
print(c_num, py_num)
"""
123 123
"""
# 但如果写成 cdef unsigned int c_num = "你好" 就不行了
# 因为 Python 的字符串和 C 的整数不存在对应关系
# 两者无法相互转化,自然也就无法赋值

# 浮点数也是同理,Python 和 C 的浮点数可以相互转化
cdef double c_pi = 3.14
# 赋值给 Python 变量时,也会转成 Python 的浮点数再赋值
py_pi = 3.14
print(c_pi, py_pi)
"""
3.14 3.14
"""

# Python 的 bytes 对象和 C 的字符串可以相互转化
cdef bytes py_name = bytes("古明地觉", encoding="utf-8")
cdef char *c_name = py_name
print(py_name == c_name)
"""
True
"""

# 注意:如果 Python 字符串所有字符的 ASCII 🐴均不超过 255
# 那么也可以赋值给 C 字符串
cdef char *name1 = "satori"
cdef char *name2 = b"satori"
print(name1, name2)
"""
b'satori' b'satori'
"""
# "satori" 会直接当成 C 字符串来处理,因为它里面的字符均为 ASCII
# 就像写 C 代码一样,所以 name1 和 name2 是等价的
# 而在转成 Python 对象的时候,一律自动转成 bytes 对象
# 但是注意:cdef char *c_name = "古明地觉" 这行代码不合法
# 因为里面出现了非 ASCII 字符,所以建议在给 C 字符串赋值的时候,一律使用 bytes 对象

# C 的结构体和 Python 的字典存在对应关系
ctypedef struct Girl:
    char *name
    int age

cdef Girl g
g.name, g.age = b"satori", 17
‍# 在打印的时候,会转成字典进行打印
# 当然前提是结构体的所有成员,都能用 Python 表示
print(g)
"""
{'name': b'satori', 'age': 17}
"""

所以 Python 数据和 C 数据是可以互相转化的,哪怕是结构体,也是可以的,只要两者存在对应关系,可以互相表示。但像指针就不行了,Python 没有任何一种原生类型能和 C 的指针相对应,所以 print 一个指针的时候就会出现编译错误。

以上这些都是之前介绍过的内容,但很多人可能都忘了,这里专门再回顾一下。

引用计数陷阱

这里需要再补充一个关键点,由于 bytes 对象实现了缓冲区协议,所以它内部有一个缓冲区,这个缓冲区内部存储了所有的字符。而在基于 bytes 对象创建 C 字符串的时候,不会拷贝缓冲区里的内容(整数、浮点数都是直接拷贝一份),而是直接创建一个指针指向这个缓冲区。

# 合法的代码
py_name = "古明地觉".encode("utf-8")
cdef char *c_name1 = py_name

# 不合法的代码,会出现如下编译错误
# Storing unsafe C derivative of temporary Python reference
cdef char *c_name2 = "古明地觉".encode("utf-8")

为啥在创建 c_name2 的时候就会报错呢?很简单,因为这个过程中进行了函数调用,所以产生了临时对象。换句话创建的 bytes 对象是临时的,这行代码执行结束后就会因为引用计数为 0 而被销毁。

问题来了,c_name2 不是已经指向它了吗?引用计数应该为 1 才对啊。相信你能猜到原因,这个 c_name2 的类型是 char *,它是一个 C 的变量,不会增加对象的引用计数。这个过程就是创建了一个 C 级指针,指向了临时的 bytes 对象内部的缓冲区,而解释器是不知道的。

所以临时对象最终会因为引用计数为 0 被销毁,但是这个 C 指针却仍指向它的缓冲区,于是就报错了。我们需要先创建一个 Python 变量指向它,让其不被销毁,然后才能赋值给 C 级指针。为了更好地说明这个现象,我们使用 bytearray 举例说明。

cdef bytearray buf = bytearray("hello", encoding="utf-8")
cdef char *c_str = buf

print(buf)  # bytearray(b'hello')
# 基于 c_str 修改数据
c_str[0] = ord("H")
# 再次打印 buf
print(buf)  # bytearray(b'Hello')
# 我们看到 buf 被修改了

bytearray 对象可以看作是可变的 bytes 对象,它们内部都实现了缓冲区,但 bytearray 对象的缓冲区是可以修改的,而 bytes 对象的缓冲区不能修改。所以这个例子就证明了上面的结论,C 字符串会直接共享 Python 对象的缓冲区。

因此在赋值的时候,我们应该像下面这么做。

print(
    "你好".encode("utf-8")
)  # b'\xe4\xbd\xa0\xe5\xa5\xbd'

# 如果出现了函数或类的调用,那么会产生临时对象
# 而临时对象不能直接赋值给 C 指针,必须先用 Python 变量保存起来
cdef bytes greet = "你好".encode("utf-8")
cdef char *c_greet1 = greet

# 如果非要直接赋值,那么赋的值一定是字面量的形式
# 这种方式也是可以的,但显然程序开发中我们不会这么做
# 除非它是纯 ASCII 字符
# 比如 cdef char *c_greet2 = b"hello"
cdef char *c_greet2 = b"\xe4\xbd\xa0\xe5\xa5\xbd"

print(c_greet1.decode("utf-8"))  # 你好
print(c_greet2.decode("utf-8"))  # 你好

来源:https://mp.weixin.qq.com/s/mwVpSlLOuq--foHoJdTi7A

标签:Cython,处理,C字符串
0
投稿

猜你喜欢

  • 最简单的tab切换实例代码

    2023-08-22 08:38:59
  • Python下使用Trackbar实现绘图板

    2023-12-11 10:13:04
  • 简单介绍一下pyinstaller打包以及安全性的实现

    2021-07-23 15:34:56
  • Python WSGI 规范简介

    2022-02-06 23:30:06
  • Python+Pillow+Pytesseract实现验证码识别

    2023-07-19 14:50:44
  • Python 虚拟环境工作原理解析

    2023-02-21 02:18:50
  • .Net Core SDK命令介绍及使用

    2024-05-13 09:16:00
  • TensorFlow卷积神经网络AlexNet实现示例详解

    2022-06-14 21:17:30
  • Python用二分法求平方根的案例

    2021-09-27 10:05:01
  • 13个你可能未使用过的Python特性分享

    2021-03-07 19:14:37
  • 说说值类型数据“.”操作符的类型转换

    2009-12-13 10:39:00
  • 微信小程序 scroll-view实现上拉加载与下拉刷新的实例

    2024-04-23 09:30:40
  • Transact_SQL小手册,适合初学者

    2008-08-25 19:40:00
  • 关于python中逆序的三位数

    2021-08-09 05:17:28
  • Python OpenCV实现传统图片格式与base64转换

    2021-07-20 21:42:21
  • Vue实现购物车功能

    2024-04-30 10:26:21
  • Python实现的knn算法示例

    2022-09-21 14:35:09
  • Python3实现简单可学习的手写体识别(实例讲解)

    2021-10-05 14:24:05
  • Python实现用手机监控远程控制电脑的方法

    2021-06-22 07:57:49
  • 轻松解决:mysql数据库连接过多的错误

    2010-09-30 14:28:00
  • asp之家 网络编程 m.aspxhome.com