PyScript - Why Do We Need create_proxy()?
Published October 24, 2022
This seems like a perfectly reasonable thing to do, but upon clicking the button, an error pops up in the developer console:
The usual band-aid is wrap the Python Function in
create_proxy() like so:
Which seems to just make things work... but why?
When you call something like
button.addEventListener("click", hello) (without create_proxy), Pyodide needs to briefly proxy the Python function
hello so the JS function
addEventListener knows how to interact with it. But once
addEventListener terminates, that proxy is no longer needed, it gets destroyed... and then when an event comes around to trigger your function, the proxy it should be using is gone. Which is why you'll see the error above talking about a "borrowed proxy being automatically destroyed".
The two functions that the Error mentions (
create_once_callable()) create a PyProxy (a JS object) of your Python object that you, the user, are supposed to manage the lifetime of, by calling
PyProxy.destroy() on it when you're done with it. Or, if you use
create_once_callable(), the proxy will destroy() itself after the first time it's called.
In practical terms, for something like an event listener, you may never want to destroy the proxy for the lifetime of your page, so you can just leave it hanging around. But it's worth noting that if you remove that event listener or button (maybe in a 'single-page-app' where you're manipulating what's on the page quite a bit), you should plan to track and destroy the PyProxy, otherwise it just hangs around taking up memory.
A Better Solution
Keeping track of the proxies that wrap each of our Python functions sounds like a real pain, no? Thankfully, there's a better way, thanks to some new features in the Pyodide runtime.
Since Pyodide 21.0 (PyScript 2022.09.1), there are now wrappers built into pyodide for adding event listeners: pyodide.ffi.wrappers.add_event_listener() and pyodide.ffi.wrappers.remove_event_listener() which, if you use them in conjunction, will handle proxy creation and destruction for you.
For example, here is the entirety of
You can see that this:
- Creates a proxy of the listener function using
- Adds a reference to that proxy in an internal dictionary for later reference
- Adds the event listener using the browser's
removeEventListener, looks up the appropriate proxy in the internal dictionary, and
So now, our code above would look like:
I personally recommend using these wrapper methods for all new code where possible, instead of using