使用boost :: python时,将python.io对象转换为std :: ostream

时间:2014-09-25 08:21:20

标签: c++ boost-python

我见过将python.io对象转换为std :: istream的相关答案。无论如何,这可以通过boost :: iostream :: sink用于std :: ostream吗? 在我的情况下,我有一个C ++函数     

void writeToStream(std :: ostream&)

如何将此函数公开给python?

1 个答案:

答案 0 :(得分:1)

正如在this回答中所做的那样,应该考虑实现Boost.IOStream使用的设备类型来执行委派而不是转换为std::ostream。在这种情况下,Boost.IOStream的Device概念需要支持Sink概念。它还可以通过对Flushable概念建模来扩展以支持其他功能,例如刷新缓冲区。

概念指定了类型必须提供的内容。在Sink概念的情况下,模型可以定义如下:

struct Sink
{
  typedef char      char_type;
  typedef sink_tag  category;
  std::streamsize write(const char* s, std::streamsize n) 
  {
    // Write up to n characters from the buffer
    // s to the output sequence, returning the 
    // number of characters written
  }
};

Flushable概念在文档中不那么直接,可以在检查flush()函数语义时确定。在这种情况下,模型可以定义如下:

struct Flushable
{
  typedef flushable_tag category;
  bool flush()
  {
    // Send all buffered characters downstream.  On error,
    // throw an exception.  Otherwise, return true.
  }
};

这是一个基本类型,它使用duck typing对Sink和Flushable概念进行建模并委托给Python对象。 python对象是:

  • 要求write(str)方法返回None或写入的字节数。
  • 可选的flush()方法。
/// @brief Type that implements the Boost.IOStream's Sink and Flushable
///        concept for writing data to Python object that support:
///          n = object.write(str) # n = None or bytes written
///          object.flush()        # if object.flush exists, then it is callable
class PythonOutputDevice
{
public:

  // This class models both the Sink and Flushable concepts.
  struct category
    : boost::iostreams::sink_tag,
      boost::iostreams::flushable_tag
  {};

  explicit
  PythonOutputDevice(boost::python::object object)
    : object_(object)
  {}

// Sink concept.
public:

  typedef char char_type;

  std::streamsize write(const char* buffer, std::streamsize buffer_size)
  {
    namespace python = boost::python;
    // Copy the buffer to a python string.
    python::str data(buffer, buffer_size);

    // Invoke write on the python object, passing in the data.  The following
    // is equivalent to:
    //   n = object_.write(data)
    python::extract<std::streamsize> bytes_written(
      object_.attr("write")(data));

    // Per the Sink concept, return the number of bytes written.  If the
    // Python return value provides a numeric result, then use it.  Otherwise,
    // such as the case of a File object, use the buffer_size.
    return bytes_written.check()
      ? bytes_written
      : buffer_size;
  }

// Flushable concept.
public:

  bool flush()
  {
    // If flush exists, then call it.
    boost::python::object flush = object_.attr("flush");
    if (!flush.is_none())
    {
      flush();
    }

    // Always return true.  If an error occurs, an exception should be thrown.
    return true;
  }

private:
  boost::python::object object_;
};

请注意,为了支持多个概念,创建了一个嵌套的category结构,它继承了模型实现的多个类别标记。

使用Boost.IOStream设备,最后一步是公开一个辅助函数,该函数将使用Python对象创建流,然后调用现有的writeToStream()函数。

/// @brief Use an auxiliary function to adapt the legacy function.
void aux_writeToStream(boost::python::object object)
{
  // Create an ostream that delegates to the python object.
  boost::iostreams::stream<PythonOutputDevice> output(object);
  writeToStream(output);
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::def("writeToStream", &aux_writeToStream);
}

这是一个完整的最小例子:

#include <iosfwd> // std::streamsize
#include <iostream>
#include <boost/python.hpp>
#include <boost/iostreams/concepts.hpp>  // boost::iostreams::sink
#include <boost/iostreams/stream.hpp>

/// @brief Legacy function.
void writeToStream(std::ostream& output)
{
  output << "Hello World";
  output.flush();
}

/// @brief Type that implements the Boost.IOStream's Sink and Flushable
///        concept for writing data to Python object that support:
///          n = object.write(str) # n = None or bytes written
///          object.flush()        # if flush exists, then it is callable
class PythonOutputDevice
{
public:

  // This class models both the Sink and Flushable concepts.
  struct category
    : boost::iostreams::sink_tag,
      boost::iostreams::flushable_tag
  {};

  explicit
  PythonOutputDevice(boost::python::object object)
    : object_(object)
  {}

// Sink concept.
public:

  typedef char char_type;

  std::streamsize write(const char* buffer, std::streamsize buffer_size)
  {
    namespace python = boost::python;
    // Copy the buffer to a python string.
    python::str data(buffer, buffer_size);

    // Invoke write on the python object, passing in the data.  The following
    // is equivalent to:
    //   n = object_.write(data)
    python::extract<std::streamsize> bytes_written(
      object_.attr("write")(data));

    // Per the Sink concept, return the number of bytes written.  If the
    // Python return value provides a numeric result, then use it.  Otherwise,
    // such as the case of a File object, use the buffer_size.
    return bytes_written.check()
      ? bytes_written
      : buffer_size;
  }

// Flushable concept.
public:

  bool flush()
  {
    // If flush exists, then call it.
    boost::python::object flush = object_.attr("flush");
    if (!flush.is_none())
    {
      flush();
    }

    // Always return true.  If an error occurs, an exception should be thrown.
    return true;
  }

private:
  boost::python::object object_;
};

/// @brief Use an auxiliary function to adapt the legacy function.
void aux_writeToStream(boost::python::object object)
{
  // Create an ostream that delegates to the python object.
  boost::iostreams::stream<PythonOutputDevice> output(object);
  writeToStream(output);
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::def("writeToStream", &aux_writeToStream);
}

交互式使用:

>>> import example
>>> import io
>>> with io.BytesIO() as f:
...     example.writeToStream(f)
...     print f.getvalue()
...
Hello World
>>> with open('test_file', 'w') as f:
...     example.writeToStream(f)
...
>>>
$ cat test_file
Hello World