在软件中,有人说所有的抽象都是有漏洞的,这对Jupyter笔记本来说是真的,对其他软件来说也是如此。我最常看到的是以下问题的表现。
我安装了X包,但现在我无法在笔记本中导入它。帮助!
这个问题是StackOverflow问题的一个常年来源(例如,这个,那个,这里,那里,另一个,这个,那个,还有这个......等等)。
从根本上说,这个问题的根源在于Jupyter内核与Jupyter的shell是断开的;换句话说,安装程序指向的Python版本与笔记本中使用的不同。在最简单的情况下,这个问题不会出现,但当它出现时,调试这个问题需要了解操作系统的复杂性、Python包安装的复杂性以及Jupyter本身的复杂性。换句话说,Jupyter笔记本,像所有的抽象概念一样,是有漏洞的。
在与同事们就这个话题进行了几次讨论之后,有些是在线讨论(展品A,展品B),有些是离线讨论,我决定在这里深入处理这个问题。这篇文章将讨论几件事。
-
首先,我将对一般的问题提供一个快速的、赤裸裸的回答,即我如何使用 pip 和/或 conda 来安装一个 Python 软件包,使其与我的 jupyter 笔记本一起工作?
-
第二,我将深入探讨Jupyter笔记本抽象的一些背景,它是如何与操作系统的复杂性互动的,以及你如何思考 "泄漏 "在哪里,从而更好地理解当事情停止工作时发生了什么。
-
第三,我将谈谈社区可以考虑的一些想法,以帮助解决这些问题,包括Jupyter、Pip和Conda开发者可以考虑的一些变化,以减轻用户的认知负担。
这篇文章将重点讨论安装Python包的两种方法:Pip和Conda。其他软件包管理器也存在(包括特定平台的工具,如yum、apt、homebrew等,以及跨平台的工具,如enstaller),但我对它们不太熟悉,不会对它们作进一步评论。
快速修复:如何从Jupyter笔记本中安装软件包?
如果你只是在寻找一个快速的答案,即我如何安装软件包,使它们能够在笔记本上工作,那么就不要再看了。
pip vs. conda¶
首先,关于pip
vs.conda
。对于许多用户来说,在pip和conda之间的选择可能是一个令人困惑的问题。我在去年的一篇文章中写了很多关于这些的内容,但两者的本质区别是这样的。
- pip在任何环境下安装python软件包。
- conda在conda环境中安装任何软件包。
如果你已经有一个正在使用的Python安装包,那么选择使用哪个就很容易了。
-
如果你用Anaconda或Miniconda安装了Python,那么就用
conda
来安装Python包。如果 conda 告诉你你想要的软件包不存在,那么使用 pip (或者尝试conda-forge,它比默认的 conda 频道有更多的软件包可用)。 -
如果你用其他方式安装了 Python (从源代码,使用 pyenv,virtualenv,等等),那么使用
pip
来安装 Python 包。
最后,因为它经常出现,我应该提到,你永远不应该使用sudo pip install
。
从长远来看,它总是会导致问题,即使它在短期内似乎能解决这些问题。例如,如果pip install
给你一个权限错误,这很可能意味着你正试图在系统python中安装/更新软件包,例如/usr/bin/python
。这样做可能会有不好的后果,因为操作系统本身往往依赖于该 Python 安装中的特定版本的软件包。对于日常的Python使用,你应该将你的软件包与系统Python隔离,使用虚拟环境或Anaconda/Miniconda- 我个人更喜欢conda,但我知道很多同事更喜欢virtualenv。
如何从Jupyter笔记本中使用Conda?
如果你在jupyter笔记本中,想用conda安装一个软件包,你可能会想使用!
符号,从笔记本中直接将conda作为shell命令运行。
在[1]中
# DON'T DO THIS!
!conda install --yes numpy
Fetching package metadata ...........
Solving package specifications: .
# All requested packages already installed.
# packages in environment at /Users/jakevdp/anaconda/envs/python3.6:
#
numpy 1.13.3 py36h2cdce51_0
(注意,我们使用--yes
,在conda要求用户确认时自动回答y
)
由于各种原因,我将在下面详细介绍,如果你想从当前的笔记本中使用这些已安装的软件包,这通常是行不通的,尽管它在最简单的情况下可能会起作用。
这里有一个简短的片段,在一般情况下应该可以工作。
在[2]中
# Install a conda package in the current Jupyter kernel
import sys
!conda install --yes --prefix {sys.prefix} numpy
Fetching package metadata ...........
Solving package specifications: .
# All requested packages already installed.
# packages in environment at /Users/jakevdp/anaconda:
#
numpy 1.13.3 py36h2cdce51_0
这一点额外的模板确保conda将软件包安装在当前运行的Jupyter内核中(感谢Min Ragan-Kelley建议的这种方法)。我稍后会讨论为什么需要这样做。
如何从Jupyter笔记本中使用Pip?
如果你正在使用Jupyter笔记本,并且想用pip
来安装一个包,你同样可能倾向于直接在shell中运行pip。
在[3]中
# DON'T DO THIS
!pip install numpy
Requirement already satisfied: numpy in /Users/jakevdp/anaconda/envs/python3.6/lib/python3.6/site-packages
由于各种原因,我将在下面更全面地概述,如果你想使用当前笔记本中的这些已安装的包,这通常不会起作用,尽管在最简单的情况下可能会起作用。
这里有一个简短的片段,一般来说应该可以工作。
在[4]中
# Install a pip package in the current Jupyter kernel
import sys
!{sys.executable} -m pip install numpy
Requirement already satisfied: numpy in /Users/jakevdp/anaconda/lib/python3.6/site-packages
这段额外的模板确保你运行的是与当前 Python 内核相关的pip
版本,这样安装的包就可以在当前的笔记本中使用。这与以下事实有关:即使把 Jupyter 笔记本放在一边,也最好使用
$ python -m pip install <package>
而不是
$ pip install <package>
因为前者更明确地说明了包将被安装在哪里 (下面会有更多的介绍)。
细节:为什么从Jupyter安装如此混乱?¶
上述解决方案应该在所有情况下都有效......但为什么还需要额外的模板?简而言之,这是因为在Jupyter中,shell环境和Python可执行文件是断开连接的。理解为什么这很重要,取决于对几个不同概念的基本理解。
- 你的操作系统是如何定位可执行程序的。
- Python 是如何安装和定位软件包的
- Jupyter 如何决定使用哪个 Python 可执行程序。
为了完整起见,我将对这些主题中的每一个进行简单的探讨(这个讨论部分来自我去年写的StackOverflow答案)。
注意:下面的讨论是假设Linux、Unix、MacOSX和类似的操作系统。Windows的架构略有不同,因此一些细节会有所不同。
你的操作系统是如何定位可执行文件的?
当你在使用终端时,输入诸如python
,jupyter
,ipython
,pip
,conda
等命令时,你的操作系统包含一个明确的机制来查找该名称所指的可执行文件。
在Linux和Mac系统中,系统将首先检查是否有与该命令相匹配的别名;如果失败,它将引用$PATH
环境变量。
在[5]中。
!echo $PATH
/Users/jakevdp/anaconda/envs/python3.6/bin:/Users/jakevdp/anaconda/envs/python3.6/bin:/Users/jakevdp/anaconda/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
$PATH
列出了按顺序搜索任何可执行文件的目录:例如,如果我在我的系统上输入 ,上面有 ,它将首先寻找 ,如果该目录不存在,它将寻找 ,以此类推。python
$PATH
/Users/jakevdp/anaconda/envs/python3.6/bin/python
/Users/jakevdp/anaconda/bin/python
(题外话:为什么$PATH
的第一个条目在这里重复两次?因为每次你启动jupyter notebook
,Jupyter都会把jupyter
可执行文件的位置提前到$PATH
的开头。在这种情况下,这个位置已经在路径的开头了,结果就是这个条目被重复了。重复的条目会增加杂乱,但不会造成伤害)。
如果你想知道当你输入python
,实际上执行的是什么,你可以使用type
shell命令。
在[6]中。
!type python
python is /Users/jakevdp/anaconda/envs/python3.6/bin/python
注意,这对你在终端使用的任何命令都是如此。
在[7]中。
!type ls
ls is /bin/ls
甚至是内置的命令,如type
本身。
在 [8]:
!type type
type is a shell builtin
你可以选择添加-a
标签来查看当前shell环境中所有可用的命令版本;例如。
在[9]中。
!type -a python
python is /Users/jakevdp/anaconda/envs/python3.6/bin/python
python is /Users/jakevdp/anaconda/envs/python3.6/bin/python
python is /Users/jakevdp/anaconda/bin/python
python is /usr/bin/python
在[10]中。
!type -a conda
conda is /Users/jakevdp/anaconda/envs/python3.6/bin/conda
conda is /Users/jakevdp/anaconda/envs/python3.6/bin/conda
conda is /Users/jakevdp/anaconda/bin/conda
在[11]中。
!type -a pip
pip is /Users/jakevdp/anaconda/envs/python3.6/bin/pip
pip is /Users/jakevdp/anaconda/envs/python3.6/bin/pip
pip is /Users/jakevdp/anaconda/bin/pip
当你有任何命令的多个可用版本时,重要的是要记住$PATH
在选择使用哪个版本时的作用。
Python 是如何定位软件包的?
Python 使用一个类似的机制来定位导入的包。Python 在导入时搜索的路径列表可以在sys.path
中找到。
In[12]:
import sys
sys.path
Out[12]:
['', '/Users/jakevdp/anaconda/lib/python36.zip', '/Users/jakevdp/anaconda/lib/python3.6', '/Users/jakevdp/anaconda/lib/python3.6/lib-dynload', '/Users/jakevdp/anaconda/lib/python3.6/site-packages', '/Users/jakevdp/anaconda/lib/python3.6/site-packages/schemapi-0.3.0.dev0+791c7f6-py3.6.egg', '/Users/jakevdp/anaconda/lib/python3.6/site-packages/setuptools-27.2.0-py3.6.egg', '/Users/jakevdp/anaconda/lib/python3.6/site-packages/IPython/extensions', '/Users/jakevdp/.ipython']
默认情况下,Python 寻找模块的第一个地方是一个空路径,即当前工作目录。如果在那里没有找到模块,它就沿着位置列表向下寻找,直到找到模块。你可以通过导入的模块的__path__
属性来了解哪个位置已经被使用。
In[13]:
import numpy
numpy.__path__
Out[13]。
['/Users/jakevdp/anaconda/lib/python3.6/site-packages/numpy']
在大多数情况下,你用pip
或conda
安装的 Python 包会被放在一个叫做site-packages
的目录中。需要认识到的重要一点是,每个 Python 可执行程序都有自己的site-packages
:这意味着当你安装一个包时,它与特定的 Python 可执行程序相关联,并且默认情况下只能与该 Python 安装一起使用
我们可以通过打印我路径中每个可用的python
可执行文件的sys.path
变量来看到这一点,使用Jupyter令人愉快的能力,在一个代码块中混合使用Python和bash命令。
在[14]中。
paths = !type -a python
for path in set(paths):
path = path.split()[-1]
print(path)
!{path} -c "import sys; print(sys.path)"
print()
/Users/jakevdp/anaconda/envs/python3.6/bin/python
['', '/Users/jakevdp/anaconda/envs/python3.6/lib/python36.zip', '/Users/jakevdp/anaconda/envs/python3.6/lib/python3.6', '/Users/jakevdp/anaconda/envs/python3.6/lib/python3.6/lib-dynload', '/Users/jakevdp/anaconda/envs/python3.6/lib/python3.6/site-packages']
/usr/bin/python
['', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python27.zip', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-darwin', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/plat-mac/lib-scriptpackages', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-old', '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload', '/Library/Python/2.7/site-packages', '/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python', '/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/PyObjC']
/Users/jakevdp/anaconda/bin/python
['', '/Users/jakevdp/anaconda/lib/python36.zip', '/Users/jakevdp/anaconda/lib/python3.6', '/Users/jakevdp/anaconda/lib/python3.6/lib-dynload', '/Users/jakevdp/anaconda/lib/python3.6/site-packages', '/Users/jakevdp/anaconda/lib/python3.6/site-packages/schemapi-0.3.0.dev0+791c7f6-py3.6.egg', '/Users/jakevdp/anaconda/lib/python3.6/site-packages/setuptools-27.2.0-py3.6.egg']
这里的全部细节并不特别重要,但需要强调的是,每个Python可执行文件都有自己不同的路径,除非你修改sys.path
(只有在非常小心的情况下才可以这样做),否则你无法导入安装在不同Python环境下的包。
当你运行pip install
或conda install
时,这些命令与一个特定的 Python 版本相关。
pip
在其相同路径的 Python 中安装软件包conda
将软件包安装在当前活动的 conda 环境中
所以,例如我们看到pip install
将安装到名为python3.6
的 conda 环境中。
在 [15] 中。
!type pip
pip is /Users/jakevdp/anaconda/envs/python3.6/bin/pip
而conda install
也会这样做,因为python3.6
是当前的活动环境(注意*
表示活动环境)。
在[16]中。
!conda env list
# conda environments:
#
python2.7 /Users/jakevdp/anaconda/envs/python2.7
python3.5 /Users/jakevdp/anaconda/envs/python3.5
python3.6 * /Users/jakevdp/anaconda/envs/python3.6
rstats /Users/jakevdp/anaconda/envs/rstats
root /Users/jakevdp/anaconda
pip
和conda
都默认为 condapython3.6
环境的原因是,这是我用来启动笔记本的 Python 环境。
为了强调这一点,我再说一遍:Jupyter笔记本中的shell环境与用于启动笔记本的Python版本一致。
Jupyter如何执行代码。Jupyter Kernels¶
下一个相关的问题是Jupyter如何选择执行Python代码,这就把我们带到了Jupyter Kernel的概念。
Jupyter内核是一组文件,它将Jupyter指向笔记本中执行代码的某种方式。对于Python内核,它将指向一个特定的Python版本,但Jupyter的设计要比这更通用。Jupyter有几十个可用的语言内核,包括Python 2、Python 3、Julia、R、Ruby、Haskell,甚至还有C++和Fortran!
如果你正在使用Jupyter笔记本,你可以在任何时候使用内核→选择内核菜单项来改变你的内核。
要查看你的系统中可用的内核,你可以在shell中运行以下命令。
在[17]:
!jupyter kernelspec list
Available kernels:
python3 /Users/jakevdp/anaconda/envs/python3.6/lib/python3.6/site-packages/ipykernel/resources
conda-root /Users/jakevdp/Library/Jupyter/kernels/conda-root
python2.7 /Users/jakevdp/Library/Jupyter/kernels/python2.7
python3.5 /Users/jakevdp/Library/Jupyter/kernels/python3.5
python3.6 /Users/jakevdp/Library/Jupyter/kernels/python3.6
每一个列出的内核都是一个目录,其中包含一个叫做kernel.json
的文件,该文件指定了,除其他事项外,内核应该使用哪种语言和可执行文件。比如说。
在[18]中。
!cat /Users/jakevdp/Library/Jupyter/kernels/conda-root/kernel.json
{
"argv": [
"/Users/jakevdp/anaconda/bin/python",
"-m",
"ipykernel_launcher",
"-f",
"{connection_file}"
],
"display_name": "python (conda-root)",
"language": "python"
}
如果你想创建一个新的内核,你可以使用jupyter ipykernel命令来完成;例如,我使用下面的模板为我的主要conda环境创建了上述内核。
$ source activate myenv
$ python -m ipykernel install --user --name myenv --display-name "Python (myenv)"
问题的根源
现在我们有了完整的背景来回答我们的问题。为什么!pip install
或!conda install
总是不能从笔记本上工作?
问题的根源在于:当Jupyter笔记本启动时,shell环境是确定的,而Python可执行文件是由内核确定的,两者不一定匹配。$PATH
换句话说,不能保证你的python
,pip
, 和conda
与笔记本使用的python
可执行文件兼容。
回顾一下,你的路径中的python
,可以用以下方法确定
在 [19] 中。
!type python
python is /Users/jakevdp/anaconda/envs/python3.6/bin/python
笔记本中使用的Python可执行文件可以通过以下方式确定
In [20]:
sys.executable
Out[20]:
'/Users/jakevdp/anaconda/bin/python'
在我目前的笔记本环境中,这两者是不同的。这就是为什么简单的!pip install
或!conda install
不起作用的原因:这些命令在错误的 Python 安装的site-packages
中安装软件包。
如上所述,我们可以通过明确指出我们想要安装软件包的位置来解决这个问题。
对于conda,你可以在 shell 命令中手动设置前缀。
$ conda install --yes --prefix /Users/jakevdp/anaconda numpy
或者,自动使用正确的前缀(使用笔记本中的语法)。
!conda install --yes --prefix {sys.prefix} numpy
对于pip,你可以明确指定 Python 的可执行文件。
$ /Users/jakevdp/anaconda/bin/python -m pip install numpy
或者,自动使用正确的可执行文件(同样使用笔记本中的shell语法)。
!{sys.executable} -m pip install numpy
记住:如果你希望安装的软件包在笔记本中可用,你需要你的安装命令与当前的 python 内核相匹配。
一些适度的建议¶
所以,综上所述,在Jupyter笔记本中安装软件包充满困难的原因,从根本上说是Jupyter的shell环境和Python内核不匹配,这意味着你必须做更多的事情,而不是简单的pip install
或conda install
来使事情顺利进行。例外的情况是,你从你的内核指向的同一个Python环境中运行jupyter notebook
;在这种情况下,简单的安装方法应该是有效的。
但这使我们处于一个不理想的境地,因为它增加了新手的学习曲线,他们可能想做一些他们(正确地)认为应该很简单的事情:安装一个包,然后使用它。那么,作为一个社区,我们能做些什么来解决这个问题呢?
我有几个想法,其中一些甚至可能是有用的。
对Jupyter的潜在改变
正如我所提到的,根本问题是Jupyter的外壳环境和计算内核之间的不匹配。那么,我们能不能对内核规格进行调整,使之与之相匹配?
也许可以:例如,这个github问题展示了一种修改shell变量的方法,作为内核启动的一部分。
基本上,在你的内核目录中,你可以添加一个脚本kernel-startup.sh
,看起来像这样(并确保你改变了权限,使其可以执行)。
#!/usr/bin/env bash
# activate anaconda env
source activate myenv
# this is the critical part, and should be at the end of your script:
exec python -m ipykernel $@
然后在你的kernel.json
文件中,把argv
字段修改成这样。
"argv": [
"/path/to/kernel-startup.sh",
"-f",
"{connection_file}"
]
一旦你这样做,切换到myenv
内核将自动激活myenv
conda环境,这将改变你的$CONDA_PREFIX
,$PATH
和其他系统变量,这样!conda install XXX
和!pip install XXX
就可以正常工作。类似的方法可以适用于virtualenvs或其他Python环境。
这里有一个棘手的问题:如果你的myenv
环境没有安装ipykernel
包,这个方法就会失败,而且可能还要求它的 jupyter 版本与用于启动笔记本的版本兼容。所以这绝不是问题的完整解决方案,但是如果Python内核可以被设计成默认做这种shell初始化,就不会让用户感到困惑了:!pip install
和!conda install
就可以工作了。
对 pip¶ 的潜在改变
即使在Jupyter之外,安装混乱的一个来源是,根据你的系统别名和$PATH
变量的性质,pip
和python
可能指向不同的路径。在这种情况下,pip install
会将软件包安装到python
可执行文件无法访问的路径上。由于这个原因,使用python -m pip install
更加安全,它明确地指定了所需的 Python 版本(毕竟,明确的比隐含的好)。
这也是pip install
不再出现在Python 文档中的原因之一,而且像 David Beazley 这样有经验的 Python 教育家从来不教裸奔。CPython 开发者 Nick Coghlan 甚至表示,pip
可执行文件可能有一天会被废弃,而改用python -m pip
。尽管它更啰嗦,但我认为强迫用户显式会是一个有用的改变,特别是随着 virtualenvs 和 conda envs 的使用变得越来越普遍。
对Conda的修改
我可以想到对conda的API进行一些修改,这可能对用户有帮助
明确的调用¶
为了与pip
对称,如果python -m conda install
能够以与pip
对应的方式工作就更好了。你可以在根环境中这样调用conda
,但目前除了根环境外,conda Python 包(相对于 conda 可执行文件)不能安装在任何地方。
(myenv) jakevdp$ conda install conda
Fetching package metadata ...........
InstallError: Error: 'conda' can only be installed into the root environment
我怀疑在所有的 conda 环境中允许python -m conda install
将需要对 conda 的安装模型进行相当大的重新设计,所以仅仅为了与pip
的 API 对称,可能不值得改变。尽管如此,这样的对称性对用户来说肯定是有帮助的。
conda的pip通道?¶
conda 可以做的另一个有用的改变是增加一个通道,它基本上反映了Python Package Index,所以当你做conda install some-package
时,它也会自动从pip
可用的包中提取。
我对conda的架构没有足够深入的了解,不知道这样的功能会有多容易实现,但我确实有很多帮助Python和/或conda新手的经验:我可以肯定地说,这样的功能会在很大程度上缓和他们的学习曲线。
新的Jupyter魔法功能
即使上述对堆栈的改变是不可能的或不可取的,我们也可以通过在Jupyter笔记本中引入%pip
和%conda
魔术函数来简化用户体验,这些函数可以检测当前的内核并确保软件包安装在正确的位置。
pip magic¶
例如,这里你可以定义一个%pip
魔法函数,在当前内核中工作。
在[21]中。
from IPython.core.magic import register_line_magic
@register_line_magic
def pip(args):
"""Use pip from the current kernel"""
from pip import main
main(args.split())
按如下方式运行,将软件包安装在预期的位置上
在[22]中。
%pip install numpy
Requirement already satisfied: numpy in /Users/jakevdp/anaconda/lib/python3.6/site-packages
请注意,Jupyter开发者Matthias Bussonnier已经在他的pip_magic仓库中发布了基本的内容,所以你可以做
$ python -m pip install pip_magic
并立即使用这个(也就是说,假设你在正确的地方安装了pip_magic
!)
conda magic¶
同样地,我们可以定义一个conda magic,如果你输入%conda install XXX
,它将做正确的事情。这比pip
magic要复杂一些,因为它必须首先确认环境是与conda兼容的,然后(与缺少python -m conda install
有关)必须调用一个子进程来执行适当的shell命令。
在[23]中。
from IPython.core.magic import register_line_magic
import sys
import os
from subprocess import Popen, PIPE
def is_conda_environment():
"""Return True if the current Python executable is in a conda env"""
# TODO: make this work with Conda.exe in Windows
conda_exec = os.path.join(os.path.dirname(sys.executable), 'conda')
conda_history = os.path.join(sys.prefix, 'conda-meta', 'history')
return os.path.exists(conda_exec) and os.path.exists(conda_history)
@register_line_magic
def conda(args):
"""Use conda from the current kernel"""
# TODO: make this work with Conda.exe in Windows
# TODO: fix string encoding to work with Python 2
if not is_conda_environment():
raise ValueError("The python kernel does not appear to be a conda environment. "
"Please use ``%pip install`` instead.")
conda_executable = os.path.join(os.path.dirname(sys.executable), 'conda')
args = [conda_executable] + args.split()
# Add --prefix to point conda installation to the current environment
if args[1] in ['install', 'update', 'upgrade', 'remove', 'uninstall', 'list']:
if '-p' not in args and '--prefix' not in args:
args.insert(2, '--prefix')
args.insert(3, sys.prefix)
# Because the notebook does not allow us to respond "yes" during the
# installation, we need to insert --yes in the argument list for some commands
if args[1] in ['install', 'update', 'upgrade', 'remove', 'uninstall', 'create']:
if '-y' not in args and '--yes' not in args:
args.insert(2, '--yes')
# Call conda from command line with subprocess & send results to stdout & stderr
with Popen(args, stdout=PIPE, stderr=PIPE) as process:
# Read stdout character by character, as it includes real-time progress updates
for c in iter(lambda: process.stdout.read(1), b''):
sys.stdout.write(c.decode(sys.stdout.encoding))
# Read stderr line by line, because real-time does not matter
for line in iter(process.stderr.readline, b''):
sys.stderr.write(line.decode(sys.stderr.encoding))
现在你可以使用%conda install
,它将把软件包安装到正确的环境中。
在[24]中。
%conda install numpy
Fetching package metadata ...........
Solving package specifications: .
# All requested packages already installed.
# packages in environment at /Users/jakevdp/anaconda:
#
numpy 1.13.3 py36h2cdce51_0
这个conda魔法仍然需要一些工作才能成为一个普遍的解决方案(参考代码中的TODO注释),但我认为这是一个有用的开始。
如果在Jupyter的默认魔法命令集中加入与上述类似的pip魔法和conda魔法,我认为这将大大有助于解决用户在试图安装Python软件包以用于Jupyter笔记本时遇到的常见问题。不过这种方法也不是没有危险的:这些魔法是另一层抽象,就像所有的抽象一样,不可避免地会泄漏。但是,如果它们被谨慎地实施,我认为这将导致一个更好的整体用户体验。
总结
在这篇文章中,我试图一劳永逸地回答一个长期存在的问题:我如何在Jupyter笔记本中安装Python包。
在提出了一些今天可以使用的简单解决方案后,我详细解释了为什么这些解决方案是必要的:归根结底,在Jupyter中,内核与外壳是断开的。内核环境可以在运行时改变,而外壳环境是在笔记本启动时确定的。我认为,一个完整的解释需要这么多字,涉及这么多的概念,这表明Jupyter生态系统存在一个真正的可用性问题,因此我提出了一些可能的途径,社区可以采用这些途径来尝试简化用户的体验。
最后补充一点:我对构成Python数据科学生态系统基础的Jupyter、conda、pip和相关工具的开发者有着极大的尊重和赞赏。我相当肯定这些开发者已经考虑到了这些问题,并权衡了其中一些潜在的修复方法--如果你们中的任何一个人正在读这篇文章,请随时评论并纠正我所忽略的任何问题最后,感谢你们为开源社区所做的一切。