使用 Python C 扩展时如何修复 'UnicodeDecodeError: 'utf-8' codec can't decode byte'?

Posted

技术标签:

【中文标题】使用 Python C 扩展时如何修复 \'UnicodeDecodeError: \'utf-8\' codec can\'t decode byte\'?【英文标题】:How to fix 'UnicodeDecodeError: 'utf-8' codec can't decode byte' when using Python C Extensions?使用 Python C 扩展时如何修复 'UnicodeDecodeError: 'utf-8' codec can't decode byte'? 【发布时间】:2019-05-19 05:14:55 【问题描述】:

给定以下文件bug.txt

event "øat" not handled

我在文件fastfilewrapper.cpp上写了以下Python C扩展

#include <Python.h>
#include <cstdio>
#include <iostream>
#include <sstream>
#include <fstream>

static PyObject* hello_world(PyObject *self, PyObject *args) 
    printf("Hello, world!\n");
    std::string retval;
    std::ifstream fileifstream;

    fileifstream.open("./bug.txt");
    std::getline( fileifstream, retval );
    fileifstream.close();
    std::cout << "retval " << retval << std::endl;
    return Py_BuildValue( "s", retval.c_str() );


static PyMethodDef hello_methods[] =  
        "hello_world", hello_world, METH_NOARGS,
        "Print 'hello world' from a method defined in a C extension."
    ,
    NULL, NULL, 0, NULL
;

static struct PyModuleDef hello_definition = 
    PyModuleDef_HEAD_INIT,
    "hello", "A Python module that prints 'hello world' from C code.",
    -1, hello_methods
;

PyMODINIT_FUNC PyInit_fastfilepackage(void) 
    Py_Initialize();
    return PyModule_Create(&hello_definition);

我用pip3 install . 用这个setup.py 构建了它

from distutils.core import setup, Extension

# https://bugs.python.org/issue35893
from distutils.command import build_ext

def get_export_symbols(self, ext):
    parts = ext.name.split(".")
    if parts[-1] == "__init__":
        initfunc_name = "PyInit_" + parts[-2]
    else:
        initfunc_name = "PyInit_" + parts[-1]

build_ext.build_ext.get_export_symbols = get_export_symbols

setup(name='fastfilepackage', version='1.0',  \
      ext_modules=[Extension('fastfilepackage', ['fastfilewrapper.cpp'])])

然后,我使用这个test.py 脚本:

import fastfilepackage

iterable = fastfilepackage.hello_world()
print('iterable', iterable)

但是当我运行test.py Python 脚本时,Python 会抛出这个异常:

$ PYTHONIOENCODING=utf8 python3 test.py
Hello, world!
retval event "▒at" not handled
Traceback (most recent call last):
  File "test.py", line 3, in <module>
    iterable = fastfilepackage.hello_world()
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf8 in position 7: invalid start byte

如何从无效的 Unicode 字符中恢复?

即,在绑定 C 和 Python 时忽略这些错误。

当纯粹使用 Python 时,我可以使用这个:

file_in = open( './bug.txt', errors='replace' )
line = file_in.read()
print( "The input line was: line".format(line=line) )

Python C Extensions绑定时errors='replace'的等价物是什么?

【问题讨论】:

【参考方案1】:

如果你想拥有“替换”错误处理语义,你应该像这样在 C 端执行它并将其返回到 python 端:

return PyUnicode_DecodeUTF8(retval.c_str(), retval.size(), "replace");

在我们的例子中,这会给出类似的东西:

Hello, world!
retval event "?at" not handled
iterable event "�at" not handled

【讨论】:

retval (std::string) 有一个名为 size() 的成员,其复杂度为 O(1),因此,无需使用复杂度为 O(n) 的 strlen(str) . retval.c_str() 也具有恒定复杂度 O(1)(C++ 11 及更高版本),因此无需分配 char* 来存储其值。修复只需将return Py_BuildValue( "s", retval.c_str() ); 替换为return PyUnicode_DecodeUTF8(retval.c_str(), retval.size(), , "replace");,因为PyUnicode_DecodeUTF8 已经返回PyObject *,这正是我需要从hello_world 返回的内容。 我已经更新了答案,因为它现在(在删除 strlen 之后)是一个更简洁的解决方案。关于赋值const char *cStr = retval.c_str();:对于字符串值没有动态分配内存,只是简单地将指向字符串的指针赋值给一个变量,无论如何从性能上来说是可以忽略不计的。

以上是关于使用 Python C 扩展时如何修复 'UnicodeDecodeError: 'utf-8' codec can't decode byte'?的主要内容,如果未能解决你的问题,请参考以下文章

尝试编译C扩展模块时缺少Python.h

如何在 virtualenv 中使用 MinGW 编译 Python C 扩展?

在 python 2 上释放 C 扩展模块时运行函数

如何使用 Python 2.7 在 Windows 上修复 pip 安装证书问题? [复制]

如何修复警告:扩展初始化列表?

如何在SSH盒上手动安装VSCode扩展名?