1

I know it is duplication of Writing Cython extension: how to access a C struct internal data from Python?

But I haven't find any sources to deal with C struct like this,

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

Using __getitem__/__setitem__ method we can access this struct array but I'm interested to do it with @property inside specific class

  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 

Now I got two error,

Special method __get__ has wrong number of arguments (2 declared, 1 expected)

Special method __set__ has wrong number of arguments (3 declared, 2 expected)

please provide suggestion on this

3
  • Your indentation looks wrong. Commented Nov 20, 2018 at 6:05
  • ignore the indentation ... it is just posted code. inside editor, it is matched with perfect indentation Commented Nov 20, 2018 at 6:06
  • I have corrected the indentation in the posted code Commented Nov 20, 2018 at 6:22

1 Answer 1

3

It looks you are trying to use property in a way it wasn't intended to work. It is possible to use properties in your scenario, but from the performance point of view it might not be wise, as this answer will show further below.

In pure Python you could use (as you surely already know) properties as follows to access elements in a list:

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

I.e. property isn't used to access elements of the list but the list itself.

And now

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

The same idea naively translated to Cython would be (your example somewhat simplified):

%%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  

However, this doesn't work as one might expect:

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

a.Addr[0]      # returns 0!

The problem is, that behind the scenes Cython converted the int C-array into a list (what an overhead!) and changing this copy of the Addr-array doesn't change the original array at all.

You need to return a (typed) memory view of array _Addr in the property:

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

Which works as expected:

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

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

You might be worried about overhead of creating a memory view for just one access (and you would be right), in this case it is possible to cache the created memory view and reuse it over and over again:

%%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

leads to a speed-up of factor 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)

This is however still too much overhead, compared to non-fancy and straight forward setting of elements via __setitem__:

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

which leads to

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)

which is about 10 times faster!

Sign up to request clarification or add additional context in comments.

4 Comments

The only problem by using setitem method is that I need to use [] operator around class object like a[0] = 10 and by using property I can use struct member name using . operator like 'a.Addr_fast[0]'. Is it possible to do the same thing by using setitem__/__getitem?
@BhoomilChavda I don't know how you could achieve it with setitem. You could offer set_fieldX(index) function which would be slightly slower than setitem but still faster than property. It up to you how many performance you can spare.
One last question @ead : On which CPU this time you were measure? If it is below corei7 then it will give me more small number while using cached version
@BhoomilChavda it was an i7. Probably not the absolute times are relevant, but the relations between the times.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.