使用pybind11开发c++扩展模块输出到控制台的中文信息显示乱码的问题

使用pybind11开发供Python项目使用的C++扩展模块时,如果在扩展模块的C++代码中向控制台输出的信息中包含中文,python程序的控制台很容易出现乱码。以如下C++扩展框架代码为例(这是对上一篇文章简明使用pybind11开发pythonc+扩展模块教程-CSDN博客中的C++扩展框架代码进行少量修正后的结果):

cpp 复制代码
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <locale>
#include <codecvt>
#include <windows.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

namespace py = pybind11;

class CSVFinder {
private:
    std::map<std::string, std::vector<std::string>> dataMap;
    std::vector<std::string> headers;


public:
    // 默认构造函数
    CSVFinder() {
    }

    // 接受 CSV 文件路径的构造函数
    CSVFinder(const std::string& filePath) {
        loadCSV(filePath);
    }

    // 载入 CSV 文件的方法
    void loadCSV(const std::string& filePath) {
        // 检查文件扩展名是否为 .csv
        if (filePath.substr(filePath.find_last_of(".") + 1) != "csv") {
            std::cerr << "文件扩展名不是 .csv,但仍尝试解析: "  << filePath << std::endl;
        }

        std::ifstream file(filePath);
        if (!file.is_open()) {
            std::cerr << "无法打开文件,请检查文件名或路径是否错误: " << filePath <<  std::endl;
            dataMap.clear();
            headers.clear();
            return;
        }

        std::string line;
        // 读取第一行作为标题
        if (!std::getline(file, line)) {
            std::cerr << "无法读取文件的第一行,请检查文件内容: " << filePath << std::endl;
            dataMap.clear();
            headers.clear();
            return;
        }

        std::istringstream iss(line);
        std::string token;
        while (std::getline(iss, token, ',')) {
            headers.push_back(token);
        }

        if (headers.empty()) {
            std::cerr << "第一行未包含有效的标题信息,请检查文件内容: "
                       << filePath << std::endl;
            dataMap.clear();
            headers.clear();
            return;
        }

        // 读取后续行
        while (std::getline(file, line)) {
            std::istringstream iss(line);
            std::vector<std::string> values;
            std::string token;
            while (std::getline(iss, token, ',')) {
                values.push_back(token);
            }
            if (values.empty()) {
                std::cerr << "某行未包含有效的数据信息,请检查文件内容: "
                           << filePath << std::endl;
                dataMap.clear();
                headers.clear();
                return;
            }
            std::string key = values[0];
            values.erase(values.begin());
            dataMap[key] = values;
        }

        if (dataMap.empty()) {
            std::cerr << "The file does not contain valid data lines:"
                       << filePath << std::endl;
            dataMap.clear();
            headers.clear();
            return;
        }

        file.close();
    }

    // 返回 dataMap 的方法
    const std::map<std::string, std::vector<std::string>>& getDataMap() const {
        return dataMap;
    }

    // 返回 headers 的方法
    const std::vector<std::string>& getHeaders() const {
        return headers;
    }

    // 查找数据的方法
    py::object findData(const std::string& rowTitle, const std::string& colTitle) {
        auto rowIt = dataMap.find(rowTitle);
        if (rowIt != dataMap.end()) {
            for (size_t i = 1; i < headers.size(); ++i) {
                if (headers[i] == colTitle && i < rowIt->second.size()) {
                    return py::cast(rowIt->second[i]);
                }
            }
        }
        return py::none();  // 返回 None 对象
    }

    // 查找行的方法
    std::vector<std::string> findRow(const std::string& rowTitle) {
        auto it = dataMap.find(rowTitle);
        if (it != dataMap.end()) {
            return it->second;
        }
        return {};
    }

    // 查找列的方法
    std::vector<std::string> findColumn(const std::string& colTitle) {
        std::vector<std::string> column;
        // 查找列标题在headers中的索引, 注意 headers[0] 为行标题,因此从index 1开始
        int colIndex = -1;
        for (size_t i = 1; i < headers.size(); ++i) {
            if (headers[i] == colTitle) {
                colIndex = static_cast<int>(i - 1); // 对应到每行数据中的索引
                break;
            }
        }
        if (colIndex < 0) {
            return column; // 未找到对应的列标题,返回空向量
        }
        // 遍历每一行数据
        for (const auto& row : dataMap) {
            const std::vector<std::string>& values = row.second;
            if (static_cast<size_t>(colIndex) < values.size()) {
                column.push_back(values[colIndex]);
            }
            else {
                column.push_back(""); // 如该行数据列数不足,可选择返回空字符串
            }
        }
        return column;
    }
};

PYBIND11_MODULE(CSVFinder, m) {
    py::class_<CSVFinder>(m, "CSVFinder")
        .def(py::init<>())
        .def(py::init<const std::string&>())
        .def("load_csv", &CSVFinder::loadCSV)
        .def("get_datamap", &CSVFinder::getDataMap)
        .def("get_headers", &CSVFinder::getHeaders)
        .def("find_data", &CSVFinder::findData)
        .def("find_row", &CSVFinder::findRow)
        .def("find_column", &CSVFinder::findColumn);
}

其中loadCSV方法中有不少向控制台输入的错误信息。将上面的框架构建分发给Python项目使用(具体过程参见本文开头提到的博客),使用下面的python代码进行测试:

python 复制代码
from CSVFinder import CSVFinder

width = 8

file = "E:/projects/ziweidoushu/csv1/destiny_type.csv"
finder = CSVFinder(file)
dict = finder.get_datamap()
headers = finder.get_headers()
print(f'{[value.ljust(width) + "|" for value in headers]}')
for key, values in dict.items():
	print(f'{key.ljust(width)  + "|" }:{[value.ljust(width)  + "|" for value in values]}')
key = '甲'
row = finder.find_row(key)
print(f"{key.ljust(width)  + '|' }:{len(row)}:{[value.ljust(width)  + '|' for value in row]}")
key = '寅'
column = finder.find_column(key)
print(f"{key.ljust(width)  + '|' }:{len(column)}:{[value.ljust(width)  + '|' for value in column]}")

测试程序中的文件路径故意写错了,本来应该向控制台输出C++代码中的包含中文的错误信息:

无法打开文件,请检查文件名或路径是否错误: E:/projects/ziweidoushu/csv1/destiny_type.csv

在控制台执行测试程序,实际输出如下图:

可以看到C++扩展模块向控制台输出的中文信息变成了乱码,但是Python程序向控制台输出的中文信息则显示正常。AI以及不少文章说用下面的命令将控制台所使用的编码改成UTF-8能够解决问题:

chcp 65001

实际上起不了作用:

实际上只要在C++扩展模块中在字符串前加上u8修饰符、在模块入口处将控制台编码改为UTF-8,并给编译器加上"/utf-8"选项即可正常显示中文,而无需调整控制台编码页。也就是:

1、在C++扩展模块代码中包含<windows.h>,然后调用Windows API在pybind11模块入口处进行如下调用即可:

//省略一些代码

std::cerr <<u8"无法打开文件,请检查文件名或路径是否错误: " << filePath << std::endl;

// 省略一些代码

PYBIND11_MODULE(CSVFinder, m) {

SetConsoleOutputCP(CP_UTF8); // 增加的代码

std::cerr.imbue(std::locale("chs")); // 增加的代码,可省略

std::cout.imbue(std::locale("chs")); // 增加的代码,可省略

// 省略后面的代码

实际上C++扩展模块中增加的三行代码后面两行省略也能解决问题,但考虑到提高健壮性,加上后两行代码,让控制台认为处于中文环境中。

2、在setup.py的扩展模块定义中,增加"/utf-8"选项:

定义扩展模块

csv_module = Extension(

'CSVFinder', # 模块名称

sources=['read_csv.cpp'], # C++ 源文件路径

include_dirs=[pybind11.get_include(), ],

language='c++', # 指定使用 C++ 语言

extra_compile_args=['/utf-8', '-D_WIN32_WINNT=0x0601', '-D__USE_MINGW_ANSI_STDIO=1'], # 编译选项

)

重新构建并测试,结果如下:

相关推荐
牵牛老人16 分钟前
Qt 元对象系统探秘:从 Q_OBJECT 到反射编程的魔法之旅
开发语言·qt
啥都鼓捣的小yao20 分钟前
Python在糖尿病分类问题上寻找具有最佳 ROC AUC 分数和 PR AUC 分数(决策树、逻辑回归、KNN、SVM)
python·决策树·机器学习·支持向量机·分类·逻辑回归
拖拉机26 分钟前
Python(七)函数
后端·python
E-iceblue28 分钟前
通过 Python 在PDF中添加、或删除超链接
python·python pdf库·pdf超链接
2401_8906661331 分钟前
免费送源码:Java+ssm+MySQL 校园二手书销售平台设计与实现 计算机毕业设计原创定制
java·spring boot·python·mysql·小程序·php·课程设计
SHIPKING39339 分钟前
【LangChain少样本提示工程实战】FewShotPromptTemplate原理与应用解析——附运行代码
数据库·python·langchain·llm·fewshotprompt
叠叠乐41 分钟前
Rust 中的Relaxed 内存指令重排演示:X=0 && Y=0 是怎么出现的?
开发语言·算法·rust
SoFlu软件机器人1 小时前
高并发场景下的 Java 性能优化
java·开发语言·性能优化
ll7788111 小时前
C++学习之路,从0到精通的征途:string类的模拟实现
开发语言·数据结构·c++·学习·算法·职场和发展
豆豆1 小时前
day24 学习笔记
笔记·python·opencv·学习