Cython - def __init__() 方法的替换,因为 Cython 的 Python 函数和方法无法处理值为 0 的无符号字符数组

Cython - Replacement for def __init__() method since Cython's Python functions and methods cannot handle unsigned char arrays with values of 0

全部。我在下面有这个 Cython 代码示例,其中我有一个无符号字符数组,a 填充了无符号整数。当我将这个数组传入 Python def 方法时,包含 0 的索引之后的任何索引的值都会变得混乱。

在此示例中,由于 0 的值位于第 6 个索引处,因此传递到 __cinit__() 方法的数组中的所有后续数组索引都具有不正确的值。 __init__() 方法或使用 Python 声明 def.

的任何函数或方法也会发生此行为

但是,当数组被传递到任何 cdefcpdef 函数或方法时,数组的值是正确的。

所以,我有两个问题(请注意,我使用的是 .pyx 转轮文件):

  1. 我是否错误地将数组传递给了 __cinit__() 方法?还有其他方法吗?
  2. 或者,是否有替代 def __cinit__() 方法的 Cythonic 方法?当然,我可以使用变通方法并使用 cdefcpdef 方法,尤其是对于我展示的这个简单的小示例,但我想了解是否有不同的方法......

代码:

cdef class Classical:
    def __cinit__(self, unsigned char *b):
        for x in range(0, 12):
            print b[x], " init" # This does not work

    cdef void bar(self, unsigned char *b):
        for x in range(0, 12):
            print b[x], " method" # This works fine

def foo(unsigned char *b):
    for x in range(0, 12):
        print b[x], " function" # This does not work either

cdef unsigned char a[12]
a = [
    83,
    12,
    85,
    31,
    7,
    0,
    91,
    11,
    0,
    12,
    77,
    100
]
Classical(a).bar(a)
foo(a)

输出:

83  init
12  init
85  init
31  init
7  init
0  init
0  init
0  init
0  init
0  init
0  init
0  init
83  method
12  method
85  method
31  method
7  method
0  method
91  method
11  method
0  method
12  method
77  method
100  method
83  function
12  function
85  function
31  function
7  function
0  function
100  function
0  function
0  function
0  function
0  function
0  function

def 函数的所有参数都是 Python 对象。 char *unsigned char * 也是如此)不是 Python 对象,但是可以自动将(某些)Python 对象转换为 char * .所以

def foo(char *x):
   ...

对于 Cython 的意思是:检查传递的 Python 对象是否可以转换为 cdef char *,执行转换并在函数体中使用此转换的结果。

当调用带有 char * 的 def 函数时(另见这个有点相关的 )作为参数:

cdef char a[12]
....
bar(a) # a decays to char *

Cython 执行以下操作:使用 char * 的自动转换,假设它是一个空终止的 c 字符串到字节对象,并将这个临时字节对象传递给 def 函数bar.

这意味着在你的情况下:

  • 调用 foo(a) 创建一个长度为 5 的临时字节对象(而不是 12,因为第 6 个元素是 0),前 5 个字符被复制到其中。
  • 在函数 foo 内部,这个字节对象的缓冲区地址被用作 unsigned char *b,现在只有 6 个元素(包括尾随 [=28=]),因此访问它通过 b[6] 是未定义的行为,可能以分段错误结束。

您可以通过

验证ab指向不同的地址
print("Address:", <unsigned long long>(&a[0])) # or &b[0]

所以问题实际上是,当您调用 foo 时,并不是整个数组都转换为临时 bytes-对象。转换 from/to char *Cython-documentation 中描述。在你的情况下,调用应该是:

foo(a[:12]) #pass the length explicitly, so cython doesn't have to depend on '[=13=]'

现在打印了以下内容:

83  function
12  function
85  function
31  function
7  function
0  function
91  function
11  function
0  function
12  function
77  function
100  function

cdef-函数的情况不同,其中 char * 保持 char * 并且不会转换为 Python-对象。但是,__cinit__ 必须是一个 def 函数,因此在这种情况下通常使用 cdef 工厂函数,如 the answer pointed out by @DavidW,例如:

cdef class Classical:
    ...
    @staticmethod
    cdef Classical create(char* ptr):
        obj = <Classical>Classical.__new__(Classical) # __init__ isn't called!
        # set up obj while using ptr
        ...
        return obj

显然,Classical.create 只能在 Cython 代码中使用,但另一方面只有 Cython 代码有指针!