2

I am wondering how I can setup a decorator in a class that can accept class variables. The example where I think this could be useful is in Pyside/PyQt where I need to block and unblock signals on widgets at the beginning and ending of functions.

Example:

class Window(QtGui.QMainWindow):
    ....

    def updateList(self, *args):
        self.list.blockSignals(False)
        //do things on self.list
        self.list.blockSignals(True)

Now, there is the potential of a lots of places on different widgets this could be done. Sure I can do this method of blocking and unblocking each item, but that's tedious. And I have to remember to unblock everything when I'm done.

Moving into another step, I can move the blocking into it's own function.

class Window(QtGui.QMainWindow):
    ....

    def updateList(self, *args):
        self.block([self.list])
        //do things on self.list
        self.block([self.list])

    def block(self, items):
        for item in items:
            if item.signalsBlocked():
                item.blockSignals(False)
            else:
                item.blockSignals(True)

Sweet! I can pepper that around my code and feels pretty useful. But I feel like there is some kind of final boss form I'm missing here to make it truely globally useful. Like some kind of decorator.

Using this answer here I can pass variables to my decorator and decorate my function! Decorators with parameters?

def block(items):
    def dec(fn):
        def wrapped(*args, **kwargs):
            for item in items:
                item.blockSignals(True)
            fn(*args, **kwargs)
            for item in items:
                item.blockSignals(False)
        return wrapped
    return dec


class Window(QtGui.QMainWindow):
    ....

    @block([self.list])
    def updateList(self, *args):
        //do things on self.list

Now I feel that could truly be useful! Except, @block([self.list]) has no idea what self is.

So, what I'm attempting to do, is that reasonable to assume I can do this? Is it possible, or am I chasing wild dragons? If it's possible, what's the correct way to attempt this?

ooklah
  • 491
  • 1
  • 5
  • 16

2 Answers2

2

You can't refer to the attribute values at class definition time, but you can use their names:

def block(*attrs):
    def dec(fn):
        @functools.wraps(fn)
        def wrap(self,*args,**kwargs):
            for a in attrs: getattr(self,a).blockSignals(True)
            ret=fn(self, *args, **kwargs)
            for a in attrs: getattr(self,a).blockSignals(False)
            return ret
        return wrap
    return dec

It's a bit ugly to have to write

class Window(QtGui.QMainWindow):
    @block("list1","list2")
    def updateList(self, *args): # ...

with the string quotes and all, but it works.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • I think I would have used `instance` as the parameter name instead of `self` in `wrap` - but ... – wwii Sep 21 '17 at 23:22
  • `instance` would conflict with an argument of the same name in the wrapped function. The function `wrap` will become a method of the class as soon as the descriptor returns it, `self` seems perfectly idiomatic to me. – Davis Herring Sep 21 '17 at 23:55
2

Here is a simple context manager, for comparison:

class blocked(object):
    def __init__(self, *targets):
        self._targets = targets

    def __enter__(self):
        for target in self._targets:
            target.blockSignals(True)

    def __exit__(self, cls, exception, traceback):
        for target in self._targets:
            target.blockSignals(False)

and here's how to use it:

class Window(QtGui.QMainWindow):
    ...

    def updateList(self, *args):
        with blocked(self.list):
            # do things with self.list

The benefit of doing things this way is that blocked can be used anywhere, and the targets can be specified dynamically. It could also be argued that it is more readable, since it is self.list (the signal emitter) that is being blocked, not self.updateList.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • I can immediately see where that's useful when the signals should not be blocked for the entire function. A decorator would blanket block the entire function in these examples. – ooklah Sep 22 '17 at 18:05