How do I update an app handler function without restarting Blender each time?

I need some advice on best practices to use while testing app handlers.
Let’s say I want to test a script like this:

import bpy

def render_function(dummy):
    print("Render done!")

bpy.app.handlers.render_complete.append(render_function)

So I click “Run Script” in Blender’s text editor and try it out and it works! But now I want to try a small change:

import bpy

def render_function(dummy):
    print("The render is complete!")

bpy.app.handlers.render_complete.append(render_function)

If I run this script, it appends my function again. Now I have TWO functions that are called by the app handler- the new one and the old one- and my console output looks like this:

Render done!
The render is complete!

Obviously running these duplicate functions is no good, but I never learned how to remove the old one in order to append the new one. So I’ve just been restarting Blender to test my script every time! It’s a very slow process. What’s a better way to do this?

the handlers are just collections, you can call bpy.app.handlers.render_complete.remove(render_function) before appending it again. in your case, you’re just running a script from the editor- it’s probably smarter to wrap it in a dummy operator first so you have easy access to register/unregister.

That’s the issue though; I can’t seem to remove the function in that way. I get this result in the console:

ValueError: list.remove(x): x not in list

The app handler function still works like before, and I don’t know how to remove it without restarting Blender.

To clean up every handlers, you could do something like this :

for handler in bpy.app.handlers.render_complete:
    bpy.app.handlers.render_complete.remove(handler)

Thanks, that works!

I have also found that bpy.app.handlers.render_complete.clear() works as well. I am still unable to select exactly which app handler gets cleared but this is better than what I was doing before.

be aware that if you clear everything in this way you run the risk of removing handlers that other addons have added. all scripts and addons share the same collection of handlers and trust that each script will manage only its own functions.

That’s the issue though; I can’t seem to remove the function in that way. I get this result in the console:

That’s why you need to do the thing I mentioned and wrap your functionality into an operator so you have access to the register/unregister calls. When you reload your script, your old function is still resident in memory at a different address until something overwrites that memory, so when your “new script” (post-reload) calls render_complete.remove() you’re trying to remove the new function at the new address which obviously doesn’t do anything because you haven’t added it yet.

if you absolutely have to set up your script in this way and can’t properly unregister the handler during the script’s lifetime it would be far better to name your handler something very unique to avoid the potential of a naming collision with other addons.

for h in bpy.app.handlers.render_complete:
    if h.__name__ == 'my_super_unique_function_name':
        bpy.app.handlers.render_complete.remove(h)

aaaahhhaaa! I knew there was a hack you’re not supposed to let bad programmers know about! That’s what I was looking for. Thanks for the tip on unique names btw; I’ll be sure to call my function “render_function” because that’s the function that runs when you render. No chance of confusion there. Thanks again!