Cython: have sequence of Extension Types as attribute of another Extension Type with access to cdef methods

半世苍凉 提交于 2019-12-23 16:53:33

问题


Suppose I defined the following Cython class

cdef class Kernel:
    cdef readonly double a

    def __init__(self, double a):
        self.a = a

    cdef public double GetValue(self, double t):
        return self.a*t 

Now I'd like to define another Extension Type that has a sequence of Kernels as attribute. Something like:

cdef class Model:
    cdef readonly Kernel[:] kernels
    cdef unsigned int n_kernels

    def __init__(self, Kernel[:] ker):
        self.kernels = ker
        self.n_kernels = ker.shape[0]

    cdef double Run(self, double t):

        cdef int i
        cdef double out=0.0

        for i in range(self.n_kernels):
            out += self.kernels[i].GetValue(t)

        return out

However, this doesn't work. First I need to substitute Kernel[:]with object[:], otherwise i get the following error from gcc

‘PyObject’ has no member named ‘__pyx_vtab’

If I use object[:] everything compiles fine but I get an error when attempting to access GetValuemethod:

AttributeError: "AttributeError: "'cytest.Kernel' object has no attribute 'GetValue'" in 'cytest.Model.Run'

What I would like

  1. Access the cdef methods of Kernel from the cdef method Run, without Python overhead.
  2. Type-check the elements of kernels.

My current workaround

At the moment I use the following solution which however doesn't satisfy the requirements above:

cdef class Kernel:
    cdef readonly double a

    def __init__(self, double a):
        self.a = a

    cpdef public double GetValue(self, double t):
        return self.a*t 

cdef class Model:
    cdef readonly object[:] kernels
    cdef unsigned int n_kernels

    def __init__(self, object[:] ker):
        self.kernels = ker
        self.n_kernels = ker.shape[0]

    def Run(self, double t):

        cdef int i
        cdef double out=0.0

        for i in range(self.n_kernels):
            out += self.kernels[i].GetValue(t)

        return out

i.e. I declare the methods of the Kernel class as cpdef so that they can be accessed to from Python and use object[:].

Question

Is there a way to achieve points 1 and 2 above in Cython without Python overhead?

Thanks in advance for your time.

NB: I don't know the length of the sequence in advance.

Edit

Following @DavidW advice I modified the code as follows

# module cytest
import cython

cdef class Kernel:
    cdef readonly double a

    def __init__(self, double a ):
        self.a = a

    cdef public double GetValue(self, double t):
        return self.a*t


cdef class Model:
    cdef readonly Kernel[:] kernels
    ### added this attribute 
    cdef Kernel k 
    cdef unsigned int n_kernels

    def __cinit__(self, Kernel[:] ker):
        self.kernels = ker
        self.n_kernels = ker.shape[0]

    cpdef double Run(self, double t):

        cdef int i
        cdef double out=0.0

        for i in range(self.n_kernels):
            # now i assign to the new attribute each time 
            # and access the cdef method from it
            self.k = self.kernels[i]
            out += self.k.GetValue(t)

        return out

Now it compiles and runs fine (and faster than my previous workaround), even if I still have some python overhead when accessing the Kernel[:] attribute.

I put here an example of building and calling a Model

import cytest
import numpy as np

ker_list = [cytest.Kernel(i*1.0) for i in range(3)]

# transform it to a numpy array
# to be able to pass it to the 'Model' constructor
ker_arr = np.array(ker_list)

# create a model instance
model = cytest.Model(ker_arr)

# call the method Run
print model.Run(1.0)

来源:https://stackoverflow.com/questions/31119510/cython-have-sequence-of-extension-types-as-attribute-of-another-extension-type

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!