Attach to all available signals in decoratored class and print debug
message each time one of the signals is emitted.
from PyQt4 import QtCore
Take a signature provided by QMetaMethod.signature() and strip off the
arguments, types, etc. The signature passed in will be suitable for a
C++ declaration, but we are in Python so the types, etc. aren't too
useful. We just want the name of the method.
However, the type information could be useful if you're overriding a
slot with multiple versions of the same method (i.e. different types)
with the pyqtSignal decorator. See pyqtSignal decorator docs for more
# This returns the full signature with type etc. like C++ would
# want, we just want attribute names so strip everything after
# the '('.
paren_idx = signature.index('(')
Find all user-defined signals provided by the wrapped class
# Must happen within closure so we have access to 'self' b/c it's
# the implicit parameter in QMetaObject calls
m = self.metaObject()
first_method = m.methodOffset()
last_method = m.methodCount()
signals = 
for ii in xrange(first_method, last_method):
if m.method(ii).methodType() == QtCore.QMetaMethod.Signal:
signature = stripArgsFromSignature(m.method(ii).signature())
def attachDebugToSignals(self, signal_names):
"""Attach our debug slot to all given signal names"""
for name in signal_names:
# Include signal name when we call it to give a bit more context
# for debugging what signal was emitted.
debug_func = partial(self._signalDebugger, name)
def getSlotName(self, sender_qobj):
Get name of slot executing from sender
sender should be self.sender() passed from within the slot
# FIXME: This probably isn't too useful b/c it will always be our slot
# b/c we just attached to each signal, we didn't OVERRIDE and inject
# into the existing slot. However, leaving this code here b/c it might
# be a useful trick if we can ever actually inject into the existing
# slots or override the emit() signal call to call us back.
# Added in Qt 4.8
idx = sender_qobj.senderSignalIndex()
slot_name = ''
m = self.metaObject()
slot_name = stripArgsFromSignature(m.method(idx).signature())
def signalDebugger(self, signal_name, *args):
This is the debugging 'slot.' It's the method that's called each time
one of the signals we connected to is emitted.
Debugging goes here!
sender = self.sender()
sender = 'Unknown'
slot_name = self._getSlotName(sender)
# For bonus points override __str__ in your classes and you'll get to
# see that here..
print 'sender: %s\nsignal_name: %s\nslot_name: %s\nargs: %s\n' % (
sender, signal_name, slot_name, args)
'Public' API to attach our own slot to all signals in wrapped class
# Replace init so we can automatically attach our signal debugging AFTER
# the 'real' init runs
orig_init = cls.__init__
def new_init(self, *args, **kwargs):
'Extend' wrapped class' __init__ so we can attach to all signals
orig_init(self, *args, **kwargs)
# Expose helper methods for what we do here so we people using decorator
# could call them later in their code if needed.
cls._getSignalNames = getSignalNames
cls._attachDebugToSignals = attachDebugToSignals
cls._signalDebugger = signalDebugger
cls._debugSignals = debugSignals
cls._getSlotName = getSlotName
cls.__init__ = new_init