通过 SWIG 在 python 中使用 std::ifstream、std::ofstream 的技术?
Posted
技术标签:
【中文标题】通过 SWIG 在 python 中使用 std::ifstream、std::ofstream 的技术?【英文标题】:Technique for using std::ifstream, std::ofstream in python via SWIG? 【发布时间】:2013-09-17 22:18:12 【问题描述】:有没有办法通过 swig 在 python 中使用std::[io]fstream
'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
类,
它对制作我需要的文件流没有帮助。
【问题讨论】:
【参考方案1】:我对这个问题的首选解决方案是让 Python 开发人员看到的界面尽可能“Pythonic”。在这种情况下,将接受 python file
对象作为您的 ostream
和 istream
参数。
为了实现这一点,我们必须编写一个类型映射来设置每个映射。
我编写了以下头文件来演示这一点:
#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"
它的核心部分基本上是调用PyFile_AsFile()
从Python 的file
对象中获取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()
请注意,缓冲语义可能不会产生您预期的结果,例如在 out.txt 我得到:
你好 蟒蛇
这是调用的相反顺序。我们也可以通过在构造 C++ 流之前强制调用我们类型映射中的 Python file
对象上的 file.flush()
来解决这个问题:
%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);
哪个具有所需的行为。
其他说明:
-
如果您有多线程代码并且在没有 GIL 的情况下进行 C++ 调用,您需要分别在 in 和 freearg 类型映射中调用
PyFile_IncUseCount
和 PyFile_DecUseCount
以确保在您执行时没有任何东西可以关闭文件'仍在使用它。
我假设 PyFile_AsFile
返回 NULL
如果它给出的对象不是 file
- 文档似乎没有指定任何一种方式,所以你可以使用 PyFile_Check
确定.
如果您想要超级灵活,您可以接受来自 Python 的字符串,并根据需要使用 PyString_Check
/PyFile_Check
构造一个 std::ifstream
来决定在类型映射中采取哪些操作。
一些 C++ 标准库提供了一个 ifstream
/ofstream
构造函数,它采用 FILE*
作为扩展。如果您有其中之一,则可以使用它而不是依赖 boost。
【讨论】:
不幸的是,PyFile_AsFile 在 python 3 中不再可用 可能最简单的解决方案是使用 boost iostreams 编写一个 ifstream/ofstream 来代理 Python 的所有内容PyFile_AsFile
不再可用,但我相信PyObject_AsFileDescriptor
具有相同的功能。它为您提供文件描述符而不是文件指针,因此您必须在其上使用fdopen
。
@HenriMenke 实际上,对于这种情况,这会更简单,因为无论如何我只是在FILE*
上调用fileno
。您可能想在此处添加答案:***.com/q/16130268/168175
我根据@HenriMenke 的建议为 Python 3 添加了(新的、未经测试的)代码。【参考方案2】:
我不知道 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);
(如果重载名称会导致问题,当然可以以不同的方式调用转发函数)。
【讨论】:
这个想法非常有效,但我收到内存泄漏警告,因为接口不知道如何释放(指向)shared_ptr 的(指针)。 Answer here 解决内存泄漏问题。【参考方案3】:我最终只是编写了自己的代理类以在接口中使用。 所以我用 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() )
【讨论】:
【参考方案4】:我对@Flexo 提供的解决方案做了一些调整
主要的变化是使用了一个唯一指针拥有的 boost::iostream::stream
其他一些区别:
使用片段满足 iostream 标头要求 删除了“io”命名空间别名以避免与已使用该名称的代码冲突 利用 fileno() 为 NULL 或无效 FILE* 参数返回 -1 使用 "%#include" "%#else" "%#endif" 以便指令位于 wrap 文件中,并且 python2 和 python3 可以使用相同的 wrap 文件 包括他关于刷新输出流的建议,因为没有它就违反了最小意外原则 对 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();
【讨论】:
【参考方案5】:根据 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!
【讨论】:
以上是关于通过 SWIG 在 python 中使用 std::ifstream、std::ofstream 的技术?的主要内容,如果未能解决你的问题,请参考以下文章
使用 SWIG 在 Python 中公开 std::list 成员
包装 std::vector 的 std::vectors,C++ SWIG Python