【问题标题】:convert a pointer to an instance of a class in cython将指针转换为 cython 中类的实例
【发布时间】:2015-04-13 11:58:14
【问题描述】:

我在 cython 中编写了一个类,并且我有一个方法可以检查类对象是否没有属性。如果文件存在,它会从文件中读取此对象,或者它会计算数组或内存视图的值并将结果保存在文件中。以下是我的部分代码:

from __future__ import division
import numpy as np
cimport numpy as np
cimport cython
from libc.stdio cimport FILE, fopen, fwrite, fscanf, fclose, stdout, stderr

cdef extern from "math.h":
    double exp(double) nogil
    double log(double) nogil

cdef class NFW(object):
    cdef object ks, source_redshift
    cdef const char* path
    def __cinit__(self, char* path, double[::1] zs=None, *args):
        self.path=path
        if self.path==NULL:
           raise ValueError("Could not find a path to the file which contains the table of  diameter distances")
        if zs is None:
           raise ValueError("You must give an array !")
        self.source_redshift=zs

    @cython.cdivision(True)    
    @cython.boundscheck(False)
    @cython.wraparound(False)
    cdef void get_ks(self):
         cdef FILE* handle
         cdef Py_ssize_t i, nz
         nz  = len(self.source_redshift)
         cdef double* array[nz] 
         if not hasattr(self, 'ks'):  # does self.ks not exist?
            try:
               ## first, check for existing file, see if we can load in self.ks
               handle = fopen(self.path, "r")
               if handle == NULL:
                  raise ValueError("cannot open file {}".format(self.path))

               for i from nz > i >= 0:
                   fscanf(handle,"%f",&array[i])
               fclose(handle)
               self.ks= &array
            except IOError:

               self.ks = self.calculate_ks() 

    @cython.cdivision(True)    
    @cython.boundscheck(False)
    @cython.wraparound(False)
    cdef double[::1] calculate_ks(self):
         cdef Py_ssize_t i, nz
         nz  = len(self.source_redshift)
         cdef double[::1] k_s = np.zeros(nz,dtype=np.float64_t)

         for i from nz > i >= 0:         
             k_s[i]= log((1.+self.source_redshift[i])/(1.-self.source_redshift[i]))
         #write the calculated k_s in a file
         cdef FILE* handle=<FILE *>fopen(self.path,"wb")
         fwrite(k_s,sizeof(k_s),1,handle)
         fclose(handle)
         return k_s

我对 c 不是很熟悉,我是一个 cython 初学者。我收到以下错误消息,我不知道如何在指针数组中读取 cython 中的文件并将其转换为类的实例的最佳方法。我应该强调我正在寻找在 cython 中执行文件读取的最快方法。

Error compiling Cython file:
------------------------------------------------------------
...
    @cython.wraparound(False)
    cdef void get_ks(self):
         cdef FILE* handle
         cdef Py_ssize_t i, nz
         nz  = len(self.source_redshift)
         cdef double* array[nz] 
                             ^
------------------------------------------------------------

WLUtilities.pyx:413:30: Not allowed in a constant expression

Error compiling Cython file:
------------------------------------------------------------
...
                  raise ValueError("cannot open file {}".format(self.path))

               for i from nz > i >= 0:
                   fscanf(handle,"%f",&array[i])
               fclose(handle)
               self.ks= &array
                       ^
------------------------------------------------------------

WLUtilities.pyx:424:24: Cannot convert 'double *(*)[__pyx_v_nz]' to Python object

Error compiling Cython file:
------------------------------------------------------------
...
         k_s = np.zeros(nz,dtype=np.float64_t)
         for i from nz > i >= 0:         
             k_s[i]= self.__ks(self.source_redshift[i])
         #write the calculated k_s in a file
         cdef FILE* handle=<FILE *>fopen(self.path,"wb")
         fwrite(k_s,sizeof(k_s),1,handle)
                  ^
------------------------------------------------------------


WLUtilities.pyx:440:19: Cannot convert Python object to 'const void *'

【问题讨论】:

    标签: c arrays class pointers cython


    【解决方案1】:

    (如果这段代码有一些小错误并且不能完全正常工作,我深表歉意 - 我还没有实际测试过。但是原理应该可以正常工作。)

    您最简单的事情不是使用双精度数组,而是使用 MemoryView。访问元素的开销非常低(并不比指针访问差很多,并且不太可能成为执行 IO 时的限制因素)。使用 MemoryView,您可以使用所有标准的 Python 方法在运行时轻松分配内存。

    代码如下,有一些注释作为cmets

    # omitted "cdivision" - you aren't actually doing any division so it doesn't matter
    @cython.boundscheck(False)
    @cython.wraparound(False)
    cdef void get_ks(self):
         cdef FILE* handle
         cdef Py_ssize_t i, nz
         nz  = len(self.source_redshift)
         # Memory is managed by numpy. Alternatively you can use the python array module
         # The "1" in "::1" promises that it's genuinely continuous (I think) and so should speed things up
         cdef double[::1] array = np.empty((nz,))
         if not hasattr(self, 'ks'):  # does self.ks not exist?
            try:
               ## first, check for existing file, see if we can load in self.ks
               handle = fopen(self.path, "r")
               if handle == NULL:
                  raise ValueError("cannot open file {}".format(self.path))
    
               # I've changed this to "range" because it's a little more iomatic
               # change it back if you like! It shouldn't make much difference
               for i in range(nz-1,-1,-1):
                   # &array[i] should still work with a memoryview
                   # Changed to 'lf' since we're using doubles
                   fscanf(handle,"%lf",&array[i])
               fclose(handle) # does this want to be in "finally" to ensure it gets done?
               self.ks= array # no need to take address - this should just work
            except IOError:
    
               self.ks = self.calculate_ks() 
    

    “calculate_ks”中的错误大概可以这样处理

    cdef double* array_as_doubles = &ks[0]
    fwrite(<void*>array_as_doubles, # cast to void
           sizeof(double),len(ks), # calculate length properly! 
           handle) 
    

    最后一点——如果你真的想自己管理内存,你可以使用 C malloc/free 函数给你一个双数组(参见http://docs.cython.org/src/tutorial/memory_allocation.html)。但是,您完全有责任确保它在适当的时间被释放,而您可能不想这样做。

    【讨论】:

      【解决方案2】:

      这里:

               nz  = len(self.source_redshift)
               cdef double* array[nz] 
                                   ^
      ------------------------------------------------------------
      
      WLUtilities.pyx:413:30: Not allowed in a constant expression
      

      由于您在 python 对象上调用 len,因此不清楚 nz 在编译时是否会被知道,这就是 Cython 在这里抱怨的原因。 比如:

           cdef double* array[5] 
      

      会编译(当然这对你来说毫无用处,但关键是你必须在编译时知道 nz,也许使用 c 函数来获取大小)。 这里:

                    self.ks= &array
                             ^
      ------------------------------------------------------------
      
      WLUtilities.pyx:424:24: Cannot convert 'double *(*)[__pyx_v_nz]' to Python object
      

      您不能将地址分配给 Python 对象。这样的事情会取消引用地址并将其内容转换为 cython 能够处理的内容:

             self.ks= np.array(<double[:5]> array[0])
      

      (同样,5 应该被你的 nz 替换,它必须在恒定时间知道)。您可能想将其转换为其他东西,关键是在将其放入 Python 对象时,您必须将其转换为不是地址的东西。

      这里:

               fwrite(k_s,sizeof(k_s),1,handle)
                        ^
      ------------------------------------------------------------
      
      
      WLUtilities.pyx:440:19: Cannot convert Python object to 'const void *'
      

      你应该把你的记忆视图转换成一个 void ponter(语法可能很烦人,但它是 cython 使用的):

       fwrite(<void*> &k_s[0],sizeof(k_s),1,handle)
      

      如果您将代码中的行替换为上面的这些行,它将编译。再说一次,你必须在编译时让 nz 被知道(你可以在代码中使用“5”来进行测试)。并且有几种使用 cython 进行投射的方法,这些是我想到的一些方法,但您可能会找到更好的方法。

      【讨论】:

      • 看来问题是我不能用**动态大小**声明一个数组但是由于我不知道nzpriory的值,恐怕你已经解释过了不可能是好的解决方案。
      • 这不是唯一的问题 - 你有一些铸造问题。为了解决他们,我继续使用任意 nz。但是对于静态知道 nz,您可以定义一个 c 函数来获取数组大小,然后 cimporting 它。比如常见的sizeof(myArray) / sizeof(myArray[0])
      • 是否可以使用动态内存分配?我的C知识非常有限。
      • 也许你最好的方法是从 C++ 导入向量库(向下滚动,他们会显示一个小包装:docs.cython.org/src/userguide/wrapping_CPlusPlus.html)。它是一个模板,所以起初获取语法可能有点烦人(如果你不是来自 C++),但优点是它感觉更像 Python(动态调整大小,你有一个 .size() 方法...... )。如果你不想使用库,总是有新的和删除的
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-01
      • 1970-01-01
      • 2021-08-03
      • 1970-01-01
      • 2021-09-20
      相关资源
      最近更新 更多