Cython присваивание значений для libcpp.list - PullRequest
1 голос
/ 01 июля 2019

Есть ли способ сделать разыменование перед присваиванием в Cython, например, для выполнения присваивания значений в списке C ++ на месте?

Документация Cython ( здесь ) говорит, что выследует использовать dereference(foo) для создания кода C ++, например *(foo), но этот синтаксис нельзя использовать для назначения (например, *(foo) = *(foo) + 1.

. Ниже приведен пример назначения (в функции inplace_normalize), которыйЯ не мог реализовать с помощью функции разыменования Cython.

from libcpp.list cimport list as cpplist
from cython.operator import dereference as deref, preincrement as inc

cpdef cpplist[double] normalize(cpplist[double] l):
    cdef double norm
    cdef cpplist[double] l2

    for v in l:
        norm += v

    for v in l:
        l2.push_back(v/norm)

    return l2

# inplace version of normalize  
cpdef void inplace_normalize(cpplist[double] l):
    cdef double norm
    for v in l:
        norm += v

    cdef cpplist[double].iterator it = l.begin()
    while it != l.end():
        # deref(it) = deref(it)/norm  #== *it = *it / norm
        print(deref(it)/norm)
        inc(it)

Если я пытаюсь использовать deref(it) = deref(it)/norm, я получаю ошибку Cannot assign to or delete this. Каково решение для присвоения значений на месте в libcpp.list используя Cython?

Ответы [ 2 ]

3 голосов
/ 01 июля 2019

С указателем вы можете использовать pt[0] вместо (*pt), потому что указатели и массивы тесно связаны в C. Однако тот же прием не совсем работает с итераторами C ++.Поэтому нам нужно получить адрес предмета.Грубый (непроверенный) контур:

cdef void inplace_normalize(cpplist[double]& l):
    cdef double norm
    cdef double* address
    for v in l:
        norm += v

    cdef cpplist[double].iterator it = l.begin()
    while it != l.end():
        address = &deref(it)
        address[0] = address[0]/norm  #== *it = *it / norm
        print(deref(it)/norm)
        inc(it)

Несколько побочных моментов:

  • C ++ list - это очень редко то, что вы хотите - вы, вероятно,вместо этого я хочу C ++ vector, что ближе к Python list и обычно более эффективно.

  • Я изменил функцию с cpdef на cdef.Если вы попытаетесь вызвать его из Python, он сначала скопирует итеративный Python в список C ++, затем нормализует содержимое этого списка, но сделанные вами изменения никогда не будут распространены обратно в итерируемый.Нет никакого смысла в том, чтобы это вызывалось из Python (поскольку вы не можете иметь cpplist объектов непосредственно в Python), поэтому лучше заблокировать это.

  • У меня естьизменил его с передачи по значению на передачу по ссылке.Не имеет смысла изменять локальную копию списка.

1 голос
/ 02 июля 2019

@ Решение DavidW - это то, что вы просили, однако я бы предложил немного другой подход при кодировании C ++ в Cython.

В Cython C ++ всегда чувствовал себя гражданином второго сорта (просто посмотрите на этоdereference-mess или отсутствующая функциональность в libcpp-wrappers!) и, поскольку "verbatim-C-code" (т.е. начиная с Cython-0.28) нет веских причин для этой пытки - просто вставьте дословно в код на c ++и иметь в своем распоряжении все c ++ - функциональность:

# make sure c++11 is enabled
%%cython --cplus

from libcpp.list cimport list as cpplist

cdef extern from *:
    """
    #include <list>
    #include <algorithm>
    #include <numeric>
    void normalize_inplace_cpp(std::list<double>& lst){
        double norm = std::accumulate(lst.cbegin(), lst.cend(), 0.0)/lst.size();
        std::for_each(lst.begin(), lst.end(), [norm](double &val){
           val/=norm;
        });
    }
    """
    void normalize_inplace_cpp(cpplist[double]& lst)

# for testing
def normalized_list(cpplist[double] lst):
    normalize_inplace_cpp(lst)
    return lst

А теперь:

>>> normalized_list([1,2,3])
# [0.5, 1.0, 1.5]
...