How to Give a C++ Class a Python __repr__() with SWIG
Asked Answered
B

3

8

I've observed that when one types

help

in the Python repl, one gets

Type help() for interactive help, ...

and when one types

help()

one gets kicked into help mode. I'm pretty sure this is because site._Helper defines __repr__() (for the first example) and __call__() (for the second).

I like this behavior (prompt for just the object, and callable syntax), and I'd like to do the same for a C++ class I'm exporting to Python via SWIG. Here is a simple example of what I've tried to do

helpMimic.h
-----------
class HelpMimic
{
public:
    HelpMimic() {};
    ~HelpMimic() {};

    char *__repr__();
    void operator()(const char *func=NULL);
};

helpMimic.cxx
-------------
char *HelpMimic::__repr__()
{
    return "Online help facilities are not yet implemented.";
}

void HelpMimic::operator()(const char *func)
{
    log4cxx::LoggerPtr transcriptPtr = oap::getTranscript();
    std::string commentMsg("# Online help facilities are not yet implemented. Cannot look up ");
    if (func) {
        commentMsg += func;
    }
    else {
        commentMsg += "anything.";
    }

    LOG4CXX_INFO(transcriptPtr, commentMsg);
}

helpMimic.i
-----------
%module sample
 %{
#include <helpMimic.h>
 %}
class HelpMimic
{
public:
    HelpMimic() {};
    ~HelpMimic() {};

    char *__repr__();
    void operator()(const char *func=NULL);
};

When I attempt to use this class in my application, I can't seem to get the behavior I see with help (the output below is taken from a C++ application with Python embedded, where each input line is sent through PyEval_String()):

 tam = sample.HelpMimic()
 tam   # echoes 'tam', nothing else
 print tam
 # _5010b70200000000_p_HelpMimic
 print repr(tam)
 # <Swig Object of type 'HelpMimic *' at 0x28230a0>
 print tam.__repr__()
 # Online help facilities are not yet implemented.

That last print shows that the method __repr__() is there, but I can't find it using the simpler object reference or using repr(tam). I also tried defining __str()__ in the hopes that I'd misunderstood which would get called, but still no luck.

I've tried using the %extend directive in the interface file to insert a __str__() or a __repr__() definition into the SWIG interface definition file, instead of defining them directly in C++, but to no avail.

What am I missing?

Bioluminescence answered 21/2, 2013 at 23:5 Comment(4)
Just tried your code and it just fine for me. I commented out the LOG4CXX* lines because I don't have the requisite stuff to compile with that, but otherwise, I changed nothing. And it worked... For the record, I'm on python2.7 on OSX10.8. No clue how my setup differs from yours. It would appear that whatever it is you are missing, it's not related to the code itselfAfterword
How about defining repr as const char* __repr__() const?Jacky
Perhaps the Python wrapper shadows the C++ version of repr. You could try defining a regular const char* printRepr() const in C++, export that via the .i file, and in that .i file extend the class with a Python repr method that calls the Python printRepr (that wraps the C++ method).Jacky
Are you using -builtin when calling swig for this?Superannuate
P
3

As @flexo suggested in a comment, if you are using the -builtin flag to the SWIG code generator, repr() will not call your __repr__ method. Instead, you need to define a function that fits in the repr slot.

%feature("python:slot", "tp_repr", functype="reprfunc") HelpMimic::printRepr;

As per HelpMimic::printRepr must have a signature that matches the expected signature (tp_repr in Python docs) - it must return a string or unicode object. Another caveat - you can't put the same function in more than one slot, so don't try to use this for tp_str!

Pronto answered 2/6, 2015 at 19:45 Comment(1)
To future me (or anybody in a similar situation like I was): The syntax, especially the HelpMimic::printRepr part, applies also when you're binding a C library to Python. Don't forget to %extend your class with a printRepr function. The name of the class to extend can be grepped from the generated C Code or by looking up the relevant struct in the C library to bind.Humiliate
L
3

I usually use the %extend feature to avoid tailoring the C/C++ to much for a specific target language. E.g.

%extend MyClass {
  %pythoncode %{
    def __repr__(self):
      # How you want your object to be shown
    __swig_getmethods__["someMember"] = SomeMemberGet
    __swig_setmethods__["someMember"] = SomeMemberSet
    if _newclass:
      someMember = property(SomeMemberGet,SomeMemberSet)
    def show(self):
      # You could possibly visualize your object using matplotlib
  %}
};

Where you inside the repr function can call basically any function and format the output to suit your needs. Further, you can add properties and define how they map to setters and getters.

Liripipe answered 11/8, 2015 at 11:6 Comment(2)
This seems to not work when SWIG's -builtin flag is used.Humiliate
It doesn't. SWIG's documentation also says so. I would recommend you to completely avoid using the builtin flagLiripipe
T
1

If you want to add a __repr__ in the Python code rather than C/C++, you may need to deal with the default swig definition of __repr__ = _swig_repr.

This turns out to be fairly straightforward:

#if defined(SWIGPYTHON)
%pythoncode %{
    del __repr__
    def __repr__(self):
        return 'object representation'
%}
#endif
Thermography answered 20/7, 2022 at 17:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.