Dynamically add a decorator to class

2019-06-17 06:35发布

问题:

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.

回答1:

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.