3

How can I iterate in C++ over my boost::python:dict ? I need key and value in every loop round.

My try was this:

for (auto x : MyBoostPythonDict.iteritems())
{
    // determine key
    // determine value 
    // ...
}

I got this error: C3312 no callable 'end' function found for type 'boost::python::api::object'

Lord Elrond
  • 13,430
  • 7
  • 40
  • 80
bob morane
  • 650
  • 1
  • 11
  • 24

3 Answers3

2

Boost Python exposes some STL iterator wrappers in the "stl_iterator" header that let you go from begin to end like a normal C++ iteration:

https://www.boost.org/doc/libs/1_75_0/libs/python/doc/html/reference/high_level_components/boost_python_stl_iterator_hpp.html

For Python version 2 you can use d.items() or d.iteritems() depending on whether you want to iterate lazily or not. For Python version 3 has a slight problem - you'd expect items to be a lazy dict view, but instead Boost Python converts it to a list. Therefore, I've called .attr("items")() instead to bypass the in-build conversion thus getting a lazy wrapper. I've returned this object to Python just to confirm that it's the view rather than a list.

Calling stl_input_iterator<tuple>() gives you an iterator for a (Python) tuple object from which you can extract the key and the value.

#include <boost/python/dict.hpp>
#include <boost/python/tuple.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/python/extract.hpp>
#include <boost/python/str.hpp>
#include <boost/python.hpp>

#include <iostream>

using namespace boost::python;

object print_dict_to_cout(dict d) {
    auto items = d.attr("items")(); // just plain d.items or d.iteritems for Python 2!
    for (auto it = stl_input_iterator<tuple>(items); it != stl_input_iterator<tuple>(); ++it) {
        tuple kv = *it;
        auto key = kv[0];
        auto value = kv[1];
        std::cout << extract<const char*>(str(key)) << " : " << extract<const char*>(str(value)) << std::endl;
    }
    return items;
}

BOOST_PYTHON_MODULE(iterdict)
{
    def("print_dict_to_cout", print_dict_to_cout);
}

The advantage of doing it this way is that it's lazy, and doesn't create the intermediate list.

DavidW
  • 29,336
  • 6
  • 55
  • 86
1

Just use item list directly:

for (auto x : MyBoostPythonDict.items())

Im guessing the iteritems is probably intended to be used something like this:

for(auto it = D.iteritems(); it; ++it)
darune
  • 10,480
  • 2
  • 24
  • 62
  • Thank you for your answer. He has a problem with "++it" or "it++": --> 'boost::python::api::object' does not define this operator or a conversion to a type acceptable to the predefined operator – bob morane Sep 25 '19 at 10:23
  • @bobmorane and what about using `items()` directly ? – darune Sep 25 '19 at 10:27
  • What do you mean with directly? – bob morane Sep 25 '19 at 10:31
  • @bobmorane if you will provide a minimal and complete example I can improve upon the answer. – darune Sep 25 '19 at 11:31
  • My usecase is simple: I got a python dict into the API as parameter and now I have to handle it. int ABC_Class::Execute_Query(boost::python::dict PyDict) { // Now I have to iterate the provided python dict to hadle all key<->values } – bob morane Sep 25 '19 at 11:40
  • 1
    @bobmorane the point is just: I shouldn't have to program the example myself to help you - you can provide that as easily. See https://stackoverflow.com/help/minimal-reproducible-example – darune Sep 25 '19 at 11:45
  • The issue is that neither of the things proposed in this answer work at all: the range-for iteration over `dict.items()` fails because the list object doesn't define `begin` and `end` while the object returned by `iteritems` doesn't have a ++ operator. – DavidW Apr 11 '21 at 07:34
1

You can simply loop over all key/value pairs as follows:

using namespace boost::python;

list items = MyBoostPythonDict.items();
for(ssize_t i = 0; i < len(items); ++i) {
    object key = items[i][0];
    object value = items[i][1];
    // ...
}

Now you need to extract the corresponding types from key and value. Assuming both are of type int you would use:

extract<int> key_int(key);
extract<int> value_int(value);

if (key_int.check() && value_int.check()) {
    cout << key_int << ": " << value_int << endl;
}
ikkuh
  • 4,473
  • 3
  • 24
  • 39
  • This creates a temporary list, which is can be problematic for very large dicts. David W's solution using stl_input_iterator should be the preferred method. – Jay West Aug 31 '22 at 21:16