I'm using Boost.Python to create Python modules from C++ classes. And I ran into a problem with references.
Condider the following case where I have a class Foo with overloaded get methods that can either return by value or reference.
Specifying that the return by value should be used was easy once I typedefed a signature. But I think it should be possible return a reference as well by using a return_value_policy. However, using what seemed appropriate (doc); return_value_policy<reference_existing_object> did not seem to work.
Have I misunderstood what it does?
struct Foo {
Foo(float x) { _x = x; }
float& get() { return _x; }
float get() const { return _x; }
private:
float _x;
};
// Wrapper code
BOOST_PYTHON_MODULE(my_module)
{
using namespace boost::python;
typedef float (Foo::*get_by_value)() const;
typedef float& (Foo::*get_by_ref)();
class_<Foo>("Foo", init<float>())
.def("get", get_by_value(&Foo::get))
.def("get_ref", get_by_ref(&Foo::get),
return_value_policy<reference_existing_object>())//Doesn't work
;
}
Note: I know it could be dangerous to reference existing object without life-time managing.
Update:
It looks like it works for objects but not basic data types.
Take this revised example:
struct Foo {
Foo(float x) { _x = x; }
float& get() { return _x; }
float get() const { return _x; }
void set( float f ){ _x = f;}
Foo& self(){return *this;}
private:
float _x;
};
// Wrapper code
using namespace boost::python;
BOOST_PYTHON_MODULE(my_module)
{
typedef float (Foo::*get_by_value)() const;
class_<Foo>("Foo", init<float>())
.def("get", get_by_value(&Foo::get))
.def("get_self", &Foo::self,
return_value_policy<reference_existing_object>())
.def("set", &Foo::set);
;
}
Which in a test gave the expected result:
>>> foo1 = Foo(123)
>>> foo1.get()
123.0
>>> foo2 = foo1.get_self()
>>> foo2.set(1)
>>> foo1.get()
1.0
>>> id(foo1) == id(foo2)
False
In Python, there's the concept of immutable types. An immutable type can't have its value changed. Examples of built-in immutable types are int, float and str.
Having said that, you can't do what you want with boost::python, because Python itself does not allow you to change the value of the float returned by the function.
Your second example shows one solution, another would be to create thin-wrappers and exposing that:
void Foo_set_x(Foo& self, float value) {
self.get() = value;
}
class_<Foo>("Foo", init<float>())
...
.def("set", &Foo_set_x);
;
Which is a better solution than having to change the original C++ class.
I think you want return internal reference instead. I have used it before to do something similar.
Edit: Latest doc
I don't know much about Boost.Python, so I may misunderstand the question, in which case this is completely unhelpful. But here goes:
In Python you can't choose between returning by reference or by value, the distinction doesn't make sense in Python. I find it's easiest to think of it as everything being handled by reference.
You just have objects, and you have names for those objects. So
foo = "ryiuy"
Creates the string object "ryiuy" and then lets you refer to that string object with the name "foo". So in Python, when you get passed something, you get passed that object. There is no "values" as such, so you can't pass the value. But then again, it's also a valid viewpoint that there aren't references either, just objects and their names.
So the answer is, I guess, is that when you get a reference in C, you need to pass a reference to the object that reference references into Python. And when you get a value in C, you need to pass a reference to the object that you create from that value into Python.
Are you sure that the c++ object is being copied? You will get a new python object each time but which references the same c++ object. How are you determining that the object has been copied?
Related
I want to use a py::dict from C++. But operator[] does not seem to be defined, and I can't find any information here or in the pybind11 docs of how to add a key/value pair or return a value for a key?
edit: Maybe also important to mention I've got integers as keys.
edit2: Needed to use py::int_()
I see operator[] defined for py::dict, example:
m.def("test", [](){
py::dict d;
d[py::int_{0}] = "foo";
return d;
});
>>> example.test()
{10: 'foo'}
You can see operator[] has two overloads takes either a py::handle or string literal, so d["xxx"] or d[py::int_{0}] work but not d[0] (which would be wrongly resolved as a invalid string literal at compile time,and would cause run-time segment-fault)
template <typename Derived>
class object_api : public pyobject_tag {
...
/** \rst
Return an internal functor to invoke the object's sequence protocol. Casting
the returned ``detail::item_accessor`` instance to a `handle` or `object`
subclass causes a corresponding call to ``__getitem__``. Assigning a `handle`
or `object` subclass causes a call to ``__setitem__``.
\endrst */
item_accessor operator[](handle key) const;
/// See above (the only difference is that they key is provided as a string literal)
item_accessor operator[](const char *key) const;
also you cannot use std::string as key:
std::string key="xxx";
d[key] = 1; // failed to compile, must change to d[pybind11::str(key)]
to make things easier, use pybind11::cast() to explicitly convert any supported C++ type into corresponding python type, as following:
std::string key="xxx";
d[pybind11::cast(1)] = 2
d[pybind11::cast(key)] = 3
I have a C function that takes an object as argument:
void fct(struct Objet * obj1) {
....
}
I would like to use this function in Python. I'm trying to parse this argument but can't find the way to. In Python:
static PyObject* NameMod_fct(PyObject* self, PyObject* args) {
PyObject * Obj;
if (!PyArg_ParseTuple(args, "O!", **&...**, &Obj)) { // what should I put as &Py_Type?
return NULL;
}
...
}
Each Python object has a reference to its type: For a pPyObj (of type PyObject*), it can be accessed with pPyObj->ob_type.
This should point to an instance of PyTypeObject.
At this point, the answer very depends on where the resp. PyTypeObject is "constructed".
Case A: Objet is a Wrapper Object Written in C
This is the case, where "I feel at home" (as I got my knowledge about Python rather exclusively by writing extensions in C/C++). There should/must exist a static instance of PyTypeObject which is registered in Python initially. Just get and pass its address.
Case B: Objet is an Object of a non-C Library
Hmm... That's IMHO the most difficult case. You have to retrieve the address of the resp. PyTypeObject instance. This probably could be done retrieving the resp. dictionaries of Python. I cannot say in detail as I've no experience regarding this.
I guess a good start regarding this is to (re-)search about PyModule_GetDict() together with PyImport_Import().
Case C: Objet is an Object of a Built-In Type of Python
Last but not least – the trivial case. In this case, I wouldn't use O because there are a lot of other designators for the built-in types.
I have an API that I am trying to wrap using SWIG such that I can call the underlying C library from python.
I have got stuck with a particular API fn:
int update_tracks(track_t **phash_tracks,
const pdws_t *pdw_frame,
const rdws_t *rdw_frame,
lib_t *lib,
lib_meta_t *lib_meta,
const cfg_t *cfg);
Its the double pointer to track_t data structure that I can't handle.
All the single pointers work fine.
This is the only API fn that has a double pointer to track_t
All the others only have a single pointer, e.g.
void print_hash_tracks(const track_t *hash_tracks, const cfg_t *cfg,
enum TRKTYPE trktype);
I'm pretty sure I need to make a typemap in my SWIG interface file (interface.i) but I am finding the SWIG docs impenetrable.
What I think I need to do is create a typemap that whenever it sees the track_t** type, it takes a track_t* and converts it to its address, something like:
/* provide typemap to handle instances of track_t** parameters */
%typemap(in) track_t** (track_t *tracks) {
$1 = &tracks;
}
but I'm just getting segmentation faults when I run:
tracks = g3.track_t()
g3.update_tracks(tracks, pdw_frame, rdw_frame, lib, lib_meta, cfg)
on the python side.
I feel like I've almost solved this but can't quite get the typemap specification right and at the same time struggling to understand the relevant documentation.
flexo - if you're out there - maybe you can shed some light on this, you seem to be the SO expert in this area..
UPDATE - m7ython (brilliant! another SWIG expert on SO)
Usage in C is pretty straigthforward
declare and initialise a track_t pointer to NULL:
track_t *hash_tracks = NULL;
then:
update_tracks(&hash_tracks, &pdw_frame, &rdw_frame,
&lib, &lib_meta, &cfg);
So the address of the pointer to track_t is passed as an arg to update_tracks(). The update_tracks() fn takes care of all the necessary mallocs for the data that gets put into hash_tracks, i.e. the hash table of track_t structs
All the other args are single pointers and I can create and populate them with no issues on the python side.
track_t is a struct containing a bunch of ints, floats, char* etc. e.g.
typedef struct
{
/* make struct hashable */
UT_hash_handle hh;
int id;
...
char name[MAX_BUF];
...
} track_t;
The reason that the track_t arg is a track_t** and not just a track_t* is because hash_tracks is a pointer to a hash table (using the UTHash library). hash_tracks points to the 1st track_t in the hash table. In the body of the update_tracks() fn track_t structs can be added/removed from the hash table, such that the pointer to the 1st track_t may change, i.e. hash_tracks may point to something else after the call to update_tracks(), hence the reason for passing a pointer to the pointer.
In other words, the track_t** arg, phash_tracks is being used both as an input and output type arg, hence the pointer to a pointer. All the other args are simply inputs, they don't change so they can be passed in as single pointers.
I attempted the 'helper fn' route with the following C fn:
track_t** make_phash_tracks(void)
{
track_t **phash_tracks;
phash_tracks = calloc(1, sizeof(track_t*));
return phash_tracks;
}
the use of calloc should ensure that *phash_tracks is NULL
this compiled and wrapped with no errors, but when I used it from the python side it segfaulted, e.g.
phash_tracks = g3.make_phash_tracks()
g3.update_tracks(phash_tracks, pdw_frame, rdw_frame, lib, lib_meta, cfg)
checking the phash_tracks var just prior to calling update_tracks gave:
(Pdb) p phash_tracks
<Swig Object of type 'track_t **' at 0x7fb9e37c9030>
EDIT: Ok, I think I now understand what update_tracksdoes. It seems you can use the function in two ways. Either to update existing tracks, or to create tracks if you pass a pointer to a NULL pointer. I am not sure about the most elegant way to handle both cases in SWIG (or if this is even a problem), but here are some options.
1. phash_tracks is an output argument
First, you must pass *phash_tracks back to Python as a return value, and use the function in some form like
>>> int_res, tracks = g3.update_tracks(tracks, pdw_frame, rdw_frame, lib, lib_meta, cfg)
or
>>> int_res, tracks = g3.update_tracks(pdw_frame, rdw_frame, lib, lib_meta, cfg)
This is accomplished by the following "argout" typemap:
%typemap(argout) track_t **phash_tracks {
%append_output(SWIG_NewPointerObj(%as_voidptr(*$1), $*1_descriptor, SWIG_POINTER_OWN));
}
Maybe you don't want Python to take ownership of the track_t*, then replace SWIG_POINTER_OWN by 0.
2. Passing an empty phash_tracks
If you only want to use the update_tracks function to create tracks, you can do essentially what you are already doing. Use the following "in" typemap, and use the function as in the second example above (without the tracks parameter).
%typemap(in, numinputs=0) track_t **phash_tracks (track_t *tracks) {
tracks = NULL;
$1 = &tracks;
}
3. phash_tracks as an input (and output) argument
If you want to use update_tracks to update existing tracks, you should be able to use the "in" typemap I suggested before, and use the function from Python as in the first example (including the tracks parameter).
%typemap(in) track_t **phash_tracks (track_t *tracks) {
if ((SWIG_ConvertPtr($input, (void **) &tracks, $*1_descriptor, SWIG_POINTER_EXCEPTION | SWIG_POINTER_DISOWN)) == -1)
return NULL;
$1 = &tracks;
}
Note that it is important that Python disowns its tracks_t*.
4. Enabling both (2) and (3) above
You could basically use version (3) also to create tracks, if you could get swig to pass a wrapped NULL tracks_t*. I am not sure if SWIG allows this -- but maybe it does. Try using a helper function:
tracks_t* empty_tracks() { return NULL; }
Alternatively, you can modify the "in" typemap along the following lines, attempting to convert the provided argument to a track_t* and passing its address, or alternatively passing the address of a NULL track_t*.
%typemap(in) track_t **phash_tracks (track_t *tracks) {
// Alternatively, check if $input is a 0 integer `PyObject`...
if ((SWIG_ConvertPtr($input, (void **) &tracks, $*1_descriptor, SWIG_POINTER_DISOWN)) == -1)
tracks = NULL;
$1 = &tracks;
}
Then, from Python, just pass something else to create tracks:
>>> int_res, tracks = g3.update_tracks(0, pdw_frame, rdw_frame, lib, lib_meta, cfg)
I am working on a C++ library with Python bindings (using boost::python) representing data stored in a file. Majority of my semi-technical users will be using Python to interact with it, so I need to make it as Pythonic as possible. However, I will also have C++ programmers using the API, so I do not want to compromise on the C++ side to accommodate Python bindings.
A large part of the library will be made out of containers. To make things intuitive for the python users, I would like them to behave like python lists, i.e.:
# an example compound class
class Foo:
def __init__( self, _val ):
self.val = _val
# add it to a list
foo = Foo(0.0)
vect = []
vect.append(foo)
# change the value of the *original* instance
foo.val = 666.0
# which also changes the instance inside the container
print vect[0].val # outputs 666.0
The test setup
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include <boost/python/register_ptr_to_python.hpp>
#include <boost/shared_ptr.hpp>
struct Foo {
double val;
Foo(double a) : val(a) {}
bool operator == (const Foo& f) const { return val == f.val; }
};
/* insert the test module wrapping code here */
int main() {
Py_Initialize();
inittest();
boost::python::object globals = boost::python::import("__main__").attr("__dict__");
boost::python::exec(
"import test\n"
"foo = test.Foo(0.0)\n" // make a new Foo instance
"vect = test.FooVector()\n" // make a new vector of Foos
"vect.append(foo)\n" // add the instance to the vector
"foo.val = 666.0\n" // assign a new value to the instance
// which should change the value in vector
"print 'Foo =', foo.val\n" // and print the results
"print 'vector[0] =', vect[0].val\n",
globals, globals
);
return 0;
}
The way of the shared_ptr
Using the shared_ptr, I can get the same behaviour as above, but it also means that I have to represent all data in C++ using shared pointers, which is not nice from many points of view.
BOOST_PYTHON_MODULE( test ) {
// wrap Foo
boost::python::class_< Foo, boost::shared_ptr<Foo> >("Foo", boost::python::init<double>())
.def_readwrite("val", &Foo::val);
// wrap vector of shared_ptr Foos
boost::python::class_< std::vector < boost::shared_ptr<Foo> > >("FooVector")
.def(boost::python::vector_indexing_suite<std::vector< boost::shared_ptr<Foo> >, true >());
}
In my test setup, this produces the same output as pure Python:
Foo = 666.0
vector[0] = 666.0
The way of the vector<Foo>
Using a vector directly gives a nice clean setup on the C++ side. However, the result does not behave in the same way as pure Python.
BOOST_PYTHON_MODULE( test ) {
// wrap Foo
boost::python::class_< Foo >("Foo", boost::python::init<double>())
.def_readwrite("val", &Foo::val);
// wrap vector of Foos
boost::python::class_< std::vector < Foo > >("FooVector")
.def(boost::python::vector_indexing_suite<std::vector< Foo > >());
}
This produces:
Foo = 666.0
vector[0] = 0.0
Which is "wrong" - changing the original instance did not change the value inside the container.
I hope I don't want too much
Interestingly enough, this code works no matter which of the two encapsulations I use:
footwo = vect[0]
footwo.val = 555.0
print vect[0].val
Which means that boost::python is able to deal with "fake shared ownership" (via its by_proxy return mechanism). Is there any way to achieve the same while inserting new elements?
However, if the answer is no, I'd love to hear other suggestions - is there an example in the Python toolkit where a similar collection encapsulation is implemented, but which does not behave as a python list?
Thanks a lot for reading this far :)
Due to the semantic differences between the languages, it is often very difficult to apply a single reusable solution to all scenarios when collections are involved. The largest issue is that the while Python collections directly support references, C++ collections require a level of indirection, such as by having shared_ptr element types. Without this indirection, C++ collections will not be able to support the same functionality as Python collections. For instance, consider two indexes that refer to the same object:
s = Spam()
spams = []
spams.append(s)
spams.append(s)
Without pointer-like element types, a C++ collection could not have two indexes referring to the same object. Nevertheless, depending on usage and needs, there may be options that allow for a Pythonic-ish interface for the Python users while still maintaining a single implementation for C++.
The most Pythonic solution would be to use a custom converter that would convert a Python iterable object to a C++ collection. See this answer for implementation details. Consider this option if:
The collection's elements are cheap to copy.
The C++ functions operate only on rvalue types (i.e., std::vector<> or const std::vector<>&). This limitation prevents C++ from making changes to the Python collection or its elements.
Enhance vector_indexing_suite capabilities, reusing as many capabilities as possible, such as its proxies for safely handling index deletion and reallocation of the underlying collection:
Expose the model with a custom HeldType that functions as a smart pointer and delegate to either the instance or the element proxy objects returned from vector_indexing_suite.
Monkey patch the collection's methods that insert elements into the collection so that the custom HeldType will be set to delegate to a element proxy.
When exposing a class to Boost.Python, the HeldType is the type of object that gets embedded within a Boost.Python object. When accessing the wrapped types object, Boost.Python invokes get_pointer() for the HeldType. The object_holder class below provides the ability to return a handle to either an instance it owns or to an element proxy:
/// #brief smart pointer type that will delegate to a python
/// object if one is set.
template <typename T>
class object_holder
{
public:
typedef T element_type;
object_holder(element_type* ptr)
: ptr_(ptr),
object_()
{}
element_type* get() const
{
if (!object_.is_none())
{
return boost::python::extract<element_type*>(object_)();
}
return ptr_ ? ptr_.get() : NULL;
}
void reset(boost::python::object object)
{
// Verify the object holds the expected element.
boost::python::extract<element_type*> extractor(object_);
if (!extractor.check()) return;
object_ = object;
ptr_.reset();
}
private:
boost::shared_ptr<element_type> ptr_;
boost::python::object object_;
};
/// #brief Helper function used to extract the pointed to object from
/// an object_holder. Boost.Python will use this through ADL.
template <typename T>
T* get_pointer(const object_holder<T>& holder)
{
return holder.get();
}
With the indirection supported, the only thing remaining is patching the collection to set the object_holder. One clean and reusable way to support this is to use def_visitor. This is a generic interface that allows for class_ objects to be extended non-intrusively. For instance, the vector_indexing_suite uses this capability.
The custom_vector_indexing_suite class below monkey patches the append() method to delegate to the original method, and then invokes object_holder.reset() with a proxy to the newly set element. This results in the object_holder referring to the element contained within the collection.
/// #brief Indexing suite that will resets the element's HeldType to
/// that of the proxy during element insertion.
template <typename Container,
typename HeldType>
class custom_vector_indexing_suite
: public boost::python::def_visitor<
custom_vector_indexing_suite<Container, HeldType>>
{
private:
friend class boost::python::def_visitor_access;
template <typename ClassT>
void visit(ClassT& cls) const
{
// Define vector indexing support.
cls.def(boost::python::vector_indexing_suite<Container>());
// Monkey patch element setters with custom functions that
// delegate to the original implementation then obtain a
// handle to the proxy.
cls
.def("append", make_append_wrapper(cls.attr("append")))
// repeat for __setitem__ (slice and non-slice) and extend
;
}
/// #brief Returned a patched 'append' function.
static boost::python::object make_append_wrapper(
boost::python::object original_fn)
{
namespace python = boost::python;
return python::make_function([original_fn](
python::object self,
HeldType& value)
{
// Copy into the collection.
original_fn(self, value.get());
// Reset handle to delegate to a proxy for the newly copied element.
value.reset(self[-1]);
},
// Call policies.
python::default_call_policies(),
// Describe the signature.
boost::mpl::vector<
void, // return
python::object, // self (collection)
HeldType>() // value
);
}
};
Wrapping needs to occur at runtime and custom functor objects cannot be directly defined on the class via def(), so the make_function() function must be used. For functors, it requires both CallPolicies and a MPL front-extensible sequence representing the signature.
Here is a complete example that demonstrates using the object_holder to delegate to proxies and custom_vector_indexing_suite to patch the collection.
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
/// #brief Mockup type.
struct spam
{
int val;
spam(int val) : val(val) {}
bool operator==(const spam& rhs) { return val == rhs.val; }
};
/// #brief Mockup function that operations on a collection of spam instances.
void modify_spams(std::vector<spam>& spams)
{
for (auto& spam : spams)
spam.val *= 2;
}
/// #brief smart pointer type that will delegate to a python
/// object if one is set.
template <typename T>
class object_holder
{
public:
typedef T element_type;
object_holder(element_type* ptr)
: ptr_(ptr),
object_()
{}
element_type* get() const
{
if (!object_.is_none())
{
return boost::python::extract<element_type*>(object_)();
}
return ptr_ ? ptr_.get() : NULL;
}
void reset(boost::python::object object)
{
// Verify the object holds the expected element.
boost::python::extract<element_type*> extractor(object_);
if (!extractor.check()) return;
object_ = object;
ptr_.reset();
}
private:
boost::shared_ptr<element_type> ptr_;
boost::python::object object_;
};
/// #brief Helper function used to extract the pointed to object from
/// an object_holder. Boost.Python will use this through ADL.
template <typename T>
T* get_pointer(const object_holder<T>& holder)
{
return holder.get();
}
/// #brief Indexing suite that will resets the element's HeldType to
/// that of the proxy during element insertion.
template <typename Container,
typename HeldType>
class custom_vector_indexing_suite
: public boost::python::def_visitor<
custom_vector_indexing_suite<Container, HeldType>>
{
private:
friend class boost::python::def_visitor_access;
template <typename ClassT>
void visit(ClassT& cls) const
{
// Define vector indexing support.
cls.def(boost::python::vector_indexing_suite<Container>());
// Monkey patch element setters with custom functions that
// delegate to the original implementation then obtain a
// handle to the proxy.
cls
.def("append", make_append_wrapper(cls.attr("append")))
// repeat for __setitem__ (slice and non-slice) and extend
;
}
/// #brief Returned a patched 'append' function.
static boost::python::object make_append_wrapper(
boost::python::object original_fn)
{
namespace python = boost::python;
return python::make_function([original_fn](
python::object self,
HeldType& value)
{
// Copy into the collection.
original_fn(self, value.get());
// Reset handle to delegate to a proxy for the newly copied element.
value.reset(self[-1]);
},
// Call policies.
python::default_call_policies(),
// Describe the signature.
boost::mpl::vector<
void, // return
python::object, // self (collection)
HeldType>() // value
);
}
// .. make_setitem_wrapper
// .. make_extend_wrapper
};
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
// Expose spam. Use a custom holder to allow for transparent delegation
// to different instances.
python::class_<spam, object_holder<spam>>("Spam", python::init<int>())
.def_readwrite("val", &spam::val)
;
// Expose a vector of spam.
python::class_<std::vector<spam>>("SpamVector")
.def(custom_vector_indexing_suite<
std::vector<spam>, object_holder<spam>>())
;
python::def("modify_spams", &modify_spams);
}
Interactive usage:
>>> import example
>>> spam = example.Spam(5)
>>> spams = example.SpamVector()
>>> spams.append(spam)
>>> assert(spams[0].val == 5)
>>> spam.val = 21
>>> assert(spams[0].val == 21)
>>> example.modify_spams(spams)
>>> assert(spam.val == 42)
>>> spams.append(spam)
>>> spam.val = 100
>>> assert(spams[1].val == 100)
>>> assert(spams[0].val == 42) # The container does not provide indirection.
As the vector_indexing_suite is still being used, the underlying C++ container should only be modified using the Python object's API. For instance, invoking push_back on the container may cause a reallocation of the underlying memory and cause problems with existing Boost.Python proxies. On the other hand, one can safely modify the elements themselves, such as was done via the modify_spams() function above.
Unfortunately, the answer is no, you can't do what you want. In python, everything is a pointer, and lists are a container of pointers. The C++ vector of shared pointers work because the underlying data structure is more or less equivalent to a python list. What you are requesting is to have the C++ vector of allocated memory act like a vector of pointers, which can't be done.
Let's see what's happening in python lists, with C++ equivalent pseudocode:
foo = Foo(0.0) # Foo* foo = new Foo(0.0)
vect = [] # std::vector<Foo*> vect
vect.append(foo) # vect.push_back(foo)
At this point, foo and vect[0] both point to the same allocated memory, so changing *foo changes *vect[0].
Now with the vector<Foo> version:
foo = Foo(0.0) # Foo* foo = new Foo(0.0)
vect = FooVector() # std::vector<Foo> vect
vect.append(foo) # vect.push_back(*foo)
Here, vect[0] has it's own allocated memory, and is a copy of *foo. Fundamentally, you can't make vect[0] be the same memory as *foo.
On a side note, be careful with lifetime management of footwo when using std::vector<Foo>:
footwo = vect[0] # Foo* footwo = &vect[0]
A subsequent append may require moving the allocated storage for the vector, and may invalidate footwo (&vect[0] may change).
I have lots of C/C++ classes to export to python using Swig. I've noticed that by default, Swig doesn't generate a __hash__ method for wrapped classes, so the default hash gets used, which is the id of the wrapper object (i.e. its memory address or something like it). This means if I end up with two python objects wrapping the same C object, they hash differently in dicts and so on, which is weird and leads to hard-to-find bugs.
I can easily extend any class with a better __hash__:
%extend Foo {
bool __eq__(const Foo &other) { return $self == &other; }
bool __ne__(const Foo &other) { return $self != &other; }
long __hash__() { return (long) $self; } // hash: just address of C struct
}
(I also think eq should by default compare based on the C struct address, rather than the wrapper's address.)
My question is this: Is there any way to tell Swig to extend all classes like this? Or do I have to do them one at a time?
I guess a secondary question would be: why doesn't swig just do this automatically? Is the default behavior useful? I don't find it to be.
I am not aware of a way to do that, if only because it is rarely what you would want ("all classes" could include more than you realize and really intended). However, SWIG does support its own macro system, so you could probably do something like:
%define PY_HASHABLE(TypeName)
%extend TypeName {
bool __eq__(const TypeName &other) { return $self == &other; }
bool __ne__(const TypeName &other) { return $self != &other; }
long __hash__() { return (long) $self; } // hash: just address of C struct
}
%enddef
Then you would do
#if defined(SWIGPYTHON)
PY_HASHABLE(Foo)
PY_HASHABLE(Bar)
#endif
etc. You can have multiple extend clauses for a given class so the above does not interfere with other extensions.