【问题标题】:How do you use the python3 c api for a command line driven app?您如何将 python3 c api 用于命令行驱动的应用程序?
【发布时间】:2013-08-17 04:43:42
【问题描述】:

一段时间以来,我一直在使用自定义构建来替代 virtualenv,它非常棒。构建需要更长的时间,但它确实有效,而且永远不会搞砸。

其中的一部分在一个简单的 python 包装器中,它将一些特定的文件夹添加到库路径中,我发现这非常有用。它的代码很简单:

#include <stdio.h>
#include <n/text/StringUtils.h>
#include <Python.h>

int main(int argc, char *argv[]) {

  /* Setup */
  Py_SetProgramName(argv[0]);
  Py_Initialize();
  PySys_SetArgv(argc, argv);

  /* Add local path */
  PyObject *sys = PyImport_ImportModule("sys");
  PyObject *path = PyObject_GetAttrString(sys, "path");

  /* Custom path */
  char *cwd = nrealpath(argv[0]);
  char *libdir = nstrpath(cwd, "python_lib", NULL);
  PyList_Append(path, PyString_FromString(libdir));
  free(cwd);
  free(libdir);

  /* Run the 'main' module */
  int rtn = Py_Main(argc, argv); // <-- Notice the command line arguments.
  Py_Finalize();

  return rtn;
}

那么,迁移到 python3 很好,对吧?所以...

我尽职尽责地将 PyString_FromString() 的调用替换为 PyByte_FromString() 并尝试重新编译,但它引发了错误:

/Users/doug/env/src/main.c:8:21: error: incompatible pointer types passing 'char *' to parameter of type 'wchar_t *' (aka 'int *')
      [-Werror,-Wincompatible-pointer-types]
  Py_SetProgramName(argv[0]);
                    ^~~~~~~
/Users/doug/projects/py-sdl2/py3/include/python3.3m/pythonrun.h:25:45: note: passing argument to parameter here
PyAPI_FUNC(void) Py_SetProgramName(wchar_t *);
                                            ^
/Users/doug/env/src/main.c:10:23: error: incompatible pointer types passing 'char **' to parameter of type 'wchar_t **' (aka 'int **')
      [-Werror,-Wincompatible-pointer-types]
  PySys_SetArgv(argc, argv);
                      ^~~~
/Users/doug/projects/py-sdl2/py3/include/python3.3m/sysmodule.h:12:47: note: passing argument to parameter here
PyAPI_FUNC(void) PySys_SetArgv(int, wchar_t **);
                                              ^
/Users/doug/env/src/main.c:24:27: error: incompatible pointer types passing 'char **' to parameter of type 'wchar_t **' (aka 'int **')
      [-Werror,-Wincompatible-pointer-types]
  int rtn = Py_Main(argc, argv);
                          ^~~~
/Users/doug/projects/py-sdl2/py3/include/python3.3m/pythonrun.h:148:45: note: passing argument to parameter 'argv' here
PyAPI_FUNC(int) Py_Main(int argc, wchar_t **argv);
                                            ^
3 errors generated.
make[2]: *** [CMakeFiles/python.dir/src/main.c.o] Error 1
make[1]: *** [CMakeFiles/python.dir/all] Error 2
make: *** [all] Error 2

从错误中可以看出,使用的是 wchar_t 而不是 char *。

你应该如何使用这个 api?

我看到有一些这样做的例子,例如: http://svn.python.org/projects/python/tags/r32rc2/Python/frozenmain.c

真的吗?

我的 29 行程序必须变成一个充满#ifdefs 的 110 行怪物?

是我误会了,还是python3 c api真的变得这么难用了?

我肯定错过了一些明显的便利功能,它以一种简单、便携和跨平台的方式为您做到这一点?

【问题讨论】:

    标签: c python-3.x


    【解决方案1】:

    官方推荐的将char转换为wchar_t的方法是使用Py_DecodeLocale。像这样:

    wchar_t *program = Py_DecodeLocale(argv[0], NULL);
    Py_SetProgramName(program);
    

    【讨论】:

    【解决方案2】:

    我自己花了很多时间寻找答案。我将其他人的cmets拼凑在一起,构建了这个sn-p来将char** argv转换为wchar_t**类型:

    wchar_t** _argv = PyMem_Malloc(sizeof(wchar_t*)*argc);
    for (int i=0; i<argc; i++) {
      wchar_t* arg = Py_DecodeLocale(argv[i], NULL);
      _argv[i] = arg;
    }
    Py_Initialize();
    PySys_SetArgv(argc, _argv);
    

    到目前为止,效果很好。我已经确认我的嵌入式 Python 代码正确接收了命令行参数。

    【讨论】:

      【解决方案3】:

      似乎没有简单的方法来做到这一点。

      我在下面最接近的。我会模糊地留下这个问题,希望有人会过来告诉我超级简单的方法来做到这一点。

      #include <stdio.h>
      #include <Python.h>
      #include <wchar.h>
      
      int main(int argc, char *argv[]) {
      
        /* These have to be wchar_t */
        char *str_program_name = argv[0];
        char **str_argv = argv;
      
        /* For ever stupid reason, these don't need to be wchar_t * */
        char *_sys = "sys";
        char *_libdir = "lib";
        char *_path = "path";
        char *_dot = ".";
      
      #if PY_MAJOR_VERSION >= 3
        wchar_t **_argv = nstrws_array(argc, str_argv);
        wchar_t *_program_name = nstrws_convert(str_program_name);
      #else
        char **_argv = str_argv;
        char *_program_name = str_program_name;
      #endif
      
        /* Setup */
        Py_SetProgramName(_program_name);
        Py_Initialize();
      
        /* Add local path */
      #if PY_MAJOR_VERSION >= 3
        PyObject *sys = PyImport_ImportModule(_sys);
        PyObject *path = PyObject_GetAttrString(sys, _path);
        PyList_Append(path, PyBytes_FromString(_dot));
        PyList_Append(path, PyBytes_FromString(_libdir));
      #else
        PyObject *sys = PyImport_ImportModule(_sys);
        PyObject *path = PyObject_GetAttrString(sys, _path);
        PyList_Append(path, PyString_FromString(_dot));
        PyList_Append(path, PyString_FromString(_libdir));
      #endif
      
        /* Run the 'main' module */
        int rtn = Py_Main(argc, _argv);
        Py_Finalize();
      
      #if PY_MAJOR_VERSION >= 3
        nstrws_dispose(argc, _argv);
        free(_program_name);
      #endif
      
        return rtn;
      }
      

      使用:

      /** Unix-like platform char * to wchar_t conversion. */
      wchar_t *nstrws_convert(char *raw) {
        wchar_t *rtn = (wchar_t *) calloc(1, (sizeof(wchar_t) * (strlen(raw) + 1)));
        setlocale(LC_ALL,"en_US.UTF-8"); // Unless you do this python 3 crashes.
        mbstowcs(rtn, raw, strlen(raw));
        return rtn;
      }
      
      /** Dispose of an array of wchar_t * */
      void nstrws_dispose(int count, wchar_t ** values) {
        for (int i = 0; i < count; i++) {
          free(values[i]);
        }
        free(values);
      }
      
      /** Convert an array of strings to wchar_t * all at once. */
      wchar_t **nstrws_array(int argc, char *argv[]) {
        wchar_t **rtn = (wchar_t **) calloc(argc, sizeof(wchar_t *));
        for (int i = 0; i < argc; i++) {
          rtn[i] = nstrws_convert(argv[i]);
        }
        return rtn;
      }
      

      如果需要,对于 Windows 用户:

      #include <windows.h>
      
      /** Windows char * to wchar_t conversion. */
      wchar_t *nstrws_convert(char *raw) {
        int size_needed = MultiByteToWideChar(CP_UTF8, 0, raw, -1, NULL, 0);
        wchar_t *rtn = (wchar_t *) calloc(1, size_needed * sizeof(wchar_t));
        MultiByteToWideChar(CP_UTF8, 0, raw, -1, rtn, size_needed);
        return rtn;
      }
      

      【讨论】:

      • 您代码中的评论 Seriously, eat a bag 是不尊重他人的,在 stackoverflow 上不受欢迎。
      【解决方案4】:

      以下代码包含 Hello World 嵌入 Python 的 C++ 实现。

      HelloWorld.cpp:

      #include <cstdlib>
      #include <stdexcept>
      
      #include <Python.h>
      
      int main(int argc, char* argv[])
      {
          wchar_t** wargv = new wchar_t*[argc];
          
          for(int i = 0; i < argc; i++)
          {
              wargv[i] = Py_DecodeLocale(argv[i], nullptr);
              if(wargv[i] == nullptr)
              {
                  return EXIT_FAILURE;
              }
          }
          
          Py_SetProgramName(wargv[0]);
      
          Py_Initialize();
          
          PySys_SetArgv(argc, wargv);
          
          PyRun_SimpleString("import sys");
          PyRun_SimpleString("print('Hello World!', sys.argv)");
          
          Py_Finalize();
          
          for(int i = 0; i < argc; i++)
          {
              PyMem_RawFree(wargv[i]);
              wargv[i] = nullptr;
          }
      
          delete[] wargv;
          wargv = nullptr;
          
          return 0;
      }
      

      为编译安装先决条件*:

      $ sudo apt install build-essential pkg-config python3 python3-dev 
      

      编译源代码:

      $ g++ HelloWorld.cpp `pkg-config python3-embed --libs --cflags` -o HelloWorld
      

      运行:

      $ ./HelloWorld
      $ # Hello World! ['./HelloWorld']
      $
      $ ./HelloWorld hi
      $ # Hello World! ['./HelloWorld', 'hi']
      

      【讨论】:

        【解决方案5】:

        我发现这可以在主函数中将 char* 转换为 wchar_t*:

        wchar_t progname[FILENAME_MAX + 1];
        mbstowcs(progname, argv[0], strlen(argv[0]) + 1);
        Py_SetProgramName(progname);
        

        如果您使用的是 unix,请使用:

        #include "sys/param.h"
        

        【讨论】:

          【解决方案6】:

          Py_DecodeLocale 返回一个指向新分配的宽字符串的指针,使用 PyMem_RawFree() 释放内存。所以不需要定义 wchar_t** _argv = PyMem_Malloc(sizeof(wchar_t*)*argc); 下面是我的代码:

          #include <iostream>
          #include <string>
          #include "hello-time.h"
          #include "main/hello-greet.h"
          #include "pybind11/embed.h"
          
          namespace py = pybind11;
          
          int main(int argc, char** argv) {
            std::string who = "world";
            if (argc > 1) {
              who = argv[1];
            }
            std::cout << get_greet(who) << std::endl;
            print_localtime();
          
            int py_argc = 9;
            py::scoped_interpreter guard{};
            wchar_t* wargv[py_argc];
            std::string py_argv[py_argc] = {"convert.py",
                                            "--input",
                                            "resnet_v1_50_inference.pb",
                                            "--inputs",
                                            "input:0",
                                            "--outputs",
                                            "resnet_v1_50/predictions/Reshape_1:0",
                                            "--output",
                                            "resnet50.onnx"};
            for (int i = 0; i < py_argc; i++) {
              wargv[i] = Py_DecodeLocale(py_argv[i].c_str(), nullptr);
              if (wargv[i] == nullptr) {
                fprintf(stderr, "Fatal error: cannot decode py_argv[%d]\n", i);
                exit(1);
              }
            }
          
            PySys_SetArgv(py_argc, wargv);
          
            py::module_ sys = py::module_::import("sys");
            py::print(sys.attr("path"));
            print_localtime();
            py::object convert = py::module_::import("tf2onnx.convert");
            py::object do_convert = convert.attr("main");
            do_convert();
            py::print(py::module::import("sys").attr("argv"));
          
            for (int i = 0; i < py_argc; i++) {
              PyMem_RawFree(wargv[i]);
              wargv[i] = nullptr;
            }
            return 0;
          }
          

          【讨论】:

          【解决方案7】:

          这可能是错误的做法,永远不会少:

          Py_SetProgramName((wchar_t*)argv[0]);
          

          此修复程序阻止了我的代码抱怨,尚未对其进行测试以了解它如何处理 args,但至少它可以编译..

          【讨论】:

          • 这会触发未定义的行为,即使在最好的情况下,您也会将 ASCII 解释为某种多字节编码,won't turn out well。
          • 是的。 OSX wchar_t 是 int,因此您将 char * 转换为 int *;这绝对行不通。
          • 好的,这样就可以确认它是错误的方法,那么正确的方法呢?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-10-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-25
          • 2021-07-08
          相关资源
          最近更新 更多