[C++-sig] Conversion of python files to C++ ostreams
Christopher Bruns
cmbruns at stanford.edu
Mon Mar 29 22:28:13 CEST 2010
I am trying to coax boost.python to automatically convert python files
to C++ std::ostreams, for methods that take arguments of type
"std::ostream&". I have made some progress, and I could use some
advice on how to go further.
I created a derived class of std::ostream, called FilestarOstream,
which takes a std::FILE* as it constructor argument. Then I created a
conversion from "python file" to the FilestarOstream class. Now my
python module can automatically convert a python file to a
FilestarOstream for any method that takes a "const FilestarOstream&"
as an argument. Ideally I would prefer to automatically convert a
python file for any method that takes a (non-const) "std::ostream&" as
an argument.
I have included my simplified source code below
My questions:
1 - Is there an easier way to do this? Does boost.python have a
built in incantation for wrapping methods that take a std::ostream
reference, to take python files?
2 - Is there a way to make the type conversion work for non-const
reference arguments? (see print_hello_wrapper2() below)
3 - Why does my print_hello_wrapper3() not work? It takes a const
std::ostream reference argument. I can pass it a FileStarOstream, so
python knows how to convert a FileStarOstream to a std::ostream. Plus
python knows how to convert a python file to a const FileStarOstream
reference. So why can't it convert a python file to a const ostream
reference?
4 - In my FilestarOstream_from_pyfile::convertible method, is there
a way to check that the object is not only a file, but that it is also
writable?
5 - The PyFile_AsFile docs say "If the caller will ever use the
returned FILE* object while the GIL is released it must also call the
PyFile_IncUseCount() and PyFile_DecUseCount() functions described
below as appropriate." What is "the GIL". If I call
PyFile_IncUseCount() when I construct the FileStarOstream, when would
I call the corresponding PyFile_DecUseCount()?
Thanks in advance for any helpful tips.
##################
//// begin test.hpp ////////
#ifndef TEST_API_PYFILE_OSTREAM_H_
#define TEST_API_PYFILE_OSTREAM_H_
#include <iostream>
// This is the sort of method I wish to wrap
std::ostream& print_hello(std::ostream& os, int foo);
// FilestarOstream is intended to aid python wrapping of methods that
// take ostream& args by converting an opaque C++ FILE* to a C++ ostream.
// This is just one part of the (yet unproven) task of automatically
// wrapping methods that take an ostream& as an argument.
class FilestarOstream : public std::ostream
{
public:
// std::ostreams can be constructed using an std::streambuf
// so the first step is to construct a specialized std::streambuf
// based on a FILE*
// Adapted from
// http://mail.python.org/pipermail/cplusplus-sig/2002-June/000896.html
class std_obuf: public std::streambuf
{
public:
std_obuf(std::FILE* file): m_file(file) {}
std::FILE* updFilestar() {return m_file;}
protected:
std::streambuf::int_type overflow(std::streambuf::int_type c) {
return std::fputc(c, m_file) == EOF ?
std::streambuf::traits_type::eof() : c;
}
std::FILE* m_file;
};
FilestarOstream(std::FILE* fp)
: buf(fp), std::ostream(&buf) {}
// Default constructor uses stdout
FilestarOstream()
: buf(stdout), std::ostream(&buf) {}
std::FILE* updFilestar() {return buf.updFilestar();}
protected:
std_obuf buf;
};
#endif // TEST_API_PYFILE_OSTREAM_H_
//////////// end test.hpp //////////
###################
//// test.cpp ///
#include "test.hpp"
std::ostream& print_hello(std::ostream& os, int foo) {
os << "Hello, foo = " << foo << std::endl;
return os;
}
/// end test.cpp ///
######################
// boost.python wrapping code
#include "boost/python.hpp"
#include "test.hpp"
namespace bp = boost::python;
// Three ways of wrapping print_hello() to take a python file argument,
// only one of which works:
// print_hello_wrapper1() works, but I wish I did not need a wrapper at all
std::ostream& print_hello_wrapper1(const FilestarOstream& os, int foo) {
FilestarOstream& os_nc = const_cast<FilestarOstream&>(os);
return print_hello(os_nc, foo);
}
// A wrapper that takes a non-const reference does not work
// Boost.Python.ArgumentError ...
std::ostream& print_hello_wrapper2(FilestarOstream& os, int foo) {
return print_hello(os, foo);
}
// A wrapper that takes the a const reference to the base class,
ostream, also fails
// Boost.Python.ArgumentError ...
std::ostream& print_hello_wrapper3(const std::ostream& os, int foo) {
std::ostream& os_nc = const_cast<std::ostream&>(os);
return print_hello(os_nc, foo);
}
// Define automatic interconversion of python file <==> FilestarOstream
struct FilestarOstream_to_pyfile // untested...
{
static PyObject* convert(FilestarOstream& os)
{return PyFile_FromFile(os.updFilestar(), "FilestarOstream", "w", NULL);}
};
struct FilestarOstream_from_pyfile
{
FilestarOstream_from_pyfile() {
bp::converter::registry::push_back(
&convertible, &construct, bp::type_id<FilestarOstream>());
}
static void* convertible(PyObject* obj_ptr)
{
if( !PyFile_Check( obj_ptr ) ) {return 0;}
// TODO - is there a way to check whether file is writable also?
return obj_ptr;
}
static void construct(
PyObject* obj_ptr,
bp::converter::rvalue_from_python_stage1_data* data)
{
// TODO - the PyFile_AsFile docs say:
// "If the caller will ever use the returned FILE* object
while the GIL is released it must also call the PyFile_IncUseCount()
and PyFile_DecUseCount() functions described below as appropriate."
std::FILE* file = PyFile_AsFile(obj_ptr);
typedef
bp::converter::rvalue_from_python_storage<FilestarOstream>
filestarOstream_storage;
void* const storage =
reinterpret_cast<filestarOstream_storage*>(data)->storage.bytes;
new (storage) FilestarOstream(file);
data->convertible = storage;
}
};
BOOST_PYTHON_MODULE(test_mod){
// wrap std::ostream, so python will know how to convert
FilestarOstream to ostream
bp::class_< std::ostream, boost::noncopyable >( "std_ostream",
bp::no_init );
{ //::FilestarOstream
typedef bp::class_< FilestarOstream, bp::bases< std::ostream
>, boost::noncopyable > FilestarOstream_exposer_t;
FilestarOstream_exposer_t FilestarOstream_exposer =
FilestarOstream_exposer_t( "FilestarOstream", bp::init< std::FILE *
>(( bp::arg("fp") )) );
bp::scope FilestarOstream_scope( FilestarOstream_exposer );
FilestarOstream_exposer.def( bp::init< >() );
}
// wrap print_hello, using one of those wrappers defined above
bp::def(
"print_hello",
// choose your wrapper
// &print_hello, // no wrapper at all, Boost.Python.ArgumentError
&print_hello_wrapper1, // OK
// &print_hello_wrapper2, // non-const arg =>
Boost.Python.ArgumentError
// &print_hello_wrapper3, // base class arg =>
Boost.Python.ArgumentError
( bp::arg("os"), bp::arg("foo") ),
bp::return_internal_reference<2>());
// Register conversion from python file to FilestarOstream
bp::to_python_converter<FilestarOstream, FilestarOstream_to_pyfile>;
FilestarOstream_from_pyfile();
}
// end boost.python wrapping code
##### python test program ######
import test_mod
import sys
# First test using explicit FilestarOstream
# succeeds with all wrappers,
# including unwrapped print_hello
stream1 = test_mod.FilestarOstream()
test_mod.print_hello(stream1, 1)
# Remaining tests that use python files
# only work with wrappers that take an
# argument of const FilestarOstream&
stream2 = sys.stdout
test_mod.print_hello(stream2, 2)
stream3 = open('test.txt', "w")
test_mod.print_hello(stream3, 3)
stream3.close()
##### end test program ####
More information about the Cplusplus-sig
mailing list