user1969231
user1969231

Reputation:

Using cython to early type class attributes

I'm writing a python class and I would like to accelerate the execution using cython early typing.
I get the error "Syntax error in C variable declaration" when I try to cython compile the following:

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

The error concerns the syntax of the last line involving self.var1. Am I not allowed to type class attributes directly? Do I always have to break this up into two steps such as,

cdef double var1
self.var1 = var1

The full error traceback is,

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

Upvotes: 11

Views: 8082

Answers (2)

Anton K
Anton K

Reputation: 4798

The answer of @bakuriu is quite good, I'd like to add how this can be done with keeping a memory view as a class member:

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

With that approach do_stuff gets simpler:

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)

Upvotes: 3

Bakuriu
Bakuriu

Reputation: 101969

What you want is to define an extension type. In particular your code should look like:

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

Note that you cannot impose the type of instance attributes in a normal class, because python allow people to change them and their types. If you try to put a cdef at class level in a normal python class you'll receive a compiler error by Cython.


Compiling the above code raises the following error:

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

Now, this is not a syntax error. The syntax is fine. The problem is that you simply cannot have an instance attribute with np.ndarray as type. It is a limitation of cython. In fact if you comment the cdef np.ndarray[double, ndim=1] Redges line the file is compiled correctly:

Code:

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

Output:

$cython test_cython.pyx 
$

Note: no output from cython which means the file was compiled succesfully.

this limitation is explained in the documentation I linked above, at the section Attributes:

Attributes of an extension type are stored directly in the object’s C struct. [omissis]

Note: You can only expose simple C types, such as ints, floats, and strings, for Python access. You can also expose Python-valued attributes.

The fact that you can only expose simple C data-types is because the attributes are members of the struct. Allowing a buffer like an np.ndarray would require to have variable size structs.

If you want an instance attribute of type np.ndarray the best you can do is to define an attribute with a generic type of object and assign the array to it:

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

However now everytime you access self.Redges you lose the speed up of cython. If you access it many times you can assign it to a local variable with the correct type. Here's what I mean:

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]

In this way inside the do_stuff function you can have all the speed of cython using ar.

Upvotes: 12

Related Questions