6

Using boost python I need create nested namespace.

Assume I have following cpp class structure:

namespace a
{
    class A{...}
    namespace b
    {
         class B{...}
    }
}

Obvious solution not work:

BOOST_PYTHON_MODULE( a ) {
    boost::python::class_<a::A>("A")
     ...
    ;
    BOOST_PYTHON_MODULE(b){
        boost::python::class_<a::b::B>("B")
        ...
    ;
    }
}

It causes compile-time error: linkage specification must be at global scope

Is there any way to declare class B that would be accessed from Python as a.b.B?

Dewfy
  • 23,277
  • 13
  • 73
  • 121

2 Answers2

12

What you want is a boost::python::scope.

Python has no concept of 'namespaces', but you can use a class very much like a namespace:

#include <boost/python/module.hpp>
#include <boost/python/class.hpp>
#include <boost/python/scope.hpp>
using namespace boost::python;

namespace a
{
    class A{};

    namespace b
    {
         class B{};
    }
}

class DummyA{};
class DummyB{};

BOOST_PYTHON_MODULE(mymodule)
{
    // Change the current scope 
    scope a
        = class_<DummyA>("a")
        ;

    // Define a class A in the current scope, a
    class_<a::A>("A")
        //.def("somemethod", &a::A::method)
        ;

    // Change the scope again, a.b:
    scope b
        = class_<DummyB>("b")
        ;

    class_<a::b::B>("B")
        //.def("somemethod", &a::b::B::method)
        ;
}

Then in python, you have:

#!/usr/bin/env python
import mylib

print mylib.a,
print mylib.a.A
print mylib.a.b
print mylib.a.b.B

All a, a.A, a.b and a.b.B are actually classes, but you can treat a and a.b just like namespaces - and never actually instantiate them

James
  • 24,676
  • 13
  • 84
  • 130
  • 1
    I was pleasantly surprised when I tried this - I wasn't actually expecting nesting scopes to work! Boost python is really very neat sometimes. – James Nov 17 '11 at 22:21
  • What if I want `mylib.a`, `mylib.a.A`, `mylib.b`, and `mylib.b.B`? How do I "exit" the "a" scope? – robert Dec 19 '11 at 22:09
  • 7
    @robert boost::python::scope objects use RAII, so add extra {} to change the lifetime of the scope objects; when the scope object is destroyed it restores the namespace that was present when it was created. – James Dec 19 '11 at 23:01
  • @Autopulated: Your construction is different from that in the docs. In the docs, the scope object is created on assignment with an arbitrary name. Then the name used to create the subclass wrapper is the name of the enclosing class. What you do is create a namespace and then create a scope object with the same name, and then use that namespace/scope object name when defining the wrapper for the subclass, i.e. `class_`. Can you explain the rationale for that? – Faheem Mitha Apr 11 '12 at 08:05
  • @FaheemMitha: Because I didn't know that constructor existed! (And I don't see it in [the documentation?](http://www.boost.org/doc/libs/1_49_0/libs/python/doc/v2/scope.html)) Using classes mirrors the way it works in python, so it's the only way I ever tried. – James Apr 11 '12 at 10:10
  • @Autopulated: See the example at the bottom - `BOOST_PYTHON_MODULE(nested)` etc. I actually don't understand why your example works, and was hoping you could explain. I'm also puzzled by your reply, since your answer http://stackoverflow.com/a/7301553/350713 uses the standard construction. – Faheem Mitha Apr 11 '12 at 18:42
  • @FaheemMitha Wait, how is that different to this? Don't be confused by the use of `a` and `A` to refer to a namespace, and a class inside that namespace --- and `DummyA` to be the c++ "tag" class of the python 'namespace' class `a`! – James Apr 11 '12 at 19:11
  • @Autopulated: Heh, I guess I just don't know enough about C++ and/or the Python C API. I'm not sure how they are different, that is my question. The two look different to me. Can you elaborate on "DummyA to be the c++ "tag" class of the python 'namespace' class a"? To start with, what is a "tag" class? My basic confusion lies in your simultaneous use of a namespace and a class, as in `scope a = class_("a");`. Here `a` is already a namespace, but you are declaring it as a scope object, which looks weird to me. I guess it must be Ok since the compiler passes it, but I don't see why. – Faheem Mitha Apr 11 '12 at 20:01
  • @FaheemMitha Ah, right. So, since there's no such thing as a namespace in python, boost python simulates namespaces using classes. So a c++ namespace will correspond to a particular class type in python. So, in c++, we need some type that corresponds to the python type (the 'namespace' class), and this is what `DummyA` is used for. It's necessary because c++ namespaces aren't types, and you can't use them in templates --- which is how the boost-python API distinguishes between the python types that it creates. – James Apr 11 '12 at 22:24
  • @FaheemMitha (continued): In essence the *type* `DummyA` is used as a sort-of name for the corresponding type in python (which is a true class type). It's actually quite common to use classes like this to name things when using c++ templates and template-metaprogramming. They are often called "tag types" or "tag classes", but are not to be confused with the other sort of [tag-classes](http://stackoverflow.com/questions/612328/difference-between-struct-and-typedef-struct-in-c) which are a holdover from c. – James Apr 11 '12 at 22:29
  • @FaheemMitha: As an example of using tag classes, [here's the documentation](http://www.boost.org/doc/libs/1_39_0/libs/multi_index/doc/tutorial/basics.html#tagging) of how they're using in `boost::multi_index` in order to refer to the different indices on a multi-indexed container. – James Apr 11 '12 at 22:29
  • @Autopulated: Thanks for the explanation, but let me try to be more specific, taking it from the top. You define the namespace `a`. Then you define a "scope" object `a` as a "sort-of assignment" to the wrapper for `DummyA`. I'm fuzzy what this scope object is, but it inherits from `boost::python::object`. Anyway, I'm surprised this works, since `a` is already in use. You then start to use syntax like `a::A`, which only makes sense if `A` is in `a` and (I think), `a` is an type. – Faheem Mitha Apr 12 '12 at 08:31
  • @Autopulated: Now `A` *is* in the namespace `a`, which is *not* a type, but `A` isn't in `DummyA`, which *is* a type. I guess the point is that `a` is somehow being identified with `DummyA` via that assignment. Can you clarify what is going on here? – Faheem Mitha Apr 12 '12 at 08:33
  • @FaheemMitha So, the mapping from c++ namespaces to python namespaces is arbitrary, in fact boost python *has no way of knowing* about c++ namespaces (they don't have types, and you can't instantiate them, or take their address). The python 'namespace' (classes) are created using `scope arbitrary_name_for_boostpython_scope_object = class_("NameOfScopeInPython")` So the python code would look exactly the same even if there were no namespaces in the c++ --- it's possible to create extra namespaces, or flatten namespaces out just by using the boost python API differently. – James Apr 12 '12 at 08:55
  • Does somebody know how to access the default scope from python i.e. if I don't create scope object in boost::python at all? – vehsakul Jun 19 '17 at 12:49
10

The trick with dummy classes is quite fine, but doesn't allow:

import mylib.a
from mylib.a.b import B

So, instead, use PyImport_AddModule(). You may find full featured examples in the following article: Packages in Python extension modules, by Vadim Macagon.

In short:

namespace py = boost::python;
std::string nested_name = py::extract<std::string>(py::scope().attr("__name__") + ".nested");
py::object nested_module(py::handle<>(py::borrowed(PyImport_AddModule(nested_name.c_str()))));
py::scope().attr("nested") = nested_module;
py::scope parent = nested_module;
py::class_<a::A>("A")...
rdesgroppes
  • 988
  • 12
  • 11