【问题标题】:Technique for using std::ifstream, std::ofstream in python via SWIG?通过 SWIG 在 python 中使用 std::ifstream、std::ofstream 的技术?
【发布时间】: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 类, 它对制作我需要的文件流没有帮助。

【问题讨论】:

    标签: c++ python file-io swig


    【解决方案1】:

    我对这个问题的首选解决方案是让 Python 开发人员看到的界面尽可能“Pythonic”。在这种情况下,将接受 python file 对象作为您的 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"
    

    它的核心部分基本上是调用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);
      }
    }
    

    哪个具有所需的行为。

    其他说明:

    1. 如果您有多线程代码并且在没有 GIL 的情况下进行 C++ 调用,您需要分别在 in 和 freearg 类型映射中调用 PyFile_IncUseCountPyFile_DecUseCount 以确保在您执行时没有任何东西可以关闭文件'仍在使用它。
    2. 我假设 PyFile_AsFile 返回 NULL 如果它给出的对象不是 file - 文档似乎没有指定任何一种方式,所以你可以使用 PyFile_Check 确定.
    3. 如果您想要超级灵活,您可以接受来自 Python 的字符串,并根据需要使用 PyString_Check/PyFile_Check 构造一个 std::ifstream 来决定在类型映射中采取哪些操作。
    4. 一些 C++ 标准库提供了一个 ifstream/ofstream 构造函数,它采用 FILE* 作为扩展。如果您有其中之一,则可以使用它而不是依赖 boost。

    【讨论】:

    • 不幸的是,PyFile_AsFile 在 python 3 中不再可用
    • 可能最简单的解决方案是使用 boost iostreams 编写一个 ifstream/ofstream 来代理 Python 的所有内容
    • PyFile_AsFile 不再可用,但我相信PyObject_AsFileDescriptor 具有相同的功能。它为您提供文件描述符而不是文件指针,因此您必须在其上使用fdopen
    • @HenriMenke 实际上,对于这种情况,这会更简单,因为无论如何我只是在FILE* 上调用fileno。您可能想在此处添加答案:stackoverflow.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!
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-10-12
          • 2012-07-20
          • 2010-10-23
          • 2020-11-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-03-01
          相关资源
          最近更新 更多