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


猜你喜欢
- 拆包是指将一个结构中的数据拆分为多个单独变量中。以元组为例:>>> a = ('windows', 10,
- 本文实例讲述了python针对mysql数据库的连接、查询、更新、删除操作。分享给大家供大家参考,具体如下:连接一 代码import pym
- 近日,有朋友一直打听flash连结服务器相关的知识,搞得我忧心重重,重点是自己也忘记了,大部分Flash的相关开发都是两年前的事,而且fla
- 本文实例讲述了从ThinkPHP3.2.3过渡到ThinkPHP5.0学习笔记。分享给大家供大家参考,具体如下:用tp3.2.3做了不少项目
- 丢弃现有MySQL的表是很容易的。但是需要非常小心,删除任何现有的一个表后将无法恢复,因为数据丢失。语法:下面是通用的SQL语法丢弃(删除)
- 利用Chrome或Firefox保存的Har文件http/https请求,可用于遍历字典提交From表单.少说废话直接上代码Github地址
- 一、在settings.py中配置DATABASES = { 'default': { 'ENGINE&
- 定义行为要定义行为,通过继承 yii\base\Behavior 或其子类来建立一个类。如:namespace app\components
- 什么是集合1.集合是一个可变容器2.集合内的数据对象都是唯一的(不能重复)3.集合是无序的存储结构,集合内的数据没有先后关系4.集合是可迭代
- 示例首先模拟一个业务场景,有订单、产品、自定义订单三个结构体,订单中包含多个产品:type Order struct {Id
- 1. 张量的拼接(1) numpy.concatenatenp.concatenate((a1,a2,a3,…), axis=0)张量的拼接
- 我就废话不多说了,大家还是直接看代码吧~import datetime# 时间格式 .%f 毫秒## "%Y-%m-%dT%H:%
- 调用的api接口:https://api.exchangerate-api.com/v4/latest/USD完整代码import requ
- 这篇文章主要介绍了mysql数据迁徙方法工具解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以
- SQL Server 2008“阻止保存要求重新创建表的更改”的错误的解决方案是本文我们主要要介绍的内容,情况是这样的:我们在用SQL Se
- Oracle中有多种方法可以向数据库或服务器文件系统上载文件,这里主要介绍如下三种:Oracle HTTP Server(OHS)的mod_
- 前言MySQL日志记录了MySQL数据库日常操作和错误信息。MySQL有不同类型的日志文件(各自存储了不同类型的日志),从日志当中可以查询到
- 今天写Python程序上传图片需要用到PIL库,于是到http://www.pythonware.com/products/pil/#pil
- js function定义函数的4种方法1.最基本的作为一个本本分分的函数声明使用。 复制代码代码如下: function func(){}
- @ResponseBody 和 @RequestBody 注解的区别1 前言在详述 @ResponseBody 和 @RequestBody