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
.
的任何函数或方法也会发生此行为
但是,当数组被传递到任何 cdef
或 cpdef
函数或方法时,数组的值是正确的。
所以,我有两个问题(请注意,我使用的是 .pyx 转轮文件):
- 我是否错误地将数组传递给了
__cinit__()
方法?还有其他方法吗?
- 或者,是否有替代
def __cinit__()
方法的 Cythonic 方法?当然,我可以使用变通方法并使用 cdef
或 cpdef
方法,尤其是对于我展示的这个简单的小示例,但我想了解是否有不同的方法......
代码:
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]
是未定义的行为,可能以分段错误结束。
您可以通过
验证a
和b
指向不同的地址
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 代码有指针!
全部。我在下面有这个 Cython 代码示例,其中我有一个无符号字符数组,a
填充了无符号整数。当我将这个数组传入 Python def
方法时,包含 0 的索引之后的任何索引的值都会变得混乱。
在此示例中,由于 0 的值位于第 6 个索引处,因此传递到 __cinit__()
方法的数组中的所有后续数组索引都具有不正确的值。 __init__()
方法或使用 Python 声明 def
.
但是,当数组被传递到任何 cdef
或 cpdef
函数或方法时,数组的值是正确的。
所以,我有两个问题(请注意,我使用的是 .pyx 转轮文件):
- 我是否错误地将数组传递给了
__cinit__()
方法?还有其他方法吗? - 或者,是否有替代
def __cinit__()
方法的 Cythonic 方法?当然,我可以使用变通方法并使用cdef
或cpdef
方法,尤其是对于我展示的这个简单的小示例,但我想了解是否有不同的方法......
代码:
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]
是未定义的行为,可能以分段错误结束。
您可以通过
验证a
和b
指向不同的地址
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 代码有指针!