通过Cython将Python字符串传递给C

8 投票
1 回答
11609 浏览
提问于 2025-04-17 06:46

我正在尝试写一个模块,里面有一些C语言和一些Python的部分。我使用Cython来连接这两者。

我想把我的(非常长的)字符串常量存储在Python中,因为这样写起来更简单:

const char long_string = "\npart of string\n"
  "next part\n"
  "last part\n";

而不是:

long_string = """
part of string
next part
last part
"""

(这些字符串比这个长得多,而且更复杂——以至于每次想编辑时,我都不想手动添加和删除"\n"。实际上,它们是OpenCL内核。)

我需要能通过Cython把这些字符串转换成C字符串,根据文档,我只需要这样做:

cdef bytes py_bytes = py_string.encode()
cdef char* c_string = py_bytes

而且不需要手动管理内存,只要我保持对py_bytes的引用,c_string就能正常工作。

然而,我在用简单的printf测试时遇到了问题。这里是我的Cython文件:

cdef extern from "stdio.h":
  printf(char* string)

def go():
  py_string = """
a complicated string
with a few
newlines.
"""

  cdef bytes py_bytes = py_string.encode()

  cdef char* c_string = py_bytes

  printf(c_string)

  print "we don't get this far :("

在运行时用pyximport编译后,终端输出如下,然后就崩溃了:

a complicated string
with a few
newlines.
Segmentation fault: 11

现在,我检查了Cython实际上在C文件中生成了什么,并在一个普通的C文件中尝试过,它没有崩溃:

#include "stdio.h"

static char __pyx_k_1[] = "\na complicated string\nwith a few\nnewlines.\n";

int main(void) {
  void* output = printf(__pyx_k_1);
  if (!output) {
    printf("apparently, !output.");
  }
}

为了明确,Cython生成的代码会捕获printf的输出,并测试“不是那个”。变量的类型是PyObject*

我唯一的猜测是字符串没有正确结束,所以printf继续读取到字符串末尾之外,导致崩溃,但在我的纯C测试中并没有发生这种情况,所以我完全搞不懂了。

所以,我真正想问的是,如何从Cython真正地把C字符串传递给C代码?如果有人能指出更简单的解决我最开始提到的问题的方法,那也非常欢迎 :)

1 个回答

9

libc.stdio 导入 printf 解决了我的问题:

from libc.stdio cimport printf

def go():
    py_string = """
a complicated string
with a few
newlines.
"""

    cdef bytes py_bytes = py_string.encode()
    cdef char* c_string = py_bytes
    printf(c_string)

    print "we actually got this far! :)"

错误出在 printf 的声明上。应该按照 stdio.pxd 中列出的那样,

cdef extern from *:
    ctypedef char const_char "const char"

int printf(const_char *, ...)

而你的版本隐式地是 object printf(char *);这里的默认返回值类型是 Python 对象,而不是 C 语言中的 int。正确的声明可以避免 Cython 尝试对 printf 的返回值进行 Py_XDECREF 操作。

(顺便说一下,在你的“普通” C 问题中,你不应该把 printf 的返回值强制转换为 void *。)

撰写回答