让IPython“猜”你想导入什么模块

7

我希望修改IPython默认处理导入错误的方式。当我在IPython shell中原型设计时,我经常忘记先导入 os, re 或其他需要的依赖库。前几个语句通常遵循这个模式:

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

当然,这是我的坏习惯,对于脚本或真实的程序而言,这是有意义的,但在shell中,我更希望IPython遵循DWIM原则,至少尝试导入我要使用的内容。
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

如果使用原版IPython无法实现,我该怎么做才能让它工作?使用包装内核是最简单的方法吗?还是应该直接在核心中实现,使用魔术命令?请注意,这与那些问题不同,其中某人想要始终加载预定义模块。 我不需要。 因为我不知道我将要处理什么,也不想加载所有内容(也不想保持所有内容的列表更新)。
1个回答

11

注意: 这份代码现在正在 Github 上维护。请从该网站下载最新版本的脚本!

我开发了一个脚本,通过set_custom_exc绑定到IPython的异常处理程序上。如果出现NameError,它会使用正则表达式查找您尝试使用的模块,然后尝试导入它。然后再次运行您尝试调用的函数。以下是代码:

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)

您可以将此脚本保存在某个位置,并将环境变量称为PYTHONSTARTUP,并设置为此文件的路径,以便在启动IPython提示符时自动运行它。根据您的操作系统不同,设置环境变量的方法也不同(记得修改路径):
  • Windows:在命令提示符中输入setx PYTHONSTARTUP C:\startup.py
  • Mac/Linux(bash):将export PYTHONSTARTUP=$HOME/startup.py放入您的~/.bashrc
以下是脚本演示:

Demo


目前,这个脚本在某些错误上会无限循环——如果导入结果出现NameError并且清理例程也执行同样的导入……你已经知道会发生什么。你需要检查你尝试导入的模块是否存在。 - Łukasz Rogalski
@Rogalski 我该怎么做?我知道 pip 有方法可以做到这一点,但有人可能正在导入他们本地机器上的某些东西。 - Aaron Christiansen
@Rogalski 谢谢,我正在更新它以支持此功能,并具有更清晰的输出。 - Aaron Christiansen
很好,使用IPython异常处理程序似乎是合适的!为什么你刚刚将NameError作为set_custom_exc的第一个参数传递?最后,有没有办法区分在库或程序代码中发生的NameError和在shell中发生的NameError?如果我的程序代码有错误,导致出现这样的NameError,那么尝试导入变量名只会让事情变得更糟。 - mknecht
我认为,我们需要进行一些堆栈跟踪分析之类的工作...无论如何,我认为你的答案是正确的方向。 - mknecht
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接