5

I can't figure out why this program is failing.

#!/usr/bin/env python
from __future__ import division, print_function
from future_builtins import *
import types
import libui as ui
from PyQt4 import QtCore
import sip

p = ui.QPoint()
q = QtCore.QPoint()

def _q_getattr(self, attr):
    print("get %s" % attr)
    value = getattr(sip.wrapinstance(self.myself(), QtCore.QPoint), attr)
    print("get2 %s returned %s" % (attr, value))
    return value

p.__getattr__ = types.MethodType(_q_getattr, p)

print(p.__getattr__('x')())  # Works!  Prints "0"
print(p.x())  # AttributeError: 'QPoint' object has no attribute 'x'

I used Boost.Python to create libui, which exposes the class QPoint. I aso included PyQt4, which has a sip-exposed QPoint. I'm trying to accomplish a mapping between the two types.

I checked that p is a new-style class, so why isn't __getattr__ being called for p.x()?

Mike Pennington
  • 41,899
  • 19
  • 136
  • 174
Neil G
  • 32,138
  • 39
  • 156
  • 257
  • I would suggest trying subclassing with multiple inheritance first. – Keith May 07 '11 at 20:21
  • If I subclass, won't there be two copies of the underlying C++ QPoint object? So, setting member variables in Python would be invisible to C++ and vice versa? – Neil G May 07 '11 at 20:25
  • Can't you just use one or the other? Or use a mixin class? Just asking, I'm not familiar with Qt nor Boost. – Keith May 07 '11 at 20:31
  • @Keith: I'm trying to write some code in C++ and some in Python. Boost.Python is a library that lets me export C++ classes to Python. sip does the same thing, and was used by PyQt to export Qt to Python. It's hard to have some code in Python and some in C++ in a Qt application where you don't end up wanting to pass Qt objects back and forth. – Neil G May 07 '11 at 20:50

3 Answers3

5

This is somewhat similar to the issue someone else has encountered just yesterday. In short, it seems like special methods (like __getattr__, __str__, __repr__, __call__ and so on) aren't overridable in new-style class instance, i.e. you can only define them in its type.

And here's an adaptation of my solution for that problem which should hopefully work for yours:

def _q_getattr(self, attr):
    print("get %s" % attr)
    return getattr(self, 'x')

def override(p, methods):
    oldType = type(p)
    newType = type(oldType.__name__ + "_Override", (oldType,), methods)
    p.__class__ = newType

override(p, { '__getattr__': _q_getattr})
print(p.__getattr__('x')())  # Works!  Prints "0"
print(p.x())                 # Should work!
Community
  • 1
  • 1
Boaz Yaniv
  • 6,334
  • 21
  • 30
  • Yes, that is why I suggest working with the system (e.g. use inheritance) rather than try "hacks". But I'm not sure how it would work in this case with two wrapped classes. Would have to investigate. – Keith May 07 '11 at 20:36
  • I have no experience with SIP, so I can't tell. If inheritance doesn't work, then my solution won't work as well. But if inheritance doesn't work, I'm afraid there is very little you can do, since all those special methods can only be overriden in the type-level. – Boaz Yaniv May 07 '11 at 20:41
  • @Boav: thanks for the Python lesson (`__getattr__` has to be set at the type level.) That solves the mystery. I will think about your solution and get back to you. – Neil G May 07 '11 at 20:54
1

I suggest that you not attempt to expose QPoint in boost python. You should be able to register converters to/from python with boost that will use the SIP api functions to convert QPoint from/to python as the sip objects.

I've done it, but not recently enough to give more details.

Winston Ewert
  • 44,070
  • 10
  • 68
  • 83
  • That sounds like exactly what I want. I'll upvote. Please let me know if you ever stumble into your old solution. Thanks. – Neil G May 07 '11 at 20:52
  • @Neil G, my old solution is in a previous employer's svn. I'm probably not going to stumble onto it. – Winston Ewert May 07 '11 at 21:08
  • lol, okay no problem. I've found this so far: http://gitorious.org/avogadro/avogadro/blobs/e297c37550a093deaf515229a4c4c040a882d5b0/libavogadro/src/python/sip.cpp Will look into it more. – Neil G May 07 '11 at 21:22
  • @Neil G, that's very similar to what I had. – Winston Ewert May 07 '11 at 21:35
1

This is an example how to integrate PyQt4 and boost::python

first of all we must define wrap/unwrap function to deal with bare pointers

long int unwrap(QObject* ptr) {
    return reinterpret_cast<long int>(ptr);
}

template <typename T>
T* wrap(long int ptr) {
    return reinterpret_cast<T*>(ptr);
}

after that we must register all classes we want integrate to

class_<QObject, QObject*, boost::noncopyable>("QObject", no_init)
    .def("unwrap", unwrap)
    .def("wrap", make_function( wrap<QObject>, return_value_policy<return_by_value>() ))
    .staticmethod("wrap");

class_<QWidget, bases<QObject>, QWidget*, boost::noncopyable>("QWidget")
    .def("wrap", make_function( wrap<QWidget>, return_value_policy<return_by_value>() ))
    .staticmethod("wrap");

class_<QFrame, bases<QWidget>, QFrame*, boost::noncopyable>("QFrame")
    .def("wrap", make_function( wrap<QFrame>, return_value_policy<return_by_value>() ))
    .staticmethod("wrap");

class_<QLabel, bases<QFrame>, QLabel*, boost::noncopyable>("QLabel")
    .def("wrap", make_function( wrap<QLabel>, return_value_policy<return_by_value>() ))
    .staticmethod("wrap");

and for example we have class that works with.. QLabel:

class worker: public QObject {
...
void add_label(QLabel*);
};

we must expose this class to python too:

class_<worker, bases<QObject>, worker*, boost::noncopyable>("worker")
        .def("add_label", &worker::add_label);

now we a ready to interaction, on C++-size do something like this

worker* w = new worker;
main_namespace["worker"] = boost::ref(w);

python:

from PyQt4.Qt import *
import sip
import mylib as MyLib

#...

#If you are using QApplication on C++-size you don't need to create another one

lb = QLabel("label from PyQt4!")

lb_ptr = sip.unwrapinstance(f)

my_lb = MyLib.QLabel.wrap(lb_ptr)

worker.add_label(my_lb)

In other case if you wan't send you own Q-object to PyQt4 :

QLabel* lb = new QLabel("C++ label");
main_namespace["lb"] = boost::ref(lb);

python:

from PyQt4.Qt import *
import sip
import mylib as MyLib

#...

my_lb_ptr = lb.unwrap()

qt_lb = sip.wrapinstance(my_lb_ptr, QLabel)

And this is my real little helper:

from PyQt4.Qt import *
import sip

def toQt(object, type):
    ptr = object.unwrap()
    return sip.wrapinstance(ptr, type)

def fromQt(object, type):
    ptr = sip.unwrapinstance(object)
    return type.wrap(ptr)
jerry_ru
  • 11
  • 1