tutorial.qbk

来自「Boost provides free peer-reviewed portab」· QBK 代码 · 共 1,973 行 · 第 1/5 页

QBK
1,973
字号
So far, we have seen how to expose C++ iterators and ranges to Python.Sometimes we wish to go the other way, though: we'd like to pass aPython sequence to an STL algorithm or use it to initialize an STLcontainer. We need to make a Python iterator look like an STL iterator.For that, we use `stl_input_iterator<>`. Consider how we mightimplement a function that exposes `std::list<int>::assign()` toPython:[c++]    template<typename T>    void list_assign(std::list<T>& l, object o) {        // Turn a Python sequence into an STL input range        stl_input_iterator<T> begin(o), end;        l.assign(begin, end);    }    // Part of the wrapper for list<int>    class_<std::list<int> >("list_int")        .def("assign", &list_assign<int>)        // ...        ;Now in Python, we can assign any integer sequence to `list_int` objects:[python]    x = list_int();    x.assign([1,2,3,4,5])[endsect][section:exception Exception Translation]All C++ exceptions must be caught at the boundary with Python code. Thisboundary is the point where C++ meets Python. Boost.Python provides adefault exception handler that translates selected standard exceptions,then gives up:    raise RuntimeError, 'unidentifiable C++ Exception'Users may provide custom translation. Here's an example:    struct PodBayDoorException;    void translator(PodBayDoorException const& x) {        PyErr_SetString(PyExc_UserWarning, "I'm sorry Dave...");    }    BOOST_PYTHON_MODULE(kubrick) {         register_exception_translator<              PodBayDoorException>(translator);         ...[endsect][section:techniques General Techniques]Here are presented some useful techniques that you can use while wrapping code with Boost.Python.[section Creating Packages]A Python package is a collection of modules that provide to the user a certainfunctionality. If you're not familiar on how to create packages, a goodintroduction to them is provided in the[@http://www.python.org/doc/current/tut/node8.html Python Tutorial].But we are wrapping C++ code, using Boost.Python. How can we provide a nicepackage interface to our users? To better explain some concepts, let's workwith an example.We have a C++ library that works with sounds: reading and writing variousformats, applying filters to the sound data, etc. It is named (conveniently)[^sounds].  Our library already has a neat C++ namespace hierarchy, like so:    sounds::core    sounds::io    sounds::filtersWe would like to present this same hierarchy to the Python user, allowing himto write code like this:    import sounds.filters    sounds.filters.echo(...) # echo is a C++ functionThe first step is to write the wrapping code. We have to export each moduleseparately with Boost.Python, like this:    /* file core.cpp */    BOOST_PYTHON_MODULE(core)    {        /* export everything in the sounds::core namespace */        ...    }    /* file io.cpp */    BOOST_PYTHON_MODULE(io)    {        /* export everything in the sounds::io namespace */        ...    }    /* file filters.cpp */    BOOST_PYTHON_MODULE(filters)    {        /* export everything in the sounds::filters namespace */        ...    }Compiling these files will generate the following Python extensions:[^core.pyd], [^io.pyd] and [^filters.pyd].[note The extension [^.pyd] is used for python extension modules, whichare just shared libraries.  Using the default for your system, like [^.so] forUnix and [^.dll] for Windows, works just as well.]Now, we create this directory structure for our Python package:[presounds/    \_\_init\_\_.py    core.pyd    filters.pyd    io.pyd]The file [^\_\_init\_\_.py] is what tells Python that the directory [^sounds/] isactually a Python package. It can be a empty file, but can also perform somemagic, that will be shown later.Now our package is ready. All the user has to do is put [^sounds] into his[@http://www.python.org/doc/current/tut/node8.html#SECTION008110000000000000000 PYTHONPATH]and fire up the interpreter:[python]    >>> import sounds.io    >>> import sounds.filters    >>> sound = sounds.io.open('file.mp3')    >>> new_sound = sounds.filters.echo(sound, 1.0)Nice heh?This is the simplest way to create hierarchies of packages, but it is not veryflexible. What if we want to add a ['pure] Python function to the filterspackage, for instance, one that applies 3 filters in a sound object at once?Sure, you can do this in C++ and export it, but why not do so in Python? Youdon't have to recompile the extension modules, plus it will be easier to writeit.If we want this flexibility, we will have to complicate our package hierarchy alittle. First, we will have to change the name of the extension modules:[c++]    /* file core.cpp */    BOOST_PYTHON_MODULE(_core)    {        ...        /* export everything in the sounds::core namespace */    }Note that we added an underscore to the module name. The filename will have tobe changed to [^_core.pyd] as well, and we do the same to the other extension modules.Now, we change our package hierarchy like so:[presounds/    \_\_init\_\_.py    core/        \_\_init\_\_.py        _core.pyd    filters/        \_\_init\_\_.py        _filters.pyd    io/        \_\_init\_\_.py        _io.pyd]Note that we created a directory for each extension module, and added a\_\_init\_\_.py to each one. But if we leave it that way, the user will have toaccess the functions in the core module with this syntax:[python]    >>> import sounds.core._core    >>> sounds.core._core.foo(...)which is not what we want. But here enters the [^\_\_init\_\_.py] magic: everythingthat is brought to the [^\_\_init\_\_.py] namespace can be accessed directly by theuser.  So, all we have to do is bring the entire namespace from [^_core.pyd]to [^core/\_\_init\_\_.py]. So add this line of code to [^sounds/core/\_\_init\_\_.py]:    from _core import *We do the same for the other packages. Now the user accesses the functions andclasses in the extension modules like before:    >>> import sounds.filters    >>> sounds.filters.echo(...)with the additional benefit that we can easily add pure Python functions toany module, in a way that the user can't tell the difference between a C++function and a Python function. Let's add a ['pure] Python function,[^echo_noise], to the [^filters] package. This function applies both the[^echo] and [^noise] filters in sequence in the given [^sound] object. Wecreate a file named [^sounds/filters/echo_noise.py] and code our function:    import _filters    def echo_noise(sound):        s = _filters.echo(sound)        s = _filters.noise(sound)        return sNext, we add this line to [^sounds/filters/\_\_init\_\_.py]:    from echo_noise import echo_noiseAnd that's it. The user now accesses this function like any other functionfrom the [^filters] package:    >>> import sounds.filters    >>> sounds.filters.echo_noise(...)[endsect][section Extending Wrapped Objects in Python]Thanks to Python's flexibility, you can easily add new methods to a class,even after it was already created:    >>> class C(object): pass    >>>    >>> # a regular function    >>> def C_str(self): return 'A C instance!'    >>>    >>> # now we turn it in a member function    >>> C.__str__ = C_str    >>>    >>> c = C()    >>> print c    A C instance!    >>> C_str(c)    A C instance!Yes, Python rox. :-)We can do the same with classes that were wrapped with Boost.Python. Supposewe have a class [^point] in C++:[c++]    class point {...};    BOOST_PYTHON_MODULE(_geom)    {        class_<point>("point")...;    }If we are using the technique from the previous session,[link python.creating_packages Creating Packages], we can code directlyinto [^geom/\_\_init\_\_.py]:[python]    from _geom import *    # a regular function    def point_str(self):        return str((self.x, self.y))    # now we turn it into a member function    point.__str__ = point_str[*All] point instances created from C++ will also have this member function!This technique has several advantages:* Cut down compile times to zero for these additional functions* Reduce the memory footprint to virtually zero* Minimize the need to recompile* Rapid prototyping (you can move the code to C++ if required without changing the interface)You can even add a little syntactic sugar with the use of metaclasses. Let'screate a special metaclass that "injects" methods in other classes.    # The one Boost.Python uses for all wrapped classes.    # You can use here any class exported by Boost instead of "point"    BoostPythonMetaclass = point.__class__    class injector(object):        class __metaclass__(BoostPythonMetaclass):            def __init__(self, name, bases, dict):                for b in bases:                    if type(b) not in (self, type):                        for k,v in dict.items():                            setattr(b,k,v)                return type.__init__(self, name, bases, dict)    # inject some methods in the point foo    class more_point(injector, point):        def __repr__(self):            return 'Point(x=%s, y=%s)' % (self.x, self.y)        def foo(self):            print 'foo!'Now let's see how it got:    >>> print point()    Point(x=10, y=10)    >>> point().foo()    foo!Another useful idea is to replace constructors with factory functions:    _point = point    def point(x=0, y=0):        return _point(x, y)In this simple case there is not much gained, but for constructurs withmany overloads and/or arguments this is often a great simplification, againwith virtually zero memory footprint and zero compile-time overhead forthe keyword support.[endsect][section Reducing Compiling Time]If you have ever exported a lot of classes, you know that it takes quite a goodtime to compile the Boost.Python wrappers. Plus the memory consumption caneasily become too high. If this is causing you problems, you can split theclass_ definitions in multiple files:[c++]    /* file point.cpp */    #include <point.h>    #include <boost/python.hpp>    void export_point()    {        class_<point>("point")...;    }    /* file triangle.cpp */    #include <triangle.h>    #include <boost/python.hpp>    void export_triangle()    {        class_<triangle>("triangle")...;    }Now you create a file [^main.cpp], which contains the [^BOOST_PYTHON_MODULE]macro, and call the various export functions inside it.    void export_point();    void export_triangle();    BOOST_PYTHON_MODULE(_geom)    {        export_point();        export_triangle();    }Compiling and linking together all this files produces the same result as theusual approach:    #include <boost/python.hpp>    #include <point.h>    #include <triangle.h>    BOOST_PYTHON_MODULE(_geom)    {        class_<point>("point")...;        class_<triangle>("triangle")...;    }but the memory is kept under control.This method is recommended too if you are developing the C++ library andexporting it to Python at the same time: changes in a class will only demandthe compilation of a single cpp, instead of the entire wrapper code.[note If you're exporting your classes with [@../../../../pyste/index.html Pyste],take a look at the [^--multiple] option, that generates the wrappers invarious files as demonstrated here.][note This method is useful too if you are getting the error message['"fatal error C1204:Compiler limit:internal structure overflow"] when compilinga large source file, as explained in the [@../../../v2/faq.html#c1204 FAQ].][endsect][endsect] [/ General Techniques]

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?