PySide get QObject's parent anywhere in its hierarchy - python

Consider I have a class with this kind of structure (CustomClass may/may not be on top of the hierarchy):
CustomClass
.. QTabWidget
.... QWidget
...... QTreeView
In QTreeView I have a function that is trying to refer back to CustomClass. Right now in order to do this I need to do: self.parent().parent().parent().
Although this works it just feels very sloppy, and if I ever need to change the structure this will fail. Is there some other way to get CustomClass? Usually I would pass an instance of it during its constructor which I can call directly, but wondering what's the best practice to go about this.

The question title leads to a very direct answer. The window() method on QWidget returns the ancestor widget that has (or could have) a window-system frame: typically the "top-level" widget that you want to find. The docs give changing the window title as a canonical use case:
self.window().setWindowTitle(newTitle)
It returns self if the Qwidget is a window itself.
However, the text of your question and your own answer give an alternative interpretation: you might alternatively want to find the ancestor that is of a particular type even if it is not the top level widget. In this case, iterating up through the ancestors is typically the right solution, pretty much as you have written for yourself. So that would be something like:
customClassInst = self.parent()
while customClassInst is not None and not isinstance(customClassInst,CustomClass):
customClassInst = customClassInst.parent()
Note that you should usually use isinstance rather than type() == because the former correctly handles sub-classes.
Also note that this code will return None if no CustomClass is found which may or may not be what you want ...

This feels like a decent procedural way to get it:
customClassInst = self.parent()
while customClassInst is not None and type(customClassInst) != CustomClass:
customClassInst = customClassInst.parent()
Any other answers are still welcome :)

Related

dynamic parent class in python

The django-file-resubmit module (file widgets.py) makes this import:
from django.forms import ClearableFileInput
and then it defines class based on ClearableFileInput:
class ResubmitBaseWidget(ClearableFileInput):
# ...some code
I try to use the module with different base class and it works well. But I have to patch the import command in module code. (DBAdminClearableFileInput is inherited from django.forms.ClearableFileInput in an other 3rd party module):
from db_file_storage.form_widgets import DBAdminClearableFileInput as ClearableFileInput
My question is: Could the code of django-file-resubmit module be rewritten more clever, so it could be used with DBAdminClearableFileInput as parameter?
Note: I'm not sure if this is not duplicate question. However I think here are special problems of modules design and a question if some Pull Request could be made or what is the best approach how to use both modules without changing them.
It sounds like what you might really want is co-operative multiple inheritance. E.g. You want to have
class MyFileInput(DBAdminClearableFileInput, ResubmitBaseWidget):
pass
For that to work, both DBAdminClearableFileInput and ResubmitBaseWidget would need to be written with co-operative multiple inheritance in mind. It may not even be (theoretically) possible depending on how the end-state behaviour has to look. E.g. if DBAdminClearableFileInput wants to render the widget as <foo> and ResubmitBaseWidget wants to render the widget as <bar>, one of them has to 'win' (in the absence of additional code you might write yourself in MyFileInput.
It's possible (though probably unlikely) that multiple inheritance will 'just work', depending on what methods etc both those classes override and whether they make they already make the correct calls to super().
It's probably worth a try at least, in the worst case scenario you can add some 'glue' to your MyFileInput class to make it work.
Here's a trite example
class Elephant: # Represents ClearableFileInput
def colour(self):
return "Grey"
class BrownElephant(Elephant): # Represents ResubmitBaseWidget
def colour(self):
return "Brown"
class PinkElephant(Elephant): # Represents DBAdminClearableFileInput
def colour(self):
return "Pink"
Now, at the moment, these classes do not cooperate, and so if you do multiple inheritance, you get:
class BrownAndPinkElephant(BrownElephant, PinkElephant):
pass
nelly = BrownAndPinkElephant()
print(nelly.colour())
Will print "Brown", since the Method Resolution Order starts with BrownElephant, which returns "Brown" without ever calling super().colour(), and so Pink and 'default' Elephant's methods are never even called.
You could 'fix' this in a very limited (but might be enough for your purposes) with a hacky 'glue' method like this:
class BrownAndPinkElephant(BrownElephant, PinkElephant):
def colour(self):
colours = [
BrownElephant.colour(self),
PinkElephant.colour(self),
]
return " and ".join(colours)
nelly = BrownAndPinkElephant()
print(nelly.colour())
Now the printed output is "Brown and Pink", which is more sensible (at least within the context of this example!). Hopefully you can see that you attempt to implement similar things for a subclass of DBAdminClearableFileInput, ResubmitBaseWidget to give you control over what aspects of each class end up being used in the final class.
It's worth saying, there are an awful lot of pitfalls with this approach and I wouldn't recommend it for anything 'serious'. However, when you have 2 classes with a common base class, that you want to combine, and you don't control the source code of either, then this may be a possible solution.

Trouble with object-based QSignalMapper

I am trying to set up a signal-slot arrangement in PyQt where the signal transmits a lot of information. I want to use a QObject as a carrier by setting the various information I want to transmit as attributes of the QObject. In theory, this should work - there is a QSignalMapper.setMapping() overload which takes a sender and a QObject as arguments.
Here's the reduced code:
self.mapper = QtCore.QSignalMapper()
self.timeline.finished.connect(self.mapper.map)
carrier = QtCore.QObject()
carrier.contents = (item1, item2)
self.mapper.setMapping(self.timeline, carrier)
self.portalMapper.mapped.connect(self.report)
def report(self, carrierObject):
print 'Triggered'
Unfortunately it doesn't work. I've traced the problem to the setMapping function by process of elimination.
This same scheme will work just fine if I switch out the QObject with an int. It also doesn't have anything to do with the attributes I added to the QObject - using a fresh-out-of-the-box QObject causes the same issue.
It seems like there is something going on here with this specific overload of the setMapping function. Any ideas about what the issue is here?
Thanks to #ekhumoro's suggestion to skip the QSignalMapper approach entirely and just create a custom signal. Worked like a charm.

Is using Python `isinstance` ever right?

I've got a 2D array of different blocks, all inheriting from Block. I want to check if the block that I clicked on is a Dirt type block, like this:
clickedblock = getClickedBlock()
if isinstance(clickedblock, Dirt):
place a block
else:
don't place a block
I've heard that isinstance is bad, and should be avoided because it creates forks in code. What times would isinstance be good to use?
Another more cumbersome solution for my problem would be to have a field of Block called 'id' and then check if it equals some constant that means Dirt. But that sounds quite bad and more prone for mistake than the simple isinstance.
Your example seems like a legitimate use case of isinstance().
It's not that isinstance() is bad, often polymorphism can be used for the same purpose (which results in cleaner code in where the class is used).
But sometimes, isinstance() is what you need. For example, the pythonic way of detecting whether a variable is string or not is isinstance(var, basestring).
I learned the hard way against using it. The problem is that the outcome is sensitive to how the class definitions were imported:
in the place where the object was instantiated
in the place where the isinstance test is performed
If one import was relative and the other absolute - the check will fail.
Essentially it'll be like checking on equality between SomeClass vs somepackage.SomeClass. It doesn't even matter that they come from the same file and so on. Also, there will be a similar outcome if you somehow have both the root directory and somepackage directory in your PYTHONPATH - then an absolute-styled import could denote a path from either of the "source roots" so two different absolute-styled imports would result in failing instance checks.
One could argue about how good practices would anyway prevent that from happening but good practices are also largely about not taking chances. In that spirit I prefer to put some abstract method in a common ancestor class so that I can later rely on how things quack rather than on what the interpreter believes them to be.
In Java each class resolves to its fully qualified class name. These are unique within a program and tests for instances are a breeze. In Python these can be slippery.
I think I'd change it to be more like:
PLACEABLE_TYPES = [ Dirt ]
if isinstance(clickedblock, PLACEABLE_TYPES):
place the block
else:
don't place the block
but the idea in the comments of:
if clickedblock.is_placeable(that_place):
place the block
else:
don't place the block
also has merit.
If you don't want to use it, you've got other options. Traditional duck typing solution:
try:
clickedblock_place = clickedblock.place
except AttributeError:
# don't place block
else:
clickedblock_place()
Or you can use hasattr:
if hasattr(clickedblock, 'place'):
clickedblock.place()
I hardly ever use isinstance except for checking up (or is it down?) the inheritance hierarchy, say, for instance, if you need to know if a name points to a str OR a unicode:
if isinstance(str1, basestring):
blah, blah, blah

Extending weakref proxy/Copying behaviour

I have a class holding a table (list of lists). This class should return a rowpointer similar to sql. For this row pointer I would like to week ref the table row (a list) with a weakref.proxy. However, I would like to add additional capabilities to a row pointer, e.g. overwrite the __getitem__ method to allow access via, say the column names.
Is there an easy way to get the same behaviour (translating access to my object to the object beeing referenced), or do I have to reimplement all the special methods?
As an easy way I could think of inheritance (but since I found no doc on weakref.ProxyType I wont even try to inherit from that, (how to init?). The other option could be to define some special method even to always redirect "special" (__xxx__) function calls to the referred object, even though this makes that seem impossible.
Iresearched some more and found out this:
http://code.activestate.com/recipes/496741-object-proxying/
http://pypi.python.org/pypi/ProxyTypes
So in short, one can forward all calls (I think the recipi on active state is better), but I have not found a way to implement:
$a = proxy([1,2,3])
$b = a
$print type(b)
>>list
I will settle for just working with an object wich pretty much behaves like the list.

Get a layout's widgets in PyQT

I have a QVBoxLayout that I've added a few widgets to, via addWidget(). I need to now delete those widgets, and it seems I need to use removeWidget() (which takes in a widget to be removed) to do that.
I thought that calling children() or findChildren(QWidget) on my layout would return a list of the widgets I've added into it; I'm in the debugger, though, and am just receiving empty lists.
Am I terribly misunderstanding something? I've just started doing PyQT this last week and have mostly been learning through trial and error with the API docs.
To get a widget from a QLayout, you have to call its itemAt(index) method.
As the name of this method implies, it will return an item instead of a widget. Calling widget() on the result will finally give you the widget:
myWidget = self.myLayout.itemAt(index).widget()
To remove a widget, set the parent widget to None:
myWidget.setParent(None)
Also really helpfull is the QLayout count() method. To find and delete all contents of a layout:
index = myLayout.count()
while(index >= 0):
myWidget = myLayout.itemAt(index).widget()
myWidget.setParent(None)
index -=1
That's odd. My understanding is that adding widgets via addWidget transfers ownership to the layout so calling children() ought to work.
However, as an alternative you could loop over the layout items by using count() and itemAt(int) to supply a QLayoutItem to removeItem(QLayoutItem*).
Edit:
I've just tried addWidget with a straight C++ test app. and it doesn't transfer QObject ownership to the layout so children() is indeed an empty list. The docs clearly say that ownership is transferred though...
Edit 2:
Okay, it looks as though it transfers ownership to the widget that has that layout (which is not what the docs said). That makes the items in the layout siblings of the layout itself in the QObject hierarchy! It's therefore easier to stick with count and itemAt.

Categories