Использование C ++ `str.erase ()` изнутри Cython - PullRequest
1 голос
/ 29 июня 2019

Я реализую функцию в Cython, которая требует, чтобы в какой-то момент удалить некоторые char из C ++ std::string. Для этого я бы использовал std::string::erase(). Однако, когда я пытаюсь использовать его, Cython заставляет объект быть bytes() вместо std::string(), и в этот момент он не может найти .erase().

Чтобы проиллюстрировать проблему, вот минимальный пример (с использованием IPython + Cython magic):

%load_ext Cython
%%cython --cplus -c-O3 -c-march=native -a


from libcpp.string cimport string


cdef string my_func(string s):
    cdef char c = b'\0'
    cdef size_t s_size = s.length()
    cdef size_t i = 0
    while i + 1 <= s_size:
        if s[i] == c:
            s.erase(i, 1)
        i += 1
    return s


def cy_func(string b):
    return my_func(b)

Это компилируется, но указывает на взаимодействие Python в строке .remove(), и когда я пытаюсь его использовать, например,

b = b'ciao\0pippo\0'
print(b)
cy_func(b)

Я получаю:

AttributeError Traceback (последний вызов был последним) AttributeError: у объекта 'bytes' нет атрибута 'erase'

Исключение игнорируется в: '_cython_magic_5beaeb4004c3afc6d85b9b158c654cb6.my_func' AttributeError: у объекта 'bytes' нет атрибута 'erase'

Как я мог решить это?

Примечания

  1. Если я заменим s.erase(i, 1) на, скажем, s[i] == 10, я получу my_func() без взаимодействия с Python (может даже использовать директиву nogil).
  2. Я знаю, что мог бы сделать это в Python с .replace(b'\0', b''), но это часть более длинного алгоритма, который я надеюсь оптимизировать с помощью Cython.

Ответы [ 2 ]

1 голос
/ 29 июня 2019

Я не знаю, почему Cython создает код, который он производит - даже erase в string.pxd нет, поэтому Cython должен выдавать ошибку.

Самый простойВ качестве обходного пути можно было бы ввести функцию erase, которая обертывает std::string::erase:

cdef extern from *:
    """
    #include <string>
    std::string &erase(std::string& s, size_t pos, size_t len){
        return s.erase(pos, len);
    }
    """
    string& erase(string& s, size_t pos, size_t len)

# replace  s.erase(i,1) -> erase(s,i,1)

Однако в C ++ стирание нулей не выполняется: это ошибка (см. @MS answer для исправления) и имеет O(n^2) время выполнения (просто попробуйте его на b"\x00"*10**6), правильный способ - использовать remove / erase-idiom :

%%cython --cplus
from libcpp.string cimport string

cdef extern from *:
    """
    #include <string>
    #include <algorithm>
    void remove_nulls(std::string& s){
       s.erase(std::remove(s.begin(), s.end(), 0), s.end());
    }
    """
    void remove_nulls(string& s)


cdef string my_func(string s):
    remove_nulls(s)
    return s

, который трудно использовать неправильно и является O(n).


Еще одно замечание, касающееся передачи `std :: string 'для значения.Подпись:

cdef string my_func(string s)
     ...
     return s

означает, что есть две (ненужные) копии (с невозможностью RVO), может быть лучше избежать и передать s по ссылке (по крайней мере, в cdef -функциях):

def cy_func(string b):
    remove_nulls(b)  # no copying
    return b
1 голос
/ 29 июня 2019

Вы получаете доступ после границ массива.Исправьте это, и ваш код будет работать.

Длина строки уменьшается после erase.Также условие i < s_size выглядит лучше, чем i + 1 <= s_size.Наконец, i нельзя увеличивать после erase, к этому индексу приходит новый символ.

while i < s_size:
    if s[i] == c:
        s.erase(i, 1)
        s_size -= 1
    else:
        i += 1

b ниже - байтовый массив.Попробуйте позвонить .decode, чтобы преобразовать его в строку.

b = b'ciao\0pippo\0'
print(b)
cy_func(b.decode('ASCII'))
...