Python method resolution mystery
Asked Answered
T

3

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()?

Thump answered 7/5, 2011 at 19:58 Comment(4)
I would suggest trying subclassing with multiple inheritance first.Streeto
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?Thump
Can't you just use one or the other? Or use a mixin class? Just asking, I'm not familiar with Qt nor Boost.Streeto
@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.Thump
S
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!
Sieber answered 7/5, 2011 at 20:30 Comment(3)
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.Streeto
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.Sieber
@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.Thump
C
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.

Conterminous answered 7/5, 2011 at 20:42 Comment(4)
That sounds like exactly what I want. I'll upvote. Please let me know if you ever stumble into your old solution. Thanks.Thump
@Neil G, my old solution is in a previous employer's svn. I'm probably not going to stumble onto it.Conterminous
lol, okay no problem. I've found this so far: gitorious.org/avogadro/avogadro/blobs/… Will look into it more.Thump
@Neil G, that's very similar to what I had.Conterminous
C
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)
Concepcion answered 6/2, 2012 at 6:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.