如何检查代码是否在IPython笔记本中执行?

146

我有一些Python代码示例想要分享,如果在终端Python/IPython或IPython笔记本中执行,应该会执行不同的操作。

如何从我的Python代码中检查它是否在IPython笔记本中运行?


8
我建议接受Gustavo Bezerra的答案。目前被接受的答案没有回答问题,而Gustavo的答案是分数最高的答案,而且在最新版本的Jupyter Notebook中仍然有效。 - Mark Amery
17个回答

131

以下方法适用于我的需求:

get_ipython().__class__.__name__

在终端IPython上返回'TerminalInteractiveShell',在Jupyter上返回'ZMQInteractiveShell'(包括notebook和qtconsole),并在普通Python解释器上失败(抛出NameError)。当启动IPython时,函数get_ipython()默认情况下在全局命名空间中可用。

将其包装在一个简单的函数中:

def is_notebook() -> bool:
    try:
        shell = get_ipython().__class__.__name__
        if shell == 'ZMQInteractiveShell':
            return True   # Jupyter notebook or qtconsole
        elif shell == 'TerminalInteractiveShell':
            return False  # Terminal running IPython
        else:
            return False  # Other type (?)
    except NameError:
        return False      # Probably standard Python interpreter

以上内容是在macOS 10.12和Ubuntu 14.04.4 LTS上使用Python 3.5.2、IPython 5.1.0和Jupyter 4.2.1进行测试的。

编辑:在2022年,使用更新的Python/IPython/Jupyter/操作系统版本仍然可以正常工作。


7
在Jupyter控制台上,不幸的是,get_ipython()返回的实例也是ZMQInteractiveShell - Josh Bode
15
如果有人想检测笔记本是否在 Google Colab 上运行,可以检查以下内容:get_ipython().__class__.__module__ == "google.colab._shell" - guiferviz
4
这只适用于笔记本中的代码。如果该函数在一个导入的包中,它就不起作用。 - Christopher Barber
4
@ChristopherBarber,我看到的不是这样。如果我将这个函数粘贴到一个名为 test.py 的文件中,然后在 Jupyter Notebook 中运行 from test import isnotebook; print(isnotebook()),它会打印出 True。(已在笔记本服务器版本为 5.2.1 和 6.0.1 的情况下进行测试。) - Mark Amery
2
这个答案的代码是在你输入函数名def we_are_in_jupyter()时由Github Copilot自动生成的 :) https://imgur.com/9mx9COw 你已经到达了AI的大脑。 - Jules G.M.
显示剩余2条评论

44
您可以使用以下代码片段[1]来检查Python是否处于交互模式:
def is_interactive():
    import __main__ as main
    return not hasattr(main, '__file__')

我发现这种方法非常有用,因为我在笔记本上进行了很多原型制作。为了测试目的,我使用默认参数。否则,我从 sys.argv 读取参数。

from sys import argv

if is_interactive():
    params = [<list of default parameters>]
else:
    params = argv[1:]

在实施了 autonotebook 之后,您可以使用以下代码来确定是否处于笔记本中。

def in_notebook():
    try:
        from IPython import get_ipython
        if 'IPKernelApp' not in get_ipython().config:  # pragma: no cover
            return False
    except ImportError:
        return False
    except AttributeError:
        return False
    return True

python -c "def is_interactive():
import __main__ as main return not hasattr(main, '__file__')print is_interactive()" True
- marscher
7
is_interactive()无法区分笔记本和控制台。 - krock
1
另一个需要注意的是,在IPython中执行%run命令是非交互式的。你可以认为它应该是交互式的,但这仍然是一个陷阱。 - dirkjot
3
这个答案的后半部分很有用,但是关于is_interactive的前半部分对于问题来说似乎基本上无关紧要。它还存在可疑的正确性; 正如@marscher指出的那样,即使这不是真的,它也会将使用python -c运行的任何东西都计算为处于“交互”模式。我不想自己做这件事,因为这不是我的回答,但我认为通过简单删除答案的整个前半部分可以使其更好。 - Mark Amery
2
请注意,当在普通的Python shell或正在运行的脚本中运行时,get_ipython将返回None,因此此代码需要防范这种情况。 - Lucas Wiman
显示剩余2条评论

41

如果您想检查当前是否在notebook中,这可能很重要,例如在确定使用哪种进度条时,可以使用以下方法:

def in_ipynb():
    try:
        cfg = get_ipython().config 
        if cfg['IPKernelApp']['parent_appname'] == 'ipython-notebook':
            return True
        else:
            return False
    except NameError:
        return False

8
在我的IPython-Notebook(IPython版本3.1)中,cfg['IPKernelApp']['parent_appname']是一个IPython.config.loader.LazyConfigValue,它与“iypthon-notebook”不相等。 - Dux
5
在ipynb(Jupyter)中,@juanjux get_ipython返回一个IPython.kernel.zmq.zmqshell.ZMQInteractiveShell实例,在终端REPL中则返回一个IPython.terminal.interactiveshell.TerminalInteractiveShell实例。如果你需要区分笔记本和终端/控制台(这会影响绘图),这就很有用了。 - hobs
4
因此,您可以将try块内部替换为:return str(type(get_ipython())) == "<class'ipykernel.zmqshell.ZMQInteractiveShell'>" - user2561747
1
像@Dux一样,这对我不起作用;即使在笔记本中也总是返回false。怀疑随着某种懒惰配置加载系统的引入,此答案已经过时。 - Mark Amery
1
请注意,您的配置可能会返回一个空字典,在这种情况下,您需要将KeyError添加到except块中。然而,最好使用Gustavo Bezerra答案中的代码。即使我得到了一个空的配置,当检查类名时,我也会得到shell='PyDevTerminalInteractiveShell' - hlongmore

27

最近我在Jupyter笔记本中遇到了一个错误,需要一个解决方法,而且我希望在不影响其他shell的功能的情况下完成。我意识到keflavich的解决方案在这种情况下不起作用,因为get_ipython()只能直接从笔记本中使用,而不能从导入的模块中使用。所以我找到了一种方法来检测我的模块是否被导入并且是在Jupyter笔记本中使用的:

import sys

def in_notebook():
    """
    Returns ``True`` if the module is running in IPython kernel,
    ``False`` if in IPython shell or other Python shell.
    """
    return 'ipykernel' in sys.modules

# later I found out this:

def ipython_info():
    ip = False
    if 'ipykernel' in sys.modules:
        ip = 'notebook'
    elif 'IPython' in sys.modules:
        ip = 'terminal'
    return ip

如果这足够健壮,欢迎留下评论。

同样的方法也可以获取有关客户端和IPython版本的一些信息:

import sys

if 'ipykernel' in sys.modules:
    ip = sys.modules['ipykernel']
    ip_version = ip.version_info
    ip_client = ip.write_connection_file.__module__.split('.')[0]

# and this might be useful too:

ip_version = IPython.utils.sysinfo.get_sys_info()['ipython_version']

嗯,我正在使用Fedora 23 Jupyter,而且'Ipython' in sys.modules的评估结果为False。也许你的意思是'IPython' in sys.modules?在我的Jupyter环境中,这个表达式的值为True。当在笔记本中运行时,sys.modules字典也不包括'ipykernel'键。 - maxschlepzig
4
在我看来,这是迄今为止最好的答案。简短而精炼。 - danielpcox
@danielpcox,那个子答案哪个是最佳答案? - jtlz2
一些软件包错误地检测到在Jupyter中运行,然后导入ipykernel,这会导致此逻辑返回错误结果(例如Weights and Biases软件包)。因此,它将在大多数情况下工作,但并非总是如此。 - David Gilbertson

13

测试过适用于Python 3.7.3

CPython实现中将__builtins__作为全局变量之一,可以通过函数globals()检索。如果脚本在Ipython环境中运行,则__IPYTHON__应该是__builtins__的属性。因此,以下代码在Ipython环境下运行时返回True,否则返回False

hasattr(__builtins__,'__IPYTHON__')

1
简单易懂! - Janosh
3
你应该 import builtins 并检查 hasattr(builtins, "__IPYTHON__"),因为 __builtins__ 是一个实现细节,可能会发生变化。 - Janosh
4
被踩了——在IPython会话中也是如此。 - Maximilian
2
@jtlz2 你可能忘记在字符串周围加引号了。 - Carlos Pinzón
1
这是在IPython中运行的一个很好的测试,但不适用于在笔记本中运行。例如,在使用IPython时,在PyCharm Python控制台中返回true。 - David Gilbertson
显示剩余4条评论

4
问题是您希望执行什么不同的操作。
我们尽力在IPython中防止内核知道连接到哪种前端,实际上,您甚至可以同时连接到许多不同的前端的内核。 即使您可以偷看 stderr / out 类型以知道您是否在ZMQ内核中,这并不能保证您拥有什么。您甚至可能没有任何前端。
您应该以独立于前端的方式编写代码,但如果您想显示不同的内容,则可以使用丰富的显示系统(链接固定到 IPython 版本4.x)来根据前端显示不同的内容,但选择是由前端决定而不是库决定。

45
我有一个例子:进度条。Jupyter笔记本终端仿真器不支持扩展终端控制字符,如 \x1b[A(向上移动),因此无法打印嵌套的进度条。使用ipywidgets没有问题,我们可以使用本地Jupyter小部件来显示进度条。但是现在我们有了两种不同的进度条显示方式,应用程序可能想要知道当前显示环境,以便适应并打印兼容的进度条。 - gaborous
3
例如,我想将IPython配置设置为始终在notebook模式下运行%matplotlib inline,但在终端中不需要这样做。 - Ciprian Tomoiagă
9
虽然这个观点完全正确,但它并没有回答实际问题。无论你希望情况如何,行为上的差异始终可能很重要。 - Christopher Barber
3
意图是好的,但实践不同。让代码依赖于环境是完全合理的原因。我的应用程序是启动文件startup.ipy。在终端IPython中,为了方便起见,我喜欢预先导入一些包。但在笔记本中,这被认为是不良的风格,并且不必要,因为我可以把导入放在第一个单元格中,而不需要一遍又一遍地输入它们。 - A. Donda
6
这不回答所提出的问题。标题是“如何检查代码是否在IPython笔记本中执行?”,请更改正确的答案或问题的标题。 - zolastro
显示剩余7条评论

4

您只需要将这两个单元格放在笔记本的开头:

单元格 1: (标记为“代码”):

is_notebook = True

单元格2:(标记为“原始NBConvert”):

is_notebook = False

第一个单元格总是会被执行,但当您将笔记本导出为 Python 脚本时,第二个单元格只会被执行一次。
随后,您可以进行检查:
if is_notebook:
    notebook_code()
else:
    script_code()

希望这可以帮到您。

哈哈,我真的很喜欢这种方法。 :-) 好极了。 - sh37211

3

一个非常简单和有效的解决方案是检查调用堆栈的顶部是否指向IPython环境,如下所示:

import traceback

def is_in_notebook():
    rstk = traceback.extract_stack(limit=1)[0]
    return rstk[0].startswith("<ipython")

这段代码适用于Python 2和3,在IPython或Jupyter上都能运行,无需检查、设置或更改环境。

有趣。你能给出你的调用栈内容吗? - Olivier OUDOT
在使用 Powershell 和 Jupyter 时,似乎在 Windows 上无法正常工作。两者都返回 false。请解释这应该如何工作?输出是什么? - not2qubit

2
据我所知,这里有三种使用 ipykernel 的ipython:
  1. ipython qtconsole(简称“qtipython”)
  2. spyder 中的 IPython(简称“spyder”)
  3. jupyter notebook 中的 IPython(简称“jn”)
使用'spyder' in sys.modules可以区分 spyder,但是对于 qtipython 和 jn,很难区分,因为它们具有相同的sys.modules和相同的 IPython 配置:get_ipython().config
我找到了qtipython 和 jn 之间的不同点:
首先在 IPython shell 中运行os.getpid()来获取 pid 号码,
然后运行ps -ef|grep [pid number]
我的 qtipython pid 是 8699。
yanglei   8699  8693  4 20:31 ?        00:00:01 /home/yanglei/miniconda2/envs/py3/bin/python -m ipykernel_launcher -f /run/user/1000/jupyter/kernel-8693.json

my jn pid is 8832

yanglei   8832  9788 13 20:32 ?        00:00:01 /home/yanglei/miniconda2/bin/python -m ipykernel_launcher -f /run/user/1000/jupyter/kernel-ccb962ec-3cd3-4008-a4b7-805a79576b1b.json

qtipython和jn的区别在于IPython的JSON名称,jn的JSON名称比qtipython的长。

因此,我们可以使用以下代码自动检测所有Python环境:

import sys,os
def jupyterNotebookOrQtConsole():
    env = 'Unknow'
    cmd = 'ps -ef'
    try:
        with os.popen(cmd) as stream:
            if not py2:
                stream = stream._stream
            s = stream.read()
        pid = os.getpid()
        ls = list(filter(lambda l:'jupyter' in l and str(pid) in l.split(' '), s.split('\n')))
        if len(ls) == 1:
            l = ls[0]
            import re
            pa = re.compile(r'kernel-([-a-z0-9]*)\.json')
            rs = pa.findall(l)
            if len(rs):
                r = rs[0]
                if len(r)<12:
                    env = 'qtipython'
                else :
                    env = 'jn'
        return env
    except:
        return env
    
pyv = sys.version_info.major
py3 = (pyv == 3)
py2 = (pyv == 2)
class pyi():
    '''
    python info
    
    plt : Bool
        mean plt avaliable
    env :
        belong [cmd, cmdipython, qtipython, spyder, jn]
    '''
    pid = os.getpid()
    gui = 'ipykernel' in sys.modules
    cmdipython = 'IPython' in sys.modules and not gui
    ipython = cmdipython or gui
    spyder = 'spyder' in sys.modules
    if gui:
        env = 'spyder' if spyder else jupyterNotebookOrQtConsole()
    else:
        env = 'cmdipython' if ipython else 'cmd'
    
    cmd = not ipython
    qtipython = env == 'qtipython'
    jn = env == 'jn'
    
    plt = gui or 'DISPLAY' in os.environ 

print('Python Envronment is %s'%pyi.env)

源代码在这里:检测Python环境,特别是区分Spyder、Jupyter notebook和Qtconsole.py


1
有趣。当然,这里的问题是不可移植性。你正在调用特定于POSIX的ps命令。那很糟糕。除非你在运行Windows子系统Linux(WSL)下,否则Windows不兼容POSIX,这意味着它在全球最流行的平台上失败了。哎呀。 - Cecil Curry

2
以下内容涵盖了https://dev59.com/AWUp5IYBdhLWcg3wPFz_#50234148的情况,无需解析ps的输出。
def pythonshell():
    """Determine python shell

    pythonshell() returns

    'shell' (started python on command line using "python")
    'ipython' (started ipython on command line using "ipython")
    'ipython-notebook' (e.g., running in Spyder or started with "ipython qtconsole")
    'jupyter-notebook' (running in a Jupyter notebook)

    See also https://dev59.com/AWUp5IYBdhLWcg3wPFz_#37661854
    """

    import os
    env = os.environ
    shell = 'shell'
    program = os.path.basename(env['_'])

    if 'jupyter-notebook' in program:
        shell = 'jupyter-notebook'
    elif 'JPY_PARENT_PID' in env or 'ipython' in program:
        shell = 'ipython'
        if 'JPY_PARENT_PID' in env:
            shell = 'ipython-notebook'

    return shell

对我来说,这只是向 jupyter 显示它是 jupyter consolejupyter qtconsole 还是 jupyter notebook - Luke Davis

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