How serious is the PyFunction_AddWatcher warning about not modifying the function?

Quoting the docs here:

The callback may inspect but must not modify func ; doing so could have unpredictable effects, including infinite recursion.

I’d like to modify the function’s globals object to point to a wrapper around the original that tracks accesses, in effect letting me track what globals are accessed by functions. I don’t see an obvious way this should lead to infinite recursion unless I go out of my way to call code that creates more python function objects. Looking at where handle_func_event(PyFunction_EVENT_CREATE, op, NULL); calls happen in funcobject.c it looks like watchers are only ever notified once the function object is fully filled in, so I don’t think there’s any problem with observing partially formed objects.

I suspect it will just work, but I want to know if I should take the docs to mean “we are very likely to break your use in the future.” That said any function decorator can replace func.__globals__ right after creation, and doing it from a C callback for new function creation seems morally equivalent.

Alternatively, I could just record that the function exists and doesn’t have global use tracking yet, and use the sys.monitoring API to then watch for the first time the bytecode for the function is executed with PY_START, and at that time do the patching, but I assume by then it will be too late. Open to suggestions for other times when it is safe to patch if not from the creation callback.

Moved to the Help category as this is a question about developing using Python, not the development of Python itself.

1 Like

I think @mpage implemented function watcher callbacks and wrote that, so maybe he can answer more definitively.

I don’t see an obvious way this should lead to infinite recursion unless I go out of my way to call code that creates more python function objects.

Yeah, I think that’s fine given the current implementation. Also, amusingly, the motivation for function watchers was to support things like the Cinder JIT, and Cinder doesn’t entirely adhere to that restriction.

I’m not aware of any upcoming changes that would break that use case.

Alternatively, I could just record that the function exists and doesn’t have global use tracking yet, and use the sys.monitoring API…

That sounds a lot more complicated, and my two cents is that complex implementations are a lot more likely to break with future changes, even if they’re not explicitly disclaimed by the docs.

1 Like

What you’re suggesting should just work, and I’m also not aware of any upcoming changes that would break your use case, but it might happen in the future.

For some historical context, these were originally designed to support invalidation of things like inline caches or compiled code that contained inlined functions, which didn’t require mutating the watched object.

1 Like

Thanks for the help, I’ll proceed modifying it and see if I run into any trouble.