Reputation: 1255
I want to modify how IPython handles import errors by default. When I prototype something in the IPython shell, I usually forget to first import os
, re
or whatever I need. The first few statements often follow this pattern:
In [1]: os.path.exists("~/myfile.txt")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-1-0ffb6014a804> in <module>()
----> 1 os.path.exists("~/myfile.txt")
NameError: name 'os' is not defined
In [2]: import os
In [3]: os.path.exists("~/myfile.txt")
Out[3]: False
Sure, that's my fault for having bad habits and, sure, in a script or real program that makes sense, but in the shell I'd rather that IPython follow the DWIM principle, by at least trying to import what I am trying to use.
In [1]: os.path.exists("~/myfile.txt")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-1-0ffb6014a804> in <module>()
----> 1 os.path.exists("~/myfile.txt")
NameError: name 'os' is not defined
Catching this for you and trying to import "os" … success!
Retrying …
---------------------------------------------------------------------------
Out[1]: False
If this is not possible with a vanilla IPython, what would I have to do to make this work? Is a wrapper kernel the easiest way forward? Or should this be implemented directly in the core, with a magic command?
Note, this is different from those kind of question where someone wants to always load pre-defined modules. I don't. Cuz I don't know what I will be working on, and I don't want to load everything (nor do I want to keep the list of everything updated.
Upvotes: 7
Views: 549
Reputation: 11807
NOTE: This is now being maintained on Github. Download the latest version of the script from there!
I developed a script that binds to IPython's exception handling through set_custom_exc
. If there's a NameError
, it uses a regex to find what module you tried to use and then attempt to import it. It then runs the function you tried to call again. Here is the code:
import sys, IPython, colorama # <-- colorama must be "pip install"-ed
colorama.init()
def custom_exc(shell, etype, evalue, tb, tb_offset=None):
pre = colorama.Fore.CYAN + colorama.Style.BRIGHT + "AutoImport: " + colorama.Style.NORMAL + colorama.Fore.WHITE
if etype == NameError:
shell.showtraceback((etype, evalue, tb), tb_offset) # Show the normal traceback
import re
try:
# Get the name of the module you tried to import
results = re.match("name '(.*)' is not defined", str(evalue))
name = results.group(1)
try:
__import__(name)
except:
print(pre + "{} isn't a module".format(name))
return
# Import the module
IPython.get_ipython().run_code("import {}".format(name))
print(pre + "Imported referenced module \"{}\", will retry".format(name))
except Exception as e:
print(pre + "Attempted to import \"{}\" but an exception occured".format(name))
try:
# Run the failed line again
res = IPython.get_ipython().run_cell(list(get_ipython().history_manager.get_range())[-1][-1])
except Exception as e:
print(pre + "Another exception occured while retrying")
shell.showtraceback((type(e), e, None), None)
else:
shell.showtraceback((etype, evalue, tb), tb_offset=tb_offset)
# Bind the function we created to IPython's exception handler
IPython.get_ipython().set_custom_exc((Exception,), custom_exc)
You can make this run automatically when you start an IPython prompt by saving it somewhere and then setting an environment variable called PYTHONSTARTUP
to the path to this file. You set environment variables differently depending on your OS (remember to alter the paths):
setx PYTHONSTARTUP C:\startup.py
in command promptexport PYTHONSTARTUP=$HOME/startup.py
into your ~/.bashrc
Here's a demo of the script in action:
Upvotes: 11