I'm developing a GUI with Qt/PySide with lots of separate classes handling various widgets. Each widget manages signals between buttons and other user inputs. I found myself having to reuse code to block widget signals at the start of method functions and then freeing up the signal at the end. I decided to try and write a general decorator to do this for me.
I've searched through SO and tried to implement this the best I could with very little experience using decorators and so I'm not satisfied by my solution.
My question is, what is the best way to write a general decorator that can access and run methods within that class which follow a clear format? Is my method in anyway a good way?
For clarity, here's what my code looks like with the repetitive code (I've removed some for brevity):
class WidgetController(...):
def __init__(...):
self.widget.myWidget.currentIndexChanged.connect(reactToChange)
def reactToChange(...):
self.widget.myWidget.blockSignals(True) # Repetetive line...
...
self.widget.myWidget.blockSignals(False)
def anotherFunction(...):
self.widget.anotherWidget.blockSignals(True)
...
self.widget.anotherWidget.blockSignals(False)
I would like something like the following:
class WidgetController(...):
@blockSignals(myWidget)
def reactToChange(...):
...
@blockSignals(anotherWidget, alsoBlockThisWidget)
def anotherFunction(...):
...
I have developed a decorator (with help from here, here and here) but I hesitate to show it as it feels horribly clunky. It uses self
where I don't understand it as well as exec
in nested functions and needs widget names to be passed as strings, but it does seem to work. Here it is:
class BlockSignals(object):
def __init__(self, *toBeBlocked):
self.toBeBlocked = toBeBlocked
def __call__(self, f):
toBeBlocked = self.toBeBlocked
def wrapped_f(self, *args):
for b in toBeBlocked:
exec 'self.widget.' + b + '.blockSignals(False)' in locals()
f(self, *args)
for b in toBeBlocked:
exec 'self.widget.' + b + '.blockSignals(False)' in locals()
return wrapped_f
Usage:
class WidgetController(...):
@BlockSignals("myWidget")
def reactToChange(...):
...
@BlockSignals("anotherWidget", "alsoBlockThisWidget")
def anotherFunction(...):
As you can see, it's not pretty. I'd love to be able to get rid of the string parsing, get rid of the exec
s, sort out the confusing self
s and be able to implement it by passing it the actual widget object @BlockSignals(self.widget.myWidget)
. Unfortunately I've reached the limit of my ability, is anyone able to help?