1

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 execs, sort out the confusing selfs 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?

Community
  • 1
  • 1
ejrb
  • 338
  • 3
  • 9

1 Answers1

2

You are looking for getattr:

import functools

def blockSignals(*widgetnames):
    def decorator(func):
        @functool.wraps(func)
        def method(self, *args, **kwargs):
            widgets = [getattr(self.widget, name) for name in widgetnames]
            for widget in widgets:
                widget.blockSignals(True) 
            result = func(self, *args, **kwargs)
            for widget in widgets:
                widget.blockSignals(False)
            return result
        return method
    return decorator

class WidgetController(...):

    def __init__(...):
       self.widget.myWidget.currentIndexChanged.connect(reactToChange)

    @blockSignals('myWidget')
    def reactToChange(...):
        ...

    @blockSignals('anotherWidget', 'alsoBlockThisWidget')
    def anotherFunction(...):
        ...

You have to pass the name of the widget, not the widget itself, because the methods are defined at the time the class is defined, not when the instance is instantiated. The instance self and the actual widgets in self.widget do not exist at class-definition time.

The functools.wraps decorator copies the name of the original function and its docstring to the decorated function.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Thanks for the answer, that looks great. Where is `func` passed to the decorator though? – ejrb Jun 17 '13 at 11:58