如何在 python 包装中使用 unicode 字符串用于带有 cython 的 c++ 类?
Posted
技术标签:
【中文标题】如何在 python 包装中使用 unicode 字符串用于带有 cython 的 c++ 类?【英文标题】:How to use unicode strings in python wrapping for c++ class with cython? 【发布时间】:2019-08-07 19:23:22 【问题描述】:我目前正在从事一个宠物项目。我现在的目标是用 cython 为 python 编写一个 c++ 类的包装器。问题是我必须使用俄语文本(unicode),但是 cython 包装只需要字节,尽管 c++ 类方法能够正确处理 unicode 字符串。我阅读了 Cython 文档并试图在 google 中找到它,但一无所获。
如何更改我的代码,以便我的 python 包装器可以采用 unicode 字符串?
这是我的 github 存储库的链接,其中包含当前代码文件 https://github.com/rproskuryakov/lemmatizer/tree/trie
“trie.pxd”
from libcpp.string cimport string
from libcpp cimport bool
cdef extern from "Trie.cpp":
pass
# Declare the class with cdef
cdef extern from "Trie.h":
cdef cppclass Trie:
Trie() except +
void add_word(string word) # function that should take unicode
bool find(string word) # function that should take unicode
“pytrie.pyx”
from trie cimport Trie # link to according .pxd file
# Create a Cython extension type which holds a C++ instance
# as an attribute and create a bunch of forwarding methods
# Python extension type.
cdef class PyTrie:
cdef Trie c_tree # Hold a C++ instance which we're wrapping
def __cinit__(self):
self.c_tree = Trie()
def add_word(self, word):
return self.c_tree.add_word(word)
def find(self, word):
return self.c_tree.find(word)
这是我在 python 中得到的。
>>> tree.add_word(b'hello') # works if i got english into ascii
>>> tree.add_word(b'привет') # doesnt work
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "wrapper/pytrie.pyx", line 13, in pytrie.PyTrie.add_word
File "stringsource", line 15, in string.from_py.__pyx_convert_string_from_py_std__in_string
TypeError: expected bytes, str found
【问题讨论】:
我很惊讶有b'привет'
这样的东西,我认为字节只包含 ASCII 文字字符......
没错,'привет' 不是 ascii,函数不能接受,但我需要。
当我在 Python 解释器中输入 a=b'привет'
时出现错误,因为字节只能包含 ASCII 文字(Python3.7),我不确定您使用的是哪个 Python 版本,即它对你有用(u'привет'
会工作,但这不是你正在做的)。
也许你应该补充一点,你还在使用 Python2,它现在不再是“默认”了。
我使用python 3.6,而tree.add_word('hello') 或tree.add_word('привет')、tree.add_word(u'привет') 返回同样的错误。
【参考方案1】:
C++ 字符串在内部是一个char
数组,因此实际上是在“字节”级别而不是Unicode 级别上操作的。因此 Cython 不会自动支持 unicode/str
std::string
转换。但是,您有两个相当简单的选择:
使用 unicode/str.encode
函数获取 unicode 对象的字节表示:
def add_word(self, word):
if isinstance(word,str): # Python3 version - use unicode for Python 2
word = word.encode()
return self.c_tree.add_word(word)
您需要注意的主要一点是,C++ 用来解释它的编码与 Python 用来编码它的编码相同(Python 默认使用 utf8)。
转换为 C++ 类型 std::wstring
- 内部为 wchar_t
数组。不幸的是 Cython 默认不包装 wstring
或提供自动转换,因此您需要编写自己的包装器。使用Cython wrapping of std::string
作为参考——你可能只需要包装构造函数。我使用the Python C API 转换为wchar_t*
。
from libc.stddef cimport wchar_t
cdef extern from "<string>" namespace std:
cdef cppclass wstring:
wstring() except +
wstring(size_t, wchar_t) except +
const wchar_T* data()
cdef extern from "Python.h":
# again, not wrapped by cython a s adefault
Py_ssize_t PyUnicode_AsWideChar(object o, wchar_t *w, Py_ssize_t size) except -1
# conversion function
cdef wstring to_wstring(s):
# create 0-filled output
cdef wstring out = wstring(len(s),0)
PyUnicode_AsWideChar(s, <wchar_t*>out.data(),len(s)) # note cast to remove const
# I'm not convinced this is 100% acceptable according the standard but practically it should work
return out
您更喜欢哪些选项很大程度上取决于您的 C++ 接受的 unicode 字符串。
【讨论】:
以上是关于如何在 python 包装中使用 unicode 字符串用于带有 cython 的 c++ 类?的主要内容,如果未能解决你的问题,请参考以下文章