如何从Python访问具有固定大小数组的C结构内部数据?

时间:2018-11-20 06:02:04

标签: python cython

我知道这是重复的 Writing Cython extension: how to access a C struct internal data from Python?

但是我没有找到任何可以处理C结构的资源,

ctypedef struct IoRegAccess:        
    int Addr[26]             
    int Data[26]             
    int AddrLen              
    int DataLen

使用__getitem__ / __setitem__方法,我们可以访问此struct数组,但我有兴趣在特定类中使用@property进行操作

  cdef class PyIoRegAccess:
      cdef IoRegAccess RegContainer

      #ctor of this class
      def __cinit__(self):
          memset(self.RegContainer.uiAddr, 0,sizeof(uint32_t)*26)
          memset(self.RegContainer.uiData, 0,sizeof(uint32_t)*26)


      @property                                 
      def uiAddr(self,key):                         
          return self.RegContainer.uiAddr[key]       

      @uiAddr.setter                            
      def uiAddr(self, key, value):             
          self.RegContainer.uiAddr[key] = value 

现在我遇到两个错误,

特殊方法__get__的参数数量错误(已声明2个,预期1个)

特殊方法__set__的参数数量错误(已声明3个,预期2个)

请对此提供建议

1 个答案:

答案 0 :(得分:2)

您似乎正在尝试以某种不希望使用的方式来使用属性。可以在您的方案中使用属性,但是从性能的角度来看,这可能并不明智,因为此答案将在下面进一步显示。

在纯Python中,您可以使用(您肯定已经知道)以下属性来访问列表中的元素:

def class A:
   def __init__(self):
       self._lst=[1,2,3,4]
   @property
   def lst(self):
       return self._lst

即属性不用于访问列表的元素,而是用于列表本身。

现在

a=A()
a.lst          # accesses list via property
a.lst[0] = 10  # accesses list via property + __getitem__ of the list
               # no property-setter needed, we don't set lst-property, 
               # just an element of the list

a.lst[0]       # returns 10

将同一个想法天真转换为Cython(您的示例有些简化):

%%cython
from libc.string cimport memset

cdef class CyA:
      cdef int _Addr[26]

      def __cinit__(self):
          memset(self._Addr, 0,sizeof(int)*26)

      @property                                 
      def Addr(self): 
          return self._Addr  

但是,这不符合人们的预期:

a=CyA()
a.Addr[0] = 10 

a.Addr[0]      # returns 0!

问题在于,Cython在后台将int C数组转换为列表(这是一个开销!),并且更改此Addr数组的副本不会更改原始数组数组。

您需要在属性中返回数组_Addr的(类型化)内存视图:

%%cython
....
      @property                                 
      def Addr(self): 
          cdef int[:] memview = self._Addr
          return memview

可按预期工作:

a=CyA()
a.Addr[0] = 10 

a.Addr[0]      # returns 10, as expected!

您可能会担心只为一个访问创建一个内存视图的开销(这是正确的),在这种情况下,可以缓存创建的内存视图并反复使用:

%%cython

cdef class CyA:
      cdef int _Addr[26]
      cdef int[:] memview_cache
      def __cinit__(self):
          memset(self._Addr, 1204,sizeof(int)*26)
          self.memview_cache = None

  ...
  @property                                 
  def Addr_fast(self): 
      if self.memview_cache is None:
          self.memview_cache = self._Addr
      return self.memview_cache

导致因子3加速:

a=CyA()
%timeit a.Addr               # 1.05 µs ± 36.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit a.Addr_fast          # 328 ns ± 13.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

然而,与通过__setitem__通过元素进行非花哨且直接的设置相比,这仍然是过多的开销:

%%cython
cdef class CyA:
      cdef int _Addr[26]
      ...
      def __setitem__(self, index, int value):
          self._Addr[index] = value

这导致

a=CyA()
%timeit a.Addr_fast[0] = 10          # 483 ns ± 22.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit a[0] = 10                    # 32.5 ns ± 0.669 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

快10倍!