利用pybind11开发c++扩展模块输出到控制台的中文信息表现乱码的题目

[复制链接]
发表于 2025-9-23 11:49:41 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
利用pybind11开发供Python项目利用的C++扩展模块时,如果在扩展模块的C++代码中向控制台输出的信息中包罗中文,python步伐的控制台很容易出现乱码。以如下C++扩展框架代码为例(这是对上一篇文章简明利用pybind11开发pythonc+扩展模块教程-CSDN博客中的C++扩展框架代码进行少量修正后的效果):
  1. #include <iostream>
  2. #include <fstream>
  3. #include <sstream>
  4. #include <string>
  5. #include <vector>
  6. #include <map>
  7. #include <locale>
  8. #include <codecvt>
  9. #include <windows.h>
  10. #include <pybind11/pybind11.h>
  11. #include <pybind11/stl.h>
  12. namespace py = pybind11;
  13. class CSVFinder {
  14. private:
  15.     std::map<std::string, std::vector<std::string>> dataMap;
  16.     std::vector<std::string> headers;
  17. public:
  18.     // 默认构造函数
  19.     CSVFinder() {
  20.     }
  21.     // 接受 CSV 文件路径的构造函数
  22.     CSVFinder(const std::string& filePath) {
  23.         loadCSV(filePath);
  24.     }
  25.     // 载入 CSV 文件的方法
  26.     void loadCSV(const std::string& filePath) {
  27.         // 检查文件扩展名是否为 .csv
  28.         if (filePath.substr(filePath.find_last_of(".") + 1) != "csv") {
  29.             std::cerr << "文件扩展名不是 .csv,但仍尝试解析: "  << filePath << std::endl;
  30.         }
  31.         std::ifstream file(filePath);
  32.         if (!file.is_open()) {
  33.             std::cerr << "无法打开文件,请检查文件名或路径是否错误: " << filePath <<  std::endl;
  34.             dataMap.clear();
  35.             headers.clear();
  36.             return;
  37.         }
  38.         std::string line;
  39.         // 读取第一行作为标题
  40.         if (!std::getline(file, line)) {
  41.             std::cerr << "无法读取文件的第一行,请检查文件内容: " << filePath << std::endl;
  42.             dataMap.clear();
  43.             headers.clear();
  44.             return;
  45.         }
  46.         std::istringstream iss(line);
  47.         std::string token;
  48.         while (std::getline(iss, token, ',')) {
  49.             headers.push_back(token);
  50.         }
  51.         if (headers.empty()) {
  52.             std::cerr << "第一行未包含有效的标题信息,请检查文件内容: "
  53.                        << filePath << std::endl;
  54.             dataMap.clear();
  55.             headers.clear();
  56.             return;
  57.         }
  58.         // 读取后续行
  59.         while (std::getline(file, line)) {
  60.             std::istringstream iss(line);
  61.             std::vector<std::string> values;
  62.             std::string token;
  63.             while (std::getline(iss, token, ',')) {
  64.                 values.push_back(token);
  65.             }
  66.             if (values.empty()) {
  67.                 std::cerr << "某行未包含有效的数据信息,请检查文件内容: "
  68.                            << filePath << std::endl;
  69.                 dataMap.clear();
  70.                 headers.clear();
  71.                 return;
  72.             }
  73.             std::string key = values[0];
  74.             values.erase(values.begin());
  75.             dataMap[key] = values;
  76.         }
  77.         if (dataMap.empty()) {
  78.             std::cerr << "The file does not contain valid data lines:"
  79.                        << filePath << std::endl;
  80.             dataMap.clear();
  81.             headers.clear();
  82.             return;
  83.         }
  84.         file.close();
  85.     }
  86.     // 返回 dataMap 的方法
  87.     const std::map<std::string, std::vector<std::string>>& getDataMap() const {
  88.         return dataMap;
  89.     }
  90.     // 返回 headers 的方法
  91.     const std::vector<std::string>& getHeaders() const {
  92.         return headers;
  93.     }
  94.     // 查找数据的方法
  95.     py::object findData(const std::string& rowTitle, const std::string& colTitle) {
  96.         auto rowIt = dataMap.find(rowTitle);
  97.         if (rowIt != dataMap.end()) {
  98.             for (size_t i = 1; i < headers.size(); ++i) {
  99.                 if (headers[i] == colTitle && i < rowIt->second.size()) {
  100.                     return py::cast(rowIt->second[i]);
  101.                 }
  102.             }
  103.         }
  104.         return py::none();  // 返回 None 对象
  105.     }
  106.     // 查找行的方法
  107.     std::vector<std::string> findRow(const std::string& rowTitle) {
  108.         auto it = dataMap.find(rowTitle);
  109.         if (it != dataMap.end()) {
  110.             return it->second;
  111.         }
  112.         return {};
  113.     }
  114.     // 查找列的方法
  115.     std::vector<std::string> findColumn(const std::string& colTitle) {
  116.         std::vector<std::string> column;
  117.         // 查找列标题在headers中的索引, 注意 headers[0] 为行标题,因此从index 1开始
  118.         int colIndex = -1;
  119.         for (size_t i = 1; i < headers.size(); ++i) {
  120.             if (headers[i] == colTitle) {
  121.                 colIndex = static_cast<int>(i - 1); // 对应到每行数据中的索引
  122.                 break;
  123.             }
  124.         }
  125.         if (colIndex < 0) {
  126.             return column; // 未找到对应的列标题,返回空向量
  127.         }
  128.         // 遍历每一行数据
  129.         for (const auto& row : dataMap) {
  130.             const std::vector<std::string>& values = row.second;
  131.             if (static_cast<size_t>(colIndex) < values.size()) {
  132.                 column.push_back(values[colIndex]);
  133.             }
  134.             else {
  135.                 column.push_back(""); // 如该行数据列数不足,可选择返回空字符串
  136.             }
  137.         }
  138.         return column;
  139.     }
  140. };
  141. PYBIND11_MODULE(CSVFinder, m) {
  142.     py::class_<CSVFinder>(m, "CSVFinder")
  143.         .def(py::init<>())
  144.         .def(py::init<const std::string&>())
  145.         .def("load_csv", &CSVFinder::loadCSV)
  146.         .def("get_datamap", &CSVFinder::getDataMap)
  147.         .def("get_headers", &CSVFinder::getHeaders)
  148.         .def("find_data", &CSVFinder::findData)
  149.         .def("find_row", &CSVFinder::findRow)
  150.         .def("find_column", &CSVFinder::findColumn);
  151. }
复制代码
此中loadCSV方法中有不少向控制台输入的错误信息。将上面的框架构建分发给Python项目利用(具体过程参见本文开头提到的博客),利用下面的python代码进行测试:
  1. from CSVFinder import CSVFinder
  2. width = 8
  3. file = "E:/projects/ziweidoushu/csv1/destiny_type.csv"
  4. finder = CSVFinder(file)
  5. dict = finder.get_datamap()
  6. headers = finder.get_headers()
  7. print(f'{[value.ljust(width) + "|" for value in headers]}')
  8. for key, values in dict.items():
  9.         print(f'{key.ljust(width)  + "|" }:{[value.ljust(width)  + "|" for value in values]}')
  10. key = '甲'
  11. row = finder.find_row(key)
  12. print(f"{key.ljust(width)  + '|' }:{len(row)}:{[value.ljust(width)  + '|' for value in row]}")
  13. key = '寅'
  14. column = finder.find_column(key)
  15. 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'],  # 编译选项
)
  重新构建并测试,效果如下:


回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表