使用Cython对类属性进行类型定义

11

我正在编写一个Python类,并希望使用Cython提前类型声明来加速执行。

当我尝试对以下内容进行Cython编译时,出现了"C变量声明语法错误"的错误:

import numpy as np
cimport numpy as np

class MyClass:
    def __init__( self, np.ndarray[double, ndim=1] Redges ):
        self.Redges = Redges
        cdef double self.var1

错误涉及最后一行涉及self.var1的语法。我不能直接输入类属性吗?我总是需要将其拆分为两个步骤吗,例如,

cdef double var1
self.var1 = var1

完整的错误回溯如下:

test.pyx:7:24:  
Syntax error in C variable declaration  
Traceback (most recent call last):  
File "setup.py", line 9, in <module>  
        ext_modules = cythonize('test.pyx'), # accepts a glob pattern  
      File "/usr/lib/python2.7/dist-packages/Cython/Build/Dependencies.py", line 713, in cythonize
        cythonize_one(*args[1:])  
      File "/usr/lib/python2.7/dist-packages/Cython/Build/Dependencies.py", line 780, in cythonize_one  
        raise CompileError(None, pyx_file)  
  Cython.Compiler.Errors.CompileError: calc_iliev_sphere.pyx

1
那不是完整的回溯。 - Bakuriu
2个回答

11
你想要定义一个扩展类型。特别是,你的代码应该长这样:
import numpy as np
cimport numpy as np

cdef class MyClass:
    cdef double var1
    cdef np.ndarray[double, ndim=1] Redges

    def __init__( self, np.ndarray[double, ndim=1] Redges ):
        self.Redges = Redges

请注意,在普通的class中,您不能强制实例属性的类型,因为python允许人们更改它们及其类型。如果您尝试在普通的Python类中将cdef放置在类级别,则会收到Cython编译器错误的消息。
编译上述代码会引发以下错误:
Error compiling Cython file:
------------------------------------------------------------                       
...                                                                                
import numpy as np                                                                 
cimport numpy as np                                                                

cdef class MyClass:                                                                
    cdef double var1                                                               
    cdef np.ndarray[double, ndim=1] Redges                                         
                                   ^                                               
------------------------------------------------------------                       

test_cython.pyx:6:36: Buffer types only allowed as function local variables

现在,这不是一个语法错误。语法是正确的。问题在于你不能将实例属性的类型设置为np.ndarray。这是cython的限制。事实上,如果你注释掉cdef np.ndarray [double, ndim=1] Redges这一行,文件就可以编译成功了:
import numpy as np
cimport numpy as np

cdef class MyClass:
    cdef double var1
    #cdef np.ndarray[double, ndim=1] Redges

    def __init__( self, np.ndarray[double, ndim=1] Redges ):
        self.Redges = Redges

输出:

$cython test_cython.pyx 
$

注意:没有来自cython的输出,这意味着文件已经成功编译。
这个限制在我上面链接的文档中有解释,在Attributes部分:
属性是直接存储在对象的C struct中的。[省略]
注意:你只能暴露简单的C类型,比如整数、浮点数和字符串,用于Python访问。你也可以暴露Python值属性。
只能暴露简单的C数据类型是因为这些属性是struct的成员。允许像np.ndarray这样的缓冲区将需要具有可变大小的struct
如果你想要一个类型为np.ndarray的实例属性,最好的办法是定义一个通用类型为object的属性,并将数组分配给它。
import numpy as np
cimport numpy as np

cdef class MyClass:
    cdef double var1
    cdef object Redges

    def __init__( self, np.ndarray[double, ndim=1] Redges ):
        self.Redges = Redges

然而,每次访问self.Redges都会失去Cython的加速。如果您需要多次访问它,可以将其分配给一个具有正确类型的局部变量。 以下是我的意思:
import numpy as np
cimport numpy as np

cdef class MyClass:
    cdef double var1
    cdef object Redges

    def __init__( self, np.ndarray[double, ndim=1] Redges ):
        self.Redges = Redges

    def do_stuff(self):
        cdef np.ndarray[double, ndim=1] ar
        ar = self.Redges
        ar[0] += 1
        return ar[0]

通过在 do_stuff 函数内使用 ar,您可以获得 Cython 的所有速度优势。


那个解决方案产生了另一个错误消息,我已经将其添加到帖子中。 - user1969231
4
值得注意的是,memoryview 语法(double[:])不需要在 object 和其实际类型之间进行转换,因此速度应该更快。 - Veedrac
1
这是一个令人困惑的答案,它与数组具有可变长度没有任何关系。我可以拥有自己的cpp类(在堆栈上创建),但不能拥有缓冲区,但这不是原因,虽然我不知道是什么原因 :) - dashesy
我还没有收到有关Cython问题的编译错误的回复。也许你能帮忙。 - ballade4op52
引用的Cython参考似乎来自于旧版Pyrex文档:“请注意,您只能公开简单的C类型,例如整数、浮点数和字符串,以供Python访问。您还可以公开Python值属性,尽管只有通用Python属性(类型为object)才能进行读写公开。如果属性声明为扩展类型,则必须将其公开为只读。” - Yibo Yang
@YiboYang 这个答案已经有4年的历史了。虽然链接网站的内容可能已经发生了变化,但它是Cython文档,而不是Pyrex文档。当相关时,Cython可能会复制并粘贴原始的Pyrex文档的部分内容。 - Bakuriu

3
@bakuriu的答案已经很好了,我想补充一下如何将其作为内存视图保留在类成员中并完成此操作:
import numpy as np
cimport numpy as np

cdef class MyClass:
    cdef public double var1
    cdef public np.float64_t[:] Redges

    def __init__( self, np.ndarray[double, ndim=1] Redges ):
        self.Redges = Redges

采用这种方法,do_stuff将变得更简单:

def do_stuff(self):
    # With using buffer protocol, this just wraps the memory view
    # with numpy object without copying data
    np_redges = np.asarray(self.Redges)

    # Now you have np_redges, a numpy object. Even though, it's not a pure 
    # C array, it allows calling numpy functions with all the power of MKL, e.g.:
    np.add(np_redges, 1.0, np_redges)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接