You're absolutely right that %autoreload 2 is powerful — but it has one key caveat: it doesn't fully work with from module import something style imports. When you do:
from mymodule.utils import my_function
Python stores a reference to my_function in your notebook's namespace. If the function's code changes in the source file, the reference doesn't automatically update — auto reload can’t help you here because it's only watching the module, not the symbol reference.
Solutions
Best Practice (Recommended): Use Module-Level Imports
Instead of importing individual functions, import the module:
import mymodule.utils as utils
Then use:
utils.my_function()
This way, %autoreload 2 can detect and reload the updated code.
Alternative : Manually Reload + Reimport
If you really want to keep using from ... import ..., you can manually reload and re-import:
import importlib
import mymodule.utils
importlib.reload(mymodule.utils)
from mymodule.utils import my_function # re-import the updated version
Still a bit messy, but much faster than restarting the kernel.