在Cython内部使用C ++的`str.erase()`

时间:2019-06-29 14:52:37

标签: python c++ cython

我正在用Cython实现一个功能,该功能要求在某个时候从C ++ char中删除一些std::string。为此,我将使用std::string::erase()。但是,当我尝试使用它时,Cython强制将对象设为bytes()而不是std::string(),这时它找不到.erase()

为说明问题,这是一个最小的示例(使用IPython + Cython魔术):

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

这可以编译,但是它指示.remove()行上的Python交互,例如当我尝试使用它时,例如

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

我得到:

  

AttributeError跟踪(最近一次通话)   AttributeError:“ bytes”对象没有属性“ erase”

     

忽略异常:“ _ cython_magic_5beaeb4004c3afc6d85b9b158c654cb6.my_func”   AttributeError:“ bytes”对象没有属性“ erase”

我该如何解决?

注释

  1. 如果我将s.erase(i, 1)替换为s[i] == 10,则我得到my_func()且没有Python交互(甚至可以使用nogil指令)。
  2. 我知道我可以使用.replace(b'\0', b'')在Python中实现此功能,但这是我希望使用Cython优化的更长算法的一部分。

2 个答案:

答案 0 :(得分:1)

您可以在数组边界之后访问。修复它,您的代码将正常工作。

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'))

答案 1 :(得分:1)

我不知道Cython为什么产生它正在产生的代码-string.pxd中甚至没有erase,所以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)运行时间(只需在{{ 1}}),正确的方法是使用remove/erase-idiom

b"\x00"*10**6

很难被滥用的%%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


再说一遍,涉及到每个值传递`std :: string'。签名:

O(n)

意味着,有两个(不必要的)副本(不可能使用RVO),最好避免并通过引用传递cdef string my_func(string s) ... return s (至少在s函数中):

cdef