Python列表中的额外元素
我在用Cython的时候,想把一个Python列表转换成Cython数组,反过来也可以。这个Python列表里装的是0到255之间的数字,所以我把数组的类型指定为unsigned char
数组。下面是我用来做转换的代码:
from libc.stdlib cimport malloc
cdef to_array(list pylist):
cdef unsigned char *array
array = <unsigned char *>malloc(len(pylist) * sizeof(unsigned char))
cdef long count = 0
for item in pylist:
array[count] = item
count += 1
return array
cdef to_list(array):
pylist = [item for item in array]
return pylist
def donothing(pylist):
return to_list(to_array(pylist))
问题在于,Cython数组里会产生一些垃圾数据,当我把它转换回Python列表的时候,这些垃圾数据也跟着过来了。例如,donothing
这个函数本来应该什么都不做,直接把原来的Python列表返回给我,但我运行后得到的结果却是这样的:
In[56]: donothing([2,3,4,5])
Out[56]: [2, 3, 4, 5, 128, 28, 184, 6, 161, 148, 185, 69, 106, 101]
这些数据是从哪里来的呢?我该怎么清理这些垃圾数据,避免浪费内存呢?
另外,可能有更好的方法可以把Python列表里的数字放进unsigned char
数组里。如果有的话,请告诉我更好的方法。
1 个回答
你的 to_array
函数没有指定返回值的类型。而且,你把结果赋值给了一个没有类型的变量。因此,Cython 被迫将 char *
转换为 Python 类型。
Cython 将其转换为 bytes
,因为 char
大致上可以看作是 bytes
。不幸的是,如果没有明确给出长度,Cython 会假设 char *
是以零结尾的。这就是问题所在:
convert_lists.donothing([1, 2, 3, 0, 4, 5, 6])
#>>> [1, 2, 3]
当没有零的时候,Cython 会一直读取,直到找到一个零,这样就会越过实际分配的内存。
你实际上不能对任意的 Cython 类型使用 for x in my_pointer_array
。这个 for
循环实际上是在处理错误转换的 bytes
。
你可以通过为所有存放 char
数组的值指定类型,明确传递长度,并使用范围循环(这样当循环变量有类型时会更快)来解决这个问题,或者使用某种包装器。如果你想知道用什么包装数组,可以参考 这个问题和答案。
请注意,当使用手动分配时,你应该非常小心错误。通过 malloc
分配的数据不会被垃圾回收,所以如果你在某个代码路径中出错,就会导致内存泄漏。你应该检查如何处理每种具体情况。