7

有没有办法std::[io]fstream通过 swig 在 python 中使用 's?

我有一个具有以下功能的 c 类:

void readFrom(std::istream& istr);
void writeTo(std::ostream& ostr);

我想在python中构造一个std::ofstream实例并将其作为参数传递给writeTo(并为读取做同样的事情)。

我试着做一个像

std::ostream& make_ostream(const std::string& file_name){
    return std::ofstream( file_name.c_str() );
}

在 swig.i文件中,这样这个函数就会成为接口的一部分。但是,这不起作用。由于流类是不可复制的,因此存在问题。

虽然std_iostream.i似乎有助于使用泛型[io]stream类,但它无助于制作我需要的文件流。

4

5 回答 5

7

我对这个问题的首选解决方案是让 Python 开发人员看到的接口尽可能“Pythonic”。在这种情况下,将接受 pythonfile对象作为您的ostreamistream参数。

为此,我们必须编写一个类型映射来设置每个映射。

我编写了以下头文件来演示这一点:

#ifndef TEST_HH
#define TEST_HH
#include <iosfwd>

void readFrom(std::istream& istr);
void writeTo(std::ostream& ostr);
#endif

我为测试编写了一个虚拟实现:

#include <iostream>
#include <cassert>
#include "test.hh"

void readFrom(std::istream& istr) {
  assert(istr.good());
  std::cout << istr.rdbuf() << "\n";
}

void writeTo(std::ostream& ostr) {
  assert(ostr.good());
  ostr << "Hello" << std::endl;
  assert(ostr.good());
}

有了它,我就可以使用以下方法成功包装它:

%module test

%{
#include <stdio.h>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
namespace io = boost::iostreams;
typedef io::stream_buffer<io::file_descriptor_sink> boost_ofdstream;
typedef io::stream_buffer<io::file_descriptor_source> boost_ifdstream;
%}

%typemap(in) std::ostream& (boost_ofdstream *stream=NULL) {
  int fd = -1;

  #if PY_VERSION_HEX >= 0x03000000
  fd = PyObject_AsFileDescriptor($input);
  #else 
  FILE *f=PyFile_AsFile($input); // Verify the semantics of this
  if (f) fd = fileno(f);
  #endif
  if (fd < 0) {
    SWIG_Error(SWIG_TypeError, "File object expected.");
    SWIG_fail;
  }
  else {
    // If threaded incrment the use count
    stream = new boost_ofdstream(fd, io::never_close_handle);
    $1 = new std::ostream(stream);
  }
}

%typemap(in) std::istream& (boost_ifdstream *stream=NULL) {
  int fd = -1;

  #if PY_VERSION_HEX >= 0x03000000
  fd = PyObject_AsFileDescriptor($input);
  #else 
  FILE *f=PyFile_AsFile($input); // Verify the semantics of this
  if (f) fd = fileno(f);
  #endif
  if (fd < 0) {
    SWIG_Error(SWIG_TypeError, "File object expected.");  
    SWIG_fail;
  }
  else {
    stream = new boost_ifdstream(fd, io::never_close_handle);
    $1 = new std::istream(stream);
  }
}

%typemap(freearg) std::ostream& {
  delete $1;
  delete stream$argnum;
}

%typemap(freearg) std::istream& {
  delete $1;
  delete stream$argnum;
}

%{
#include "test.hh"
%}
%include "test.hh"

其核心部分基本上是调用从 Python对象PyFile_AsFile()中获取 a 。有了它,我们就可以构建一个 boost 对象,它使用文件描述符作为适当的源/接收器。FILE*file

唯一剩下的就是在调用发生后清理我们创建的对象(或者如果错误阻止调用发生)。

有了它,我们就可以在 Python 中按预期使用它:

import test
outf=open("out.txt", "w")
inf=open("in.txt", "r")

outf.write("Python\n");

test.writeTo(outf)
test.readFrom(inf)

outf.close()
inf.close()

请注意,缓冲语义可能不会产生您期望的结果,例如在 out.txt 我得到:

你好
Python

这是调用的相反顺序。我们也可以通过在构造 C++ 流之前强制调用我们类型映射file.flush()中的 Python对象来解决这个问题:file

%typemap(in) std::ostream& (boost_ofdstream *stream=NULL) {
  PyObject_CallMethod($input, "flush", NULL);
  FILE *f=PyFile_AsFile($input); // Verify the semantics of this
  if (!f) {
    SWIG_Error(SWIG_TypeError, "File object expected.");
    SWIG_fail;
  }
  else {
    // If threaded incrment the use count
    stream = new boost_ofdstream(fileno(f), io::never_close_handle);
    $1 = new std::ostream(stream);
  }
}

哪个具有所需的行为。

其他注意事项:

  1. 如果您有多线程代码并且 C++ 调用在没有 GIL 的情况下发生,您需要分别调用inPyFile_IncUseCountPyFile_DecUseCountfreearg 类型映射,以确保在您仍在使用文件时没有任何东西可以关闭文件。
  2. 我假设如果给出的对象不是 a 则PyFile_AsFile返回- 文档似乎没有指定任何一种方式,所以你可以使用来确定。NULLfilePyFile_Check
  3. 如果你想变得超级灵活,你可以接受来自 Python 的字符串,并使用/构造一个std::ifstream适当的值来决定在类型映射中采取哪些操作。PyString_CheckPyFile_Check
  4. 一些 C++ 标准库提供了一个ifstream/ofstream构造函数,它将FILE*, 作为扩展。如果你有其中之一,你可以使用它而不是依赖于 boost。
于 2013-09-21T13:08:03.333 回答
1

我最终只是编写了自己的代理类以在界面中使用。所以我用 SWIG 来包装这个类:

 /**
 * Simple class to expose std::streams in the python
 * interface.  works around some issues with trying to directy
 * the file stream objects
 */
class ifstream_proxy: boost::noncopyable{
    public:
        ifstream_proxy(): m_istr(){
            // no op
        }
        virtual ~ifstream_proxy(){
            // no op
        }
        void open(const std::string& fname ){
            m_istr.close(); 
            m_istr.open( fname.c_str(), std::ifstream::in|std::ifstream::binary) ;
        }
        std::istream& stream(){
            return m_istr;
        }
        // TBD: do I want to  add additional stream manipulation functions?
   private: 
        std::ifstream m_istr;
};

并在 python 调用中进行调用

>>> proxy=ifstream_proxy()
>>> proxy.open('file_to_read_from.txt')
>>> readFrom( stream_proxy.stream() )
于 2013-09-18T17:59:38.223 回答
1

我不知道 swig 但假设您需要创建一个可复制的对象,您可能会使用类似的函数逃脱

std::shared_ptr<std::ostream> make_ostream(std::string const& filename) {
    return std::make_shared<std::ofstream>(filename);
}

...然后使用转发函数调用您实际要调用的函数:

void writeTo(std::shared_ptr<std::ostream> stream) {
    if (stream) {
        writeTo(*stream);
    }
}

(如果重载名称会导致问题,您当然可以以不同的方式调用转发函数)。

于 2013-09-17T22:25:34.707 回答
1

我对@Flexo 提供的解决方案做了一些调整

主要的变化是使用了一个由唯一指针拥有的 boost::iostream::stream

其他一些区别:

  • 使用片段来满足 iostream 标头要求
  • 删除了“io”命名空间别名以避免与已使用该名称的代码冲突
  • 利用 fileno() 为 NULL 或无效 FILE* 参数返回 -1
  • 使用 "%#include" "%#else" "%#endif" 所以指令在包装文件中,相同的包装文件可用于 python2 和 python3
  • 包括他关于刷新输出流的建议,因为没有它就违反了最小意外原则
  • 对 python 输出流 flush() 方法调用的结果调用 Py_DECREF - 此结果为 None 因此如果 None 的引用计数增加,世界不会着火,但它更干净,并防止引用计数溢出
%fragment("iostream_header", "header") %{
#include <stdio.h>
#include <memory.h>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
using boost_ofd_stream = boost::iostreams::stream<boost::iostreams::file_descriptor_sink>;
using boost_ifd_stream = boost::iostreams::stream<boost::iostreams::file_descriptor_source>;
%}  

%typemap(in, fragment="iostream_header") std::ostream& (std::unique_ptr<boost_ofd_stream> stream) {
    PyObject *flush_result = PyObject_CallMethod($input, const_cast<char*>("flush"), nullptr);
    if (flush_result) Py_DECREF(flush_result);
%#if PY_VERSION_HEX < 0x03000000
    int fd = fileno(PyFile_AsFile($input));
%#else
    int fd = PyObject_AsFileDescriptor($input);
%#endif
    if (fd < 0) { SWIG_Error(SWIG_TypeError, "File object expected."); SWIG_fail; }
    stream = std::make_unique<boost_ofd_stream>(fd, boost::iostreams::never_close_handle);
    $1 = stream.get();
}   

%typemap(in, fragment="iostream_header") std::istream& (std::unique_ptr<boost_ifd_stream> stream) {
%#if PY_VERSION_HEX < 0x03000000
    int fd = fileno(PyFile_AsFile($input));
%#else
    int fd = PyObject_AsFileDescriptor($input);
%#endif
    if (fd < 0) { SWIG_Error(SWIG_TypeError, "File object expected.");  SWIG_fail; }
    stream = std::make_unique<boost_ifd_stream>(fd, boost::iostreams::never_close_handle);
    $1 = stream.get();
}   

于 2019-09-30T01:05:43.720 回答
0

根据 Dietmar 建议使用共享指针的工作 .i 文件:

%module ptrtest

%include "boost_shared_ptr.i"
%include "std_string.i"

%shared_ptr( std::ostream )

%{
#include <iostream>
#include <fstream>
#include <boost/shared_ptr.hpp>

typedef boost::shared_ptr< std::ostream > ostream_ptr;

ostream_ptr mk_out(const std::string& fname ){
    return ostream_ptr( new std::ofstream( fname.c_str() ) );
}

void writeTo(std::ostream& ostr){
    ostr<<"OK"<<std::endl;
}

%}

namespace std{
    class ostream{
    public:
        // I think we could expose things like write,put here
        // and actually make this class useful from python
    protected:
        // Need to declare this as protected otherwise swig tries 
        // to make/use a public default constructor.
        ostream();
    };
}


// Tell swig to put these into the interface
typedef boost::shared_ptr< std::ostream > ostream_ptr;
ostream_ptr mk_out(const std::string& fname );
void writeTo(std::ostream& ostr);

// Usage:
//>>>ostr=mk_out('/path/to/file.txt')
//>>>writeTo(ostr) # no need to cast/call-function!
于 2013-09-20T13:19:52.713 回答