I was trying to figure out what a Django app was doing today. It turned out that the original developer had decided to monkey patch a third party app. I hardly need say that monkey patching is evil (i.e. a last resort), but one of the things I needed to check along the way was that there was no code being triggered by a (Django) signal, and the problems it causes are very similar to monkey patching.The problem with signals is that it is not obvious to someone else reading your code what is going on. I had a similar problem a few weeks ago when I found a signal was already doing the same as code I was writing in a view. In both cases it added a lot of extra time (proportionate to the fairly simple problems I was fixing) to work out what was going on.
The is exactly the same as the worst of the problems caused by monkey patching. It makes code less readable. A signal could be anywhere in a project, and it could do anything. You look at a view and think you know what it does, but there could be code anywhere that does something you do not know about. Readability counts.
Of course monkey patching is worse. For one thing the risk of incompatibilities break on upgrade is much higher: signals use a well defined API, whereas monkey patching is entirely unpredictable and may change anything.
What are the alternatives to signals? You can often put the code somewhere else. In fact you can always put the code somewhere else if you are willing to fork when signals come from other people’s code. For model signals you can usually use the save method instead, for others you may be able to to subclass or wrap views.
Like monkey patching signals should be used reluctantly, because the alternative is even worse.