Cython到python和具有未知大小字符数组的c库之间的接口
我有一个C库,它从文件中读取二进制数据,将其转换并将所有内容存储在一个大字符*中,以将数据返回给任何调用它的对象。这在C语言中运行良好,但使用python/Cython时,我在分配内存时遇到了问题 图书馆原型是:Cython到python和具有未知大小字符数组的c库之间的接口,python,memory,dynamic,cython,cythonize,Python,Memory,Dynamic,Cython,Cythonize,我有一个C库,它从文件中读取二进制数据,将其转换并将所有内容存储在一个大字符*中,以将数据返回给任何调用它的对象。这在C语言中运行良好,但使用python/Cython时,我在分配内存时遇到了问题 图书馆原型是: int readWrapper(结构选项opt,char*lineOut) 我的pyx文件我有: from libc.string cimport strcpy, memset from libc.stdlib cimport malloc, free from libc.stdio
int readWrapper(结构选项opt,char*lineOut)代码>
我的pyx文件我有:
from libc.string cimport strcpy, memset
from libc.stdlib cimport malloc, free
from libc.stdio cimport printf
cdef extern from "reader.h":
struct options:
int debug;
char *filename;
options opt
int readWrapper(options opt, char *lineOut);
def pyreader(file, date, debug=0):
import logging
cdef options options
# Get the filename
options.filename = <char *>malloc(len(file) * sizeof(char))
options.debug = debug
# Size of array
outSize = 50000
cdef char *line_output = <char *> malloc(outSize * sizeof(char))
memset(line_output, 1, outSize)
line_output[outSize] = 0
# Call reader
return_val = readWrapper(options, line_output)
# Create dataframe
from io import StringIO
data = StringIO(line_output.decode('UTF-8', 'strict'))
df = pd.read_csv(data, delim_whitespace=True, header=None)
# Free memory
free(line_output)
return df
新的pyx文件:
from libc.string cimport strcpy, memset
from libc.stdlib cimport malloc, free
from libc.stdio cimport printf
cdef extern from "reader.h":
struct options:
int debug;
char *filename;
options opt
int readWrapper(options opt, char *lineOut);
def pyreader(file, date, debug=0):
import logging
cdef options options
# Get the filename
options.filename = <char *>malloc(len(file) * sizeof(char))
options.debug = debug
cdef char *line_output = NULL
# Call reader
return_val = readWrapper(options, &line_output)
# Create dataframe
from io import StringIO
data = StringIO(line_output.decode('UTF-8', 'strict'))
df = pd.read_csv(data, delim_whitespace=True, header=None)
# Free memory
free(line_output)
free(options.filename)
return df
使用python3 test.py | head时
。如果没有头部,则不会显示错误
最后,关于文件名及其分配的建议也不适用于我。使用options.filename=file
会导致TypeError:expected bytes,str在运行时找到
,但会编译。有趣的是,只有在运行调用包装器的python代码时才会发生这种情况:
python3 test.py | head
。如果没有管道和封头,则不存在断开管道错误。因此,这没什么大不了的,但我想知道是什么导致了它
搜索BrokenPipeError后编辑
此断开管道错误问题发生在头部,而不是尾部。可在此处找到此“错误”的解释:
解决方案pyx文件:
与前面提到的readWrapper.c一起使用的最后一个reader.pyx文件。内存分配由C处理,清理(最后)由pyx代码处理
from libc.stdlib cimport free
cdef extern from "reader.h":
struct options:
int debug;
char *filename;
char *DAY;
options opt
int readWrapper(options opt, char **lineOut);
def pyreader(file, date, debug=0):
import logging
import sys
import errno
import pandas as pd
# Init return valus
a = pd.DataFrame()
cdef options options
cdef char *line_output = NULL
# logging
logging.basicConfig(stream=sys.stdout,
format='%(asctime)s:%(process)d:%(filename)s:%(lineno)s:pyreader: %(message)s',
datefmt='%Y%m%d_%H.%M.%S',
level=logging.DEBUG if debug > 0 else logging.INFO)
try:
# Check inputs
if file is None:
raise Exception("No valid filename provided")
if date is None:
raise Exception("No valid date provided")
# Get the filename
file_enc = file.encode("ascii")
options.filename = file_enc
# Get date
day_enc = date.encode('ascii')
options.DAY = day_enc
try:
# Call reader
return_val = readWrapper(options, &line_output)
if (return_val > 0):
logging.error("pyreadASTERIX2 failed with exitcode {}".format(return_val))
return a
except Exception:
logging.exception("Error occurred")
free(line_output)
return a
from io import StringIO
try:
data = StringIO(line_output.decode('UTF-8', 'strict'))
logging.debug("return_val: {} and size: {}".format(return_val, len(line_output.decode('UTF-8', 'strict'))))
a = pd.read_csv(data, delim_whitespace=True, header=None, dtype={'id':str})
if a.empty:
logging.error("failed to load {} not enough data to construct DataFrame".format(file))
return a
logging.debug("converted data into pd")
except Exception as e:
logging.exception("Exception occured while loading: {} into DataFrame".format(file))
return a
finally:
free(line_output)
logging.debug("Size of df: {}".format(len(a)))
# Success, return DataFrame
return a
except Exception:
logging.exception("pyreader returned with an exception:")
return a
您有两个基本选项:
事先弄清楚如何计算尺寸
size = calculateSize(...) # for example, by pre-reading the file
line_output = <char*>malloc(size)
return_val = readWrapper(options, line_output)
b。将指针传递给指针并更改它
// C
int readWrapper(options opt, char** str_out) {
// work out the length
*str_out = malloc(length);
// etc
}
# Cython
char* line_out
return_value = readWrapper(options, &line_out)
您需要确保您分配的所有字符串都已清理。您仍然存在选项.filename的内存泄漏。对于options.filename
您最好通过Cython获取指向文件内容的指针。只要文件
存在,这是有效的,因此您不需要进行分配
options.filename = file
只需确保选项
不会超过文件
(也就是说,它不会被存储起来供以后在C中的任何地方使用)
大体上
something = malloc(...)
try:
# code
finally:
free(something)
是确保清理的良好模式。行输出[outSize]=0
-这是最后一个。此外,还存在一些内存泄漏。不过,最根本的问题是,您的C库有一个完全损坏的接口—它在不知道数组大小的情况下写入C数组。要想修好它,就必须扔掉这个图书馆。幸运的是,我控制了这个图书馆。该库还由一个可执行文件使用,该文件将数据打印到标准输出。我还希望在pandas数据帧中提供相同的数据,因此使用这个pyx例程以及这个char数组和StringIO。由于文件的二进制性质,数据帧中要打印或存储的数据量不可能事先知道。你知道如何绕过这个或使用这个字符*?解包数据是一行,每行包含20到25个“列”(取决于选项)。我将让readWrapper
负责分配内存。要么返回数组,要么使用指针到指针的参数。我选择了选项2b,并尝试了一些方法。不幸的是,readWrapper是专有的,我不允许在这里共享它,但只要我删除所有printf语句,它就可以工作。fprintf(stderr,“string here”)可以很好地工作,但使用stdout时,我会遇到我真的不理解的断管错误。关于剩余的漏洞,我也释放了这些漏洞,因此将编辑我的OP。使用您的建议会导致:TypeError:expected bytes,str found,我也不理解。谢谢你!我猜编码错误在options.filename=file
?这是因为Python3字符串是Unicode的,所以不要简单地转换为C字符串。您可能需要file\u enc=file.encode(“ascii”);options.filename=file_enc
。显然ascii可能不是正确的编码。。。我恐怕对BrokenPipeErrors
一点线索也没有。您是否实际设置了打开的文件名?我想知道这是否是您的一些问题的根源……嗨,大卫,我将尝试编码并报告。还添加了关于管道错误的新注释,该错误已“解决”,但仍不理解该问题。关于该文件,我的问题现在包含最新版本,包括打开该文件的readWrapper函数。我发现管道问题是头部如何工作的结果(通过“过早”关闭管道,例如,尾部不会折断管道)。请看下面的答案:
// C
int readWrapper(options opt, char** str_out) {
// work out the length
*str_out = malloc(length);
// etc
}
# Cython
char* line_out
return_value = readWrapper(options, &line_out)
options.filename = file
something = malloc(...)
try:
# code
finally:
free(something)