【问题标题】:Using C++'s `str.erase()` from within Cython在 Cython 中使用 C++ `string.erase()`
【发布时间】:2019-06-29 14:52:37
【问题描述】:

我正在 Cython 中实现一个函数,该函数需要在某些时候从 C++ std::string 中删除一些 char。为此,我会使用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 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 优化的更长算法的一部分。

【问题讨论】:

  • 尝试使用 string.replace 而不是 string.erase

标签: python c++ cython


【解决方案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) 运行时间(只需在 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-functions 中):

def cy_func(string b):
    remove_nulls(b)  # no copying
    return b

【讨论】:

  • 您好,只是想知道,您所说的“RVO 不可能”是什么意思? Cython 编译的代码不提供返回优化?
  • @WillianFuks,当您查看为my_fun 生成的 cpp 代码时,您会看到它不使用复制构造函数,而是使用默认构造函数 + 赋值运算符 - 但 RVO 可以省略一个复制构造函数。好的,在这种所有内容都被内联的特殊情况下,编译器可能能够优化所有内容,但总的来说情况并非如此。
  • 谢谢!然后我将调整我的代码以仅使用带有输入引用的函数。似乎是最可靠的方法。
【解决方案2】:

您可以在数组边界后访问。修复它,你的代码就可以工作了。

字符串的长度在erase 之后减少。条件i &lt; s_size 看起来也比i + 1 &lt;= 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'))

【讨论】:

  • 我复制粘贴了你的代码,但它给了我完全相同的错误。
  • @norok2 我已经用你的代码的额外更改更新了我的答案。
  • 还是不行。最重要的一点是它不遵循指定的类型,而是使用bytes()而不是C++的string
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-08
  • 1970-01-01
  • 1970-01-01
  • 2012-07-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多