Как импортировать ostringstream, используя ctypes в python3? - PullRequest
1 голос
/ 07 июля 2019

Я пишу анализатор для естественного языка, и у меня есть оболочка кода C ++ в Python 3, созданная с помощью swig.Я хотел бы использовать функцию, которая является своего рода потоковым писателем, и в качестве параметра она принимает std::ostream & os.Поэтому я думаю, что это сработало бы, если бы я как-то импортировал ostringstream (читай, какой lib.so я должен использовать в моем ctypes.CDLL) в своем коде Python, затем передал бы его этой функции, чтобы он не вызывал create_stream_writer(stream), а затем использовалstream.str () чтобы получить строку.Есть ли способ сделать это с помощью ctypes или любой другой библиотеки?Я использую Docker-контейнер под управлением Ubuntu 18.04, код python3.6

должен выглядеть следующим образом:

def analyse(text, config):
    reader = PlainTextReader.create_string_reader(text, config)
    stream = ctypes.ostringstream() # some magic hear
    writer = TokenWriter.create_stream_writer('plain', stream, reader.tagset())

    for sentence in sentences(reader):
        writer.write_sentence(sentence)
    return stream.str()

Ответы [ 2 ]

2 голосов
/ 09 июля 2019

Вы можете сделать это (и сделать это хорошо для разработчиков Python).Этот ответ, по сути, является версией моего старого ответа на упаковку iostreams .

на языке Python 3. Чтобы упростить ситуацию, я использовал библиотеку boost iostreams.Если вы не можете / не используете boost, вы можете написать все это из стандартных библиотечных компонентов C ++, это просто намного более многословно.

Я также нацелился выше, чем отображение io.StringIO в std::stringstreamи вместо этого ушел для отображения любого объекта типа «файл как» Python на любой iostream.То есть мы используем цель, чтобы использовать типизацию утили на объекте Python, чтобы просто вызывать read() и write() разумно, как и когда это необходимо для наших потоковых объектов C ++.

%module test

%{
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/categories.hpp> 

// This is just a helper that we can use with boost iostreams to proxy everything to a Python object    
struct python_stream_device {
  typedef char char_type;
  typedef boost::iostreams::bidirectional_device_tag category;

  std::streamsize read(char* s, std::streamsize n) {
    PyObject *p = PyObject_CallMethod(o, "read", "l", static_cast<long int>(n));
    if (PyErr_Occurred()) {
      // TODO: throw a C++ exception to back out of wherever we are and then re-throw the Python one...
      assert(false);
    }
    assert(p);
    char *ptr = nullptr;
    Py_ssize_t len = 0;
    PyObject *str = PyUnicode_AsUTF8String(p);
    PyBytes_AsStringAndSize(str, &ptr, &len);
    if (PyErr_Occurred()) {
      assert(false); // Let's just pretend this is error handlng...
    }

    memcpy(s, ptr, len);
    Py_DECREF(str);
    Py_DECREF(p);
    return len;
  }

  std::streamsize write(const char* s, std::streamsize n) {
    PyObject *ret = PyObject_CallMethod(o, "write", "s#", s, static_cast<Py_ssize_t>(n));
    if (PyErr_Occurred()) {
      // See above
      assert(false);
    }
    std::streamsize r = PyLong_AsSsize_t(ret);
    Py_DECREF(ret);
    return r;
  }

  // Using this means we can rely on the default synthesised operator= + copy ctor etc. and saves us some code.
  swig::SwigPtr_PyObject o;

  python_stream_device(PyObject *o) : o(o) {}
};

typedef boost::iostreams::stream<python_stream_device> python_stream;

%}

// Here is the stuff that wraps it neatly
%typemap(in) std::iostream& (python_stream tmp) {
  // Writing the typemap this way lets us get RAII semantics despite the goto in the SWIG macros in the simplest way
  tmp.open(python_stream_device($input));  
  $1 = &tmp;
}

// We can just use the same typemaps for other cases too:
%apply std::iostream& { std::istream&, std::ostream& }; 


// Below is just for testing:    
%{
#include <iostream>
%}

%inline %{
  // This is the function you want to call
  void fun1(std::ostream& out) {
    assert(out.good());
    out << "Hello world, from C++";
    assert(out.good());
  }

  // This one is here for completeness because once you've got this far you may as well support this too.
  void fun2(std::istream& in) {
    std::string tmp;
    //in >> tmp;
    std::getline(in, tmp);
    assert(in.good());
    std::cout << "fun2 got: " << tmp << std::endl;
  }
%}

Этого достаточно, чтобы вызатем можно использовать такой Python:

import io
import test

i=io.StringIO()
test.fun1(i)
print('After fun1: %s' % i.getvalue())

i=io.StringIO('hello world, from Python!\n')
test.fun2(i)
0 голосов
/ 09 июля 2019

Как отметили Марк Толонен в комментариях, это невозможно сделать с помощью ctypes.Поэтому я просто написал функцию на С ++, которая делает все, что мне нужно, а затем создал оболочку, используя SWIG.Потому что использование typemap в SWIG для отображения StringIO (Python) в ostreingstream (C ++) выглядит как черная магия, и я не смог найти способ сделать это.

...