Как получить доступ к внутренним данным структуры C, которые имеют массив фиксированного размера из Python? - PullRequest
0 голосов
/ 20 ноября 2018

Я знаю, что это дублирование Написание расширения Cython: как получить доступ к внутренним данным структуры C из Python?

Но я не нашел источников, чтобы иметь дело со структурой C, напримерthis,

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 голосов
/ 20 ноября 2018

Похоже, вы пытаетесь использовать свойство так, как оно не было предназначено для работы.Можно использовать свойства в вашем сценарии, но с точки зрения производительности это может быть нецелесообразно, так как этот ответ будет показан ниже.

В чистом 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 преобразовал массив C int в список (что накладные расходы!), И изменение этой копии массива Addr не изменяет исходный массив приall.

Вам необходимо вернуть (типизированное) представление памяти массива _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 раз быстрее!

...