Search code examples
pythonpython-decorators

Calling a decorated function correctly


I'm trying to repeatedly call a decorated function from within another class, such that the decorator is executed everytime the function is called.

The original question is below but does not explicitly relate to pyqt as pointed out correctly.

I'm trying to use decorators within a pyqt thread. From how I understand decorators, the decoration should be executed every time the function is called. (Or at least that is what I want.) However, calling a decorated function from within a pyqt thread leads to execution of the decorator only once.

This is my tested example:

import time, sys
import numpy as np
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *


class Decor:

    def deco(self, *args, **kwargs):
        print(kwargs['txt'])
        print("In decorator")

        def inner(func):
            return func

        return inner


dec = Decor()


class Window(QWidget):
    def __init__(self, parent = None):
        QWidget.__init__(self, parent)
        self.thread = Worker()
        label = QLabel(self.tr("random number"))
        self.thread.output[str].connect(label.setText)
        layout = QGridLayout()
        layout.addWidget(label, 0, 0)
        self.setLayout(layout)
        self.thread.start()


class Worker(QThread):
    output = pyqtSignal(str)

    def run(self):
        # Note: This is never called directly. It is called by Qt once the
        # thread environment has been set up.
        while True:
            time.sleep(1)
            number = self.random()
            self.output.emit('random number {}'.format(number))

    @dec.deco(txt='kw_argument')
    def random(self):
        return np.random.rand(1)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

I expected to get the prints of 'kw_argument' and 'in decorator' as often as self.normal()is called, but get it only once. What am I doing wrong?


Solution

  • You could use function decorator instead:

    import time, sys
    from functools import wraps
    from PyQt5.QtCore import *
    from PyQt5.QtWidgets import *
    
    
    def dec2(*args, **kwargs):
        def real_decorator(fn):
            @wraps(fn)
            def wrapper(*args, **kwargs):
                print(args, kwargs)
                print("In decorator")
                return fn(*args, **kwargs)
    
            return wrapper
    
        return real_decorator
    
    
    class Window(QWidget):
        def __init__(self, parent=None):
            QWidget.__init__(self, parent)
            self.thread = Worker()
            label = QLabel(self.tr("random number"))
            self.thread.output[str].connect(label.setText)
            layout = QGridLayout()
            layout.addWidget(label, 0, 0)
            self.setLayout(layout)
            self.thread.start()
    
    
    class Worker(QThread):
        output = pyqtSignal(str)
    
        def run(self):
            # Note: This is never called directly. It is called by Qt once the
            # thread environment has been set up.
            while True:
                time.sleep(1)
                number = self.random()
                self.output.emit('random number {}'.format(number))
    
        @dec2(txt='kw_argument')
        def random(self):
            return np.random.rand(1)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        window = Window()
        window.show()
        sys.exit(app.exec_())
    

    Out:

    (<__main__.Worker object at 0x10edc37d0>,) {}
    In decorator
    (<__main__.Worker object at 0x10edc37d0>,) {}
    In decorator
    (<__main__.Worker object at 0x10edc37d0>,) {}
    In decorator
    (<__main__.Worker object at 0x10edc37d0>,) {}
    In decorator
    (<__main__.Worker object at 0x10edc37d0>,) {}
    In decorator
    ...
    

    If you really need to print txt always, stick with a class decorator:

    class dec2(object):
        def __init__(self, *args, **kwargs):
            self.deco_args = args
            self.deco_kwargs = kwargs
    
        def __call__(self, f):
            def wrapped_f(*args):
                print(self.deco_kwargs['txt'])
                print('in decorator')
                return f(*args)
    
            return wrapped_f
    

    Out:

    w_argument
    in decorator
    kw_argument
    in decorator
    ...