Python列表中的额外元素

8 投票
1 回答
1029 浏览
提问于 2025-04-18 09:00

我在用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 个回答

3

你的 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 分配的数据不会被垃圾回收,所以如果你在某个代码路径中出错,就会导致内存泄漏。你应该检查如何处理每种具体情况。

撰写回答