【问题标题】:Converting python.io object to std::istream when using boost::python使用 boost::python 时将 python.io 对象转换为 std::istream
【发布时间】:2014-06-15 00:11:53
【问题描述】:

在编写我的第一个 django 应用程序时,我遇到了 boost::python 的以下问题。在 python 代码中,我需要将 io.BytesIO 传递给采用 std::istream 的 C++ 类。

我有一个用于读取特定格式文件的旧版 C++ 库。我们称之为somelib。这个库的接口使用 std::istream 作为输入。像这样的:

class SomeReader
{
public:
    bool read_from_stream(std::istream&);
};

我想包装它,以便我可以通过以下方式从 python 中使用我的库:

reader = somelib.SomeReader()
print ">>Pyhton: reading from BytesIO"
buf = io.BytesIO("Hello Stack Overflow")
reader.read(buf)

我发现了如何为实际的 python 文件对象做这件事。但尚不清楚如何对任意 file-like 对象执行此操作。这是我到目前为止的 python 绑定的定义:

using namespace boost::python;
namespace io = boost::iostreams;

struct SomeReaderWrap: SomeReader, wrapper<SomeReader>
{
    bool read(object &py_file)
    {
        if (PyFile_Check(py_file.ptr()))
        {
            FILE* handle = PyFile_AsFile(py_file.ptr());
            io::stream_buffer<io::file_descriptor_source> fpstream (fileno(handle), io::never_close_handle);
            std::istream in(&fpstream);
            return this->read_from_stream(in);
        }
        else
        {
            //
            // How do we implement this???
            //
            throw std::runtime_error("Not a file, have no idea how to read this!");
        }
    }
};


BOOST_PYTHON_MODULE(somelib)
{
    class_<SomeReaderWrap, boost::noncopyable>("SomeReader")
        .def("read", &SomeReaderWrap::read);
}

是否有或多或少通用的方式将 python IO 对象转换为 C++ 流?

提前谢谢你。


作为我的实验结果,我创建了一个小的github repo 来说明这个问题。

【问题讨论】:

    标签: python c++ io boost-python


    【解决方案1】:

    考虑实现一个能够从 Python io.BytesIO 对象读取的 Boost.IOStreams Source 概念模型,而不是转换 Python io.BytesIO 对象。这将允许构建一个boost::iostreams::stream 并可供SomeReader::read_from_stream() 使用。

    tutorial 演示了如何创建和使用自定义 Boost.IOStream 源。总的来说,这个过程应该是相当简单的。只需按照io.BufferedIOBase.read() 实现Source 概念的read() 功能:

    /// Type that implements the Boost.IOStream's Source concept for reading
    /// data from a Python object supporting read(size).
    class PythonInputDevice
      : public boost::iostreams::source // Use convenience class.
    {
    public:
    
      explicit
      PythonInputDevice(boost::python::object object)
        : object_(object)
      {}
    
      std::streamsize read(char_type* buffer, std::streamsize buffer_size) 
      {
        namespace python = boost::python;
        // Read data through the Python object's API.  The following is
        // is equivalent to:
        //   data = object_.read(buffer_size)
        boost::python::object py_data = object_.attr("read")(buffer_size);
        std::string data = python::extract<std::string>(py_data);
    
        // If the string is empty, then EOF has been reached.
        if (data.empty())
        {
          return -1; // Indicate end-of-sequence, per Source concept.
        }
    
        // Otherwise, copy data into the buffer.
        copy(data.begin(), data.end(), buffer);
        return data.size();
      }
    
    private:
      boost::python::object object_;
    };
    

    然后使用源设备创建boost::iostreams::stream

    boost::iostreams::stream<PythonInputDevice> input(py_object);
    SomeReader reader;
    reader.read_from_stream(input);
    

    由于PythonInputDevice 是根据object.read() 实现的,duck typing 允许PythonInputDevice 与任何支持具有相同前置条件和后置条件的read() 方法的Python 对象一起使用。这包括内置的 Python file 对象,使得不再需要基于 SomeReaderWrap::read() 中的类型进行条件分支。


    这是一个基于原始代码的完整最小示例:

    #include <algorithm> // std::copy
    #include <iosfwd> // std::streamsize
    #include <iostream>
    #include <boost/python.hpp>
    #include <boost/iostreams/concepts.hpp>  // boost::iostreams::source
    #include <boost/iostreams/stream.hpp>
    
    class SomeReader
    {
    public:
      bool read_from_stream(std::istream& input)
      {
        std::string content(std::istreambuf_iterator<char>(input.rdbuf()),
                            (std::istreambuf_iterator<char>()));
        std::cout << "SomeReader::read_from_stream(): " << content << std::endl;
        return true;      
      }
    };
    
    /// Type that implements a model of the Boost.IOStream's Source concept
    /// for reading data from a Python object supporting:
    ///   data = object.read(size).
    class PythonInputDevice
      : public boost::iostreams::source // Use convenience class.
    {
    public:
    
      explicit
      PythonInputDevice(boost::python::object object)
        : object_(object)
      {}
    
      std::streamsize read(char_type* buffer, std::streamsize buffer_size) 
      {
        namespace python = boost::python;
        // Read data through the Python object's API.  The following is
        // is equivalent to:
        //   data = object_.read(buffer_size)
        boost::python::object py_data = object_.attr("read")(buffer_size);
        std::string data = python::extract<std::string>(py_data);
    
        // If the string is empty, then EOF has been reached.
        if (data.empty())
        {
          return -1; // Indicate end-of-sequence, per Source concept.
        }
    
        // Otherwise, copy data into the buffer.
        copy(data.begin(), data.end(), buffer);
        return data.size();
      }
    
    private:
      boost::python::object object_;
    };
    
    struct SomeReaderWrap
      : SomeReader,
        boost::python::wrapper<SomeReader>
    {
      bool read(boost::python::object& object)
      {
        boost::iostreams::stream<PythonInputDevice> input(object);
        return this->read_from_stream(input);
      }
    };
    
    BOOST_PYTHON_MODULE(example)
    {
      namespace python = boost::python;
      python::class_<SomeReaderWrap, boost::noncopyable>("SomeReader")
        .def("read", &SomeReaderWrap::read)
        ;
    }
    

    互动使用:

    $ echo -n "test file" > test_file
    $ python
    >>> import example
    >>> with open('test_file') as f:
    ...     reader = example.SomeReader()
    ...     reader.read(f)
    ... 
    SomeReader::read_from_stream(): test file
    True
    >>> import io
    >>> with io.BytesIO("Hello Stack Overflow") as f:
    ...     reaader = example.SomeReader()
    ...     reader.read(f)
    ... 
    SomeReader::read_from_stream(): Hello Stack Overflow
    True
    

    【讨论】:

      猜你喜欢
      • 2017-07-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-28
      • 2015-12-31
      • 2013-08-23
      相关资源
      最近更新 更多