如何在 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++ 类?的主要内容,如果未能解决你的问题,请参考以下文章

json.dumps()包装中文字符串

如何在 Python 中动态指定 unicode 字符串?

python中如何打印或保存unicode编码内容成中文?

如何使用 ctypes 在 python 中正确包装 C API?

使用 Python 发送 HTML 电子邮件

如何在 Python 2 上使用 psutil.Popen 和 unicode 命令