8

I have a rather large and involved decorator to debug PyQt signals that I want to dynamically add to a class. Is there a way to add a decorator to a class dynamically?

I might be approaching this problem from the wrong angle, so here is what I want to accomplish.

Goal

  1. I have a decorator that will discover/attach to all pyqt signals in a class and print debug when those signals are emitted.
  2. This decorator is great for debugging a single class' signals. However, there might be a time when I would like to attach to ALL my signals in an application. This could be used to see if I'm emitting signals at unexpected times, etc.
  3. I'd like to dynamically attach this decorator to all my classes that have signals.

Possible solutions/ideas

I've thought through a few possible solutions so far:

  1. Inheritance: This would be easy if all my classes had the same base class (other than Python's built-in object and PyQt's built-in QtCore.QObject). I suppose I could just attach this decorator to my base class and everything would workout as expected. However, this is not the case in this particular application. I don't want to change all my classes to have the same base class either.
  2. Monkey-patch Python object or QtCore.QObject: I don't know how this would work practically. However, in theory could I change one of these base classes' __init__ to be the new_init I define in my decorator? This seems really dangerous and hackish but maybe it's a good way?
  3. Metaclasses: I don't think metaclasses will work in this scenario because I'd have to dynamically add the __metaclass__ attribute to the classes I want to inject the decorator into. I think this is impossible because to insert this attribute the class must have already been constructed. Thus, whatever metaclass I define won't be called. Is this true?

I tried a few variants of metaclass magic but nothing seemed to work. I feel like using metaclasses might be a way to accomplish what I want, but I can't seem to get it working.

Again, I might be going about this all wrong. Essentially I want to attach the behavior in my decorator referenced above to all classes in my application (maybe even a list of select classes). Also, I could refactor my decorator if necessary. I don't really care if I attach this behavior with a decorator or another mechanism. I just assumed this decorator already accomplishes what I want for a single class so maybe it was easy to extend.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
durden2.0
  • 9,222
  • 9
  • 44
  • 57

1 Answers1

10

Decorators are nothing more than callables that are applied automatically. To apply it manually, replace the class with the return value of the decorator:

import somemodule

somemodule.someclass = debug_signals(somemodule.someclass)

This replaces the somemodule.someclass name with the return value of debug_signals, which we passed the original somemodule.someclass class.

denvaar
  • 2,174
  • 2
  • 22
  • 26
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • This does work. However, this solution still has the same problem of having to add the decorator manually. I want to dynamically add the functionality to all classes without having to know their names, etc. For this solution I have to import the class and manually add the decorator. So, this doesn't seem to have any benefits over just adding `@debug_signals` to the classes themselves. Am I missing something? – durden2.0 Apr 11 '13 at 21:41
  • 1
    @durden2.0: right, you *still* have to add things manually. But you can add it to the `QtCore.QObject` object in this case (provided it is a pure-Python class), without having to alter it's source code. – Martijn Pieters Apr 11 '13 at 21:45
  • Good point. However, `QtCore.QObject` is not a pure-Python class. It's actually auto-generated code from [sip](http://en.wikipedia.org/wiki/SIP_(software)) I believe. I just tested this and it **sort of** worked for a second but caused a segfault. :) – durden2.0 Apr 11 '13 at 21:49
  • I believe the segfault is due to replacing the `QtCore.QObject.__init__`. I'm a bit confused though because I save off the original and call it exactly as it would have been done before. Guessing it's something down at the C++ wrapping layer but running with `gdb` doesn't give me really any good information. – durden2.0 Apr 11 '13 at 22:08
  • 1
    I'm going to accept this answer because it *technically* should work. However, I think there is either a bug in my decorator **or** replacing `__init__` on a `QtCore.QObject` is not allowed. Regardless, I think this answer represents the best way to accomplish what I want if this behavior is actually possible on `QtCore.QObject`. If you are interested, I've asked a [related question](http://stackoverflow.com/questions/16017397/injecting-function-call-after-init-with-decorator) for the decorator segfault. – durden2.0 Apr 15 '13 at 14:17
  • I saw your new question; sorry, no help from me there! – Martijn Pieters Apr 15 '13 at 14:19
  • @MartijnPieters: This can be done to *__dynamically* decorate attributes__ via `obj.__dict__`, right? – IAbstract May 23 '20 at 12:43
  • @IAbstract decorating is just syntactic sugar, you can manually replace objects with the return value of `decorator(object)` to get the same effect, yes. – Martijn Pieters May 23 '20 at 12:56