通过SWIG在python中使用std :: ifstream,std :: ofstream的技巧?

Dav*_*ave 7 c++ python file-io swig

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

我有一个c-class,其功能如下:

void readFrom(std::istream& istr);
void writeTo(std::ostream& ostr);
Run Code Online (Sandbox Code Playgroud)

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

我尝试过像这样的功能

std::ostream& make_ostream(const std::string& file_name){
    return std::ofstream( file_name.c_str() );
}
Run Code Online (Sandbox Code Playgroud)

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

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

Fle*_*exo 6

我对这个问题的首选解决方案是将接口暴露给Python开发人员尽可能"Pythonic".在这种情况下,这将是接受蟒蛇file对象作为你ostreamistream参数.

为了实现这一点,我们必须编写一个typemap来设置每个映射.

我编写了以下头文件来演示这个实际操作:

#ifndef TEST_HH
#define TEST_HH
#include <iosfwd>

void readFrom(std::istream& istr);
void writeTo(std::ostream& ostr);
#endif
Run Code Online (Sandbox Code Playgroud)

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

#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());
}
Run Code Online (Sandbox Code Playgroud)

有了这个,我能够使用以下方法成功包装:

%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"
Run Code Online (Sandbox Code Playgroud)

这个核心部分基本上PyFile_AsFile()FILE*从Python file对象中获取一个.然后,我们可以构造一个boost对象,该对象使用文件描述符作为源/接收器.

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

有了这个,我们就可以在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()
Run Code Online (Sandbox Code Playgroud)

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

你好
Python

这是与电话相反的顺序.我们也可以通过在构造C++流之前强制调用我们的typemap中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);
  }
}
Run Code Online (Sandbox Code Playgroud)

哪个有所需的行为.

其他说明:

  1. 如果你已经有了多线程代码和C++调用没有GIL发生的事情,你需要调用PyFile_IncUseCount,并PyFile_DecUseCount分别在中和freearg typemaps,以确保没有任何东西可以,而你还在使用它关闭文件.
  2. 我假定PyFile_AsFile返回NULL如果它给出的对象不是file-文档似乎并没有指定任何一种方式,因此您可以使用PyFile_Check以确保万无一失.
  3. 如果你想要超级灵活,你可以接受来自Python的字符串并std::ifstream使用PyString_Check/ PyFile_Check来构造一个适当的用来决定在typemap中采取哪个动作.
  4. 一些C++标准库提供了一个ifstream/ ofstream构造函数,它FILE*作为扩展.如果你有其中一个你可以使用它而不是依靠提升.


Die*_*ühl 1

我不知道 swig 但假设你需要创建一个可复制的对象,你可能会使用像这样的函数

std::shared_ptr<std::ostream> make_ostream(std::string const& filename) {
    return std::make_shared<std::ofstream>(filename);
}
Run Code Online (Sandbox Code Playgroud)

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

void writeTo(std::shared_ptr<std::ostream> stream) {
    if (stream) {
        writeTo(*stream);
    }
}
Run Code Online (Sandbox Code Playgroud)

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