【问题标题】:C++ vector to Python 3.3C++ 向量到 Python 3.3
【发布时间】:2013-05-22 13:31:49
【问题描述】:

我想从 C++ 脚本中获取一个 Python 列表,例如 [1,2,3,4]。我编写了 C++ 脚本,它返回一个向量。

如何在没有 SWIG/SIP/Cython/和其他的情况下连接两端?

将 C++ 编译为 .exe 或 elf 文件,然后从命令行调用,让 .exe 创建一个包含向量的 .txt 并使用 python 读取它会更容易吗?

我的意思是,我只需要一个来自 C++ 的非常小的函数来对大量数据进行繁重的计算。做到这一点最不痛苦和最短的方法是什么?

编辑: 举个例子。 Python 会给 C++ 一个文件名字符串(“foo.txt”),然后它将读取文件的上下文(200,000 行乘 300 列),计算缺失值,然后将每行缺失的数量返回给 Python。这会产生一个包含 200,000 个数字的列表。 两人之间如何进行这种交流?

为了完整起见,这就是我仍然想知道如何去做的事情:

  • 将 python 文件名字符串传递给 C++
  • 在 C++ 中接收 python 字符串
  • 完成在 C++ 中创建向量
  • 将向量返回给 Python
  • 在 Python 中接收向量

【问题讨论】:

  • 很可能大部分时间都花在 C++ 解析输入文件上,因此从时间性能的角度来看,您将如何与 Python 进行通信并不重要。在同一进程中从 Python 调用 C++ 函数(例如,using ctypes 或更容易使用 Cython)是一种更简洁的解决方案,但对您而言,在两个进程之间传递数据可能更简单。

标签: c++ python cython cpython


【解决方案1】:

现在这可能没有实际意义,我在 your other question 上发布了类似的内容,但我已将此版本改编为 Python 3.3 和 C++,而不是 Python 2.7 和 C。

如果您想取回一个 Python 列表对象,并且由于您正在构建一个可能很长(200,000 个项目)的列表,那么在 C++ 代码中构建 Python 列表可能比构建std::vector,然后将其转换为 Python 列表。

根据您其他问题中的代码,我建议使用类似这样的代码...

// foo.cpp
#include <python3.3/Python.h>
#include <fstream>
#include <string>
using namespace std;

extern "C"
{
    PyObject* foo(const char* FILE_NAME)
    {
        string line;
        ifstream myfile(FILE_NAME);
        PyObject* result = PyList_New(0);

        while (getline(myfile, line))
        {
            PyList_Append(result, PyLong_FromLong(1));
        }

        return result;
    }
}

...编译...

$ g++ -fPIC -shared -o foo.so foo.cpp -lpython3.3m

...以及使用示例...

>>> from ctypes import *
>>> foo = CDLL('./foo.so')
>>> foo.foo.restype = py_object
>>> foo.foo(b'foo.cpp')
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

...虽然如果你需要将现有的std::vector转换为Python列表,你可以通过将向量的长度传递给PyList_New()来预先分配Python列表所需的内存,然后使用@ 987654323@ 而不是 PyList_Append()

我能想到的唯一其他方法是......

  1. 要在 Python 中预先分配一块 RAM,并让 C++ 函数填写值,就像 qarma 的回答一样,但您必须提前知道要分配多少 RAM。您可以选择任意值,但鉴于事先不知道文件中的行数,这个数字可能太大或太小。

  2. 在 C++ 中堆分配std::vector,并返回指向第一个元素的指针和元素的数量,但是一旦完成,您必须编写第二个函数来释放 RAM用它。

无论哪种方式,您仍然需要将“返回”数组转换为 Python 列表的开销,因此您不妨自己动手。

【讨论】:

    【解决方案2】:

    定义您的入口点extern "C" 并使用ctypes

    这里有一个例子让你开始,数据从 Python 传递,C++ 代码对数据进行排序,Python 得到结果:

    #include <sys/types.h>
    #include <algorithm>
    
    extern "C" {
        void foo(float* arr, size_t len);
    }
    
    void foo(float* arr, size_t len)
    {
        // if arr is input, convert to C++ array
    
        // crazy C++ code here
        std::sort(arr, arr+len);
    
        // if arr is output, convert C++ array to arr
    }
    

    将您的代码编译成一个共享对象(Linux 上的 libxxx.so,win 上的 libxxx.dll,osx 上的 libxxx.dylib),然后动态加载它并通过 ctypes 传入/传出数据:

    import ctypes
    import posix
    
    # Darwin .dylib; Linux .so; Windows .dll; use sys.platform() for runtime detection
    libxxx = ctypes.CDLL("./libxxx.so")
    libxxx.foo.argtypes = [ctypes.POINTER(ctypes.c_float), ctypes.c_size_t]
    libxxx.foo.restype = None
    
    data = ctypes.ARRAY(ctypes.c_float, 100)()
    
    # write someting into data[x]
    import random
    for i in range(100): data[i] = random.random()
    print data[:3], "...", data[-3:]
    
    libxxx.foo(data, len(data))
    
    # read out from data[x]
    print data[:3], "...", data[-3:]
    

    ctypes 的优点在于它从 2.5 开始就与 Python 捆绑在一起,您不需要任何额外的库。

    如果您想使用更高级的东西,请查看cffi

    【讨论】:

    • 还有一个令人愉快的副作用,GIL 在您的 C++ 函数执行时被释放,这意味着您可以将您的工作负载分成多个块并最大限度地使用您的多核处理器。在这种情况下,我建议在 Python 领域使用线程。仅当您知道自己在做什么时才使用 C++ 进行线程化。
    • 优秀的答案!
    • ctypes +1。如果我正确理解了这个问题,那么函数应该是int* filter_file(const char* filename, int* result_array, size_t* result_size),即在函数调用之前结果的大小是未知的。如果传递了 None ,该函数可能返回 realloced result_array 或新的 malloc'ed 数组(调用者必须释放它)。 result_size 可以使用ctypes.byref() 传递。要将返回的指针转换为 numpy 数组:a = np.frombuffer(cast(result_array, POINTER(c_int*result_size.value)).contents).
    • 顺便说一句,不要直接使用posix,而是使用os 模块(无论哪种方式uname() 都被记录为仅适用于Unix)。您可以改用sys.platform.startswith("linux")。您可能应该将 mode 保留为默认值。
    • 这里不需要或受益于显式转换,例如 int printf(const char*) 可以隐式调用为 libc.printf("blah") (返回默认为 int 和 Python 的自然 C 类型的参数“blah”,一个 C string)。我建议您像这样明确地调用它:libc.printf.restype = int; libc.printf.argtypes = [ctypes.c_char_p]; libc.printf("blah") 那么如果您不小心提供了错误的参数,例如None123 ctypes 将引发异常并且不会因分段错误而死。
    【解决方案3】:

    你在正确的轨道上。

    你有两个可执行文件吗?

    最好将其保存到中间文件。锁定文件,从您的 C++ 代码写入文件。解锁并从 Python 中读取。

    如果你只是想从 python 运行,你总是可以考虑扩展 python:

    Extending Python with C++

    也可以选择通过套接字进行,但如果您只想通过列表,这可能有点矫枉过正。

    【讨论】:

    • Extending Python with C++ 是对 Python 2.x 的引用。甚至 Python 3 版本也不是我想要的:它有 C 代码教程。
    • 它可以在 C++ 中工作,你只需要定义一些东西。阅读有关使其与 C++ 一起工作的部分。但是,是的,这些示例将是直接的 C 代码,这正是 Python 的本质。并不意味着它不适用于 C++。
    • 您会建议还是反对下面关于子流程的其他答案?
    • 这取决于。您计划执行多少次外部 c++ 文件?看来您这样做是出于效率原因?如果是这样,Python 很快。可能不如 c++ 快,但您最终会在这些中间步骤中失去任何性能优势。
    • 效率确实是关键。我更新了答案,现在我正在为一个可以提供更多方向的答案提供赏金(我尝试理解,但我无法真正理解链接中发生的事情)。
    【解决方案4】:

    您可以使用 python 中的 subprocess 模块从您的 c++ exe 文件中读取输出。 例如:

    c++ 文件:

    #include <iostream>
    using namespace std;
    int main()
    {
        int a[]={1,2,3,4};
        cout<<"[";
        for(int i=0; i<3; i++)
            cout<<a[i]<<",";
        cout<<a[3]<<"]";
        return 0;
    }
    

    那么你的 python 脚本将是:

    import subprocess
    a=subprocess.check_output("c++compiledfile")
    l=eval(a)
    print l
    

    【讨论】:

    • 你可以在这里使用ast.literal_eval(甚至json模块)而不是eval。将 C 数组写入二进制并使用 array 模块从 Python 读取可能更有效。
    【解决方案5】:

    您可以在进程间通信中使用字符串:
    使用 Python 中的 subprocess.check_output() 函数检查 C++ 程序的输出,并将文件名作为参数传递:

    Python 的代码:

    import subprocess
    from time import clock
    
    
    ti=clock()
    txt_fname='foo.txt'
    # pass the filename string to a c++ program, and receive vector in python
    output=subprocess.check_output(["./prog", txt_fname])
    result = eval(output)   # convert the str_result to a vector/list
    tf=clock()
    
    print(result)
    print(format(tf-ti, '.3f'))
    

    C++ 代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <iostream>
    #include <vector>
    
    
    int main(int argc, char *argv[])
    {
        // receive python string in c++
        char* txt_fname = argv[1];
    
    /*  // read matrix from file in c++
        FILE *f_matrix = fopen(txt_fname, "r");
        // ... [done by the question's author!]
        if (f_matrix!=NULL){
            fclose(f_matrix);
        }*/
    
        // create the vector in c++
        int n=200000;
        std::vector<int> vect(n);
        // or:  int vect[];
        // ... [done by the question's author!]
    
        // return the vector to python
        std::cout << "[";
        for (int i=0; i<n; i++)
            std::cout << vect[i] << ", ";
        std::cout << "]";
    
        return 0;
    }
    

    编辑: 添加计时器并将“ast.literal_eval()”替换为“eval()”,因为实际上在这种情况下 eval() 更快并且它与 python 兼容3.3.

    【讨论】:

    • 真的很有趣。它的速度怎么样?
    • 它“快”,您可以直接运行该代码。这个调用 C++ 程序并等待它的 Python 脚本在我的笔记本电脑中运行大约需要 0.470 秒。那个时间对你好吗?我要在脚本中添加计时器...
    猜你喜欢
    • 2012-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多