Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Namespaces mixed up when returning scoped enum from class method

I have the following code in C++:

class Person
{
    public:
        enum Gender {Male, Female};

        Gender GetGender() const;
}

I wrapped it in boost::python in this way:

BOOST_PYTHON_MODULE(TestPython)
{
    scope the_scope = class_<Person>("Person")
        .def("GetGender", &Person::GetGender);

    enum_<Person::Gender>("Gender")
        .value(Male, Person::Male)
        .value(Female, Person::Female)
        .export_values();
}

When I try to call person.GetGender() from Python I get the following exception:

Can't pickle : attribute lookup **PyBF.TestPython.Gender**.
It guesses the namespace of the Gender (which is actually **PyBF.TestPython.Person.Gender**) enum return type incorrectly.

How can I tell the GetGender function what type to return explicitly?

like image 569
Grim Avatar asked Nov 04 '22 07:11

Grim


1 Answers

Since we do not have the code that produces the error, I assume it occurs when you try to pickle a Person object.

Your problem is not specifically related to the usage of boost. It lies in cPickle module. It has problem pickling object with nested classes. See this answer for an explanation. Here's a simple code example that produces the error:

import cPickle

class MyOuterClass(object):
    class MyInnerClass(object):
        pass

    def __init__(self):
        self.my_inner_class = self.MyInnerClass()


def pickle_error():
    print "Pickling ..."
    my_outer_class = MyOuterClass()
    print cPickle.dumps(my_outer_class)

if __name__ == "__main__":
    pickle_error()

Running it produces this output:

Pickling ...
Traceback (most recent call last):
  File "pickle.py", line 18, in <module>
    pickle_error()
  File "pickle.py", line 15, in pickle_error
    print cPickle.dumps(my_outer_class)
cPickle.PicklingError: Can't pickle <class '__main__.MyInnerClass'>: 
attribute lookup __main__.MyInnerClass failed

As mentioned in the linked answer, when cPickle asks inner class for its name it returns '__main__.MyInnerClass'. However, this name can't be found in module's namespace, hence you get the exception.


Now you experience this because since there isn't something similar to an enum type in python, boost create an object to represent it. The enum_ construct declares a class in the current scope. By capturing the class scope, you end up creating a nested class within Person and you get the pickle error mentioned above.

There's a couple of solutions to your problem.

The simplest would be to declare the enum outside of Person's scope. You may not want to expose a Person's related enum besides it for code organization. You could then declare the Person class in a sub module so your enum is somewhat declared close to your class without being too public.

You could also take a look at boost's pickle support. However I haven't tried it.

A third solution could be to use something else than pickle to store your object.

like image 81
Eric Fortin Avatar answered Nov 09 '22 15:11

Eric Fortin