很多人第一次碰 Jupyter Notebook,是在浏览器里点开一个链接,地址栏里拖着一长串?token=...,页面一开就是一个闪烁的光标格子,敲两行 Python,Shift+Enter,结果直接出现在下面。用起来确实顺手,但真到自己要在另一台机器上把这套东西跑起来,问题就扎堆冒出来了:端口被占、令牌对不上、浏览器打开一片白、内核莫名其妙连不上、产物导出来格式全乱。这篇就把 Jupyter Notebook 网页使用这条链路从头到尾捋一遍——它到底是什么在跑、怎么把它拉起来、浏览器里那些操作怎么用得快、以及我这些年踩过的坑按什么顺序排查。不管你是刚接触数据脚本的新手,还是已经在服务器上折腾过一阵的人,下面这些细节应该都能捡到几块能直接用的。
1. 网页版 Notebook 的运行模型:三层结构与三种用法的取舍
1.1 浏览器里那一个个单元格,背后其实是三个独立进程
先把这件事说透,后面所有排错都会轻松很多。你在浏览器里看到的 Jupyter Notebook 界面,本质上是三个东西协作的结果:
- 前端页面:一堆 HTML/JS,跑在你的浏览器里,负责渲染单元格、接键盘事件、显示输出。它自己不执行任何 Python。
- 服务进程(Notebook Server):一个跑在某个端口上的 Web 服务,负责读写
.ipynb文件、管理会话、把前端发来的"执行这段代码"请求转发给内核。 - 内核进程(Kernel):真正跑 Python 的那个解释器,独立进程,通过 ZeroMQ 和服务端通信。
理解这三层的关键在于:它们可以分布在不同的机器上,而且任何一层出问题,症状都不一样。浏览器白屏一般是前端资源加载失败或服务没起;页面能打开但点运行没反应,多半是内核没起来或者 WebSocket 通道断了;代码跑着跑着自己停了,是内核崩了但页面还没刷新。
我刚上手那会儿完全没这个概念,看到"Kernel died"就以为是代码写错了,反复重启浏览器,折腾了半小时才发现是服务器内存被吃光了,内核被系统直接杀掉。知道有三层之后,排查就变成了"先确认哪一层坏了",而不是瞎试。
1.2 本地单机、局域网共享、远程服务器:三种形态的取舍
同样是网页使用,部署位置不同,体验和坑点差得很远。我按实际用过的三种场景做个对照:
| 形态 | 典型场景 | 启动方式 | 主要好处 | 主要麻烦 |
|---|---|---|---|---|
| 本地单机 | 个人笔记本上做分析 | 命令行敲jupyter notebook | 零配置,默认自动开浏览器 | 换机器就得重装环境 |
| 局域网共享 | 几个人共用一台工作站 | 绑定内网地址加端口 | 算力集中,文件不用来回拷 | 令牌分发、端口冲突 |
| 远程服务器 | 长期跑任务、数据在远端 | 后台常驻 + 反向代理 | 断网不影响执行 | 访问链路配置、进程保活 |
三种形态里,本地单机最省事,但只要你换一台机器,环境就全丢了,所以我习惯把依赖写进requirements.txt,走到哪装到哪。局域网共享是我用得最多的一种,实验室里一台带显卡的机器,几个人分不同端口各跑一个服务,互不干扰。远程服务器那种则必须考虑进程保活和访问入口,否则 SSH 一断,服务跟着没了。
有个容易被忽略的点:网页版的价值不只是"能用浏览器打开",而是执行环境可以离你的操作设备很远。你的轻薄本只负责显示界面,真正吃内存、跑长任务的机器在别处,这才是它比本地 IDE 更适合数据工作的原因。
2. 从零把服务跑起来:安装、启动、拿到那一串 token
2.1 环境与安装:为什么我更倾向独立的虚拟环境
安装本身一行命令的事:
python -m pip install notebook但我强烈建议先建一个独立环境再装,原因很实际。Jupyter 会往系统里装一堆依赖(tornado、traitlets、jupyter_client、jupyter_server等),这些东西的版本和别的项目很容易打架。我遇到过一次,装完 Notebook 之后另一个项目的 Web 服务起不来了,查了半天是tornado的版本被顶掉了。用独立环境隔离,这类问题直接消失。
python -m venv nb-env source nb-env/bin/activate # Windows 下是 nb-env\Scripts\activate python -m pip install --upgrade pip python -m pip install notebook装完验证一下版本,这个很重要,因为 Notebook 7 和 Notebook 6 在配置文件名、启动参数、界面布局上都有差异,网上搜到的教程经常混着写:
jupyter notebook --version如果输出版本号是 7 开头,说明底层已经换成了新的服务框架,配置文件叫jupyter_server_config.py;如果是 6 开头,配置文件是jupyter_notebook_config.py。两者的配置项前缀也不一样,一个是c.ServerApp.*,一个是c.NotebookApp.*,写错了配置不生效还不报错,这个坑我踩过不止一次。
2.2 启动命令的每一个参数都在解决什么问题
最朴素的启动就是:
jupyter notebook它会做几件事:默认在127.0.0.1:8888起服务,随机生成一个访问令牌,然后尝试调用系统默认浏览器打开。本地自己用完全够。
但只要涉及"别人也要访问",就得改参数了:
jupyter notebook \ --ip=0.0.0.0 \ --port=8899 \ --no-browser \ --notebook-dir=/data/work这几个参数每个都有明确的解决目标,我逐个说:
--ip=0.0.0.0:默认只监听回环地址,外面访问不到。改成0.0.0.0表示监听所有网卡。注意这只是让它"能连",不代表"该连",暴露范围要自己想清楚。--port=8899:默认 8888 太容易被抢。我习惯给每个人分配固定的号段,谁用哪个号记在文档里,省得天天lsof查占用。--no-browser:在远程机器上不加这个,它会尝试调用图形界面打开浏览器,通常报一堆xdg-open相关的错,虽然不影响服务,但刷屏很烦。--notebook-dir:指定文件浏览器的根目录。这个参数用好了很省事,直接把根目录指到项目文件夹,打开就是自己要的文件,不用每次在树形目录里一层层点。
还有一个容易被忽略的:启动时不加&或nohup,关掉终端服务就没了。长期跑的服务我一般这么起:
nohup jupyter notebook --ip=0.0.0.0 --port=8899 --no-browser > ~/jupyter.log 2>&1 &日志重定向到文件,出问题先看日志,比瞎猜快得多。
2.3 忘记 token 与设置密码的正确姿势
令牌这东西,第一次启动时打印在终端里,随手一划就找不到了。两个办法找回来:
jupyter server list这条命令会列出当前正在运行的服务实例、地址和完整令牌,Notebook 7 环境下很好用。老版本可以用jupyter notebook list。
但每次都从地址栏拖一串 token 太麻烦,我一般直接设密码:
jupyter notebook password它会提示你输两遍密码,然后把哈希值写进配置目录下的 JSON 文件里。之后访问只需要输密码,不用管 token。这里有个细节值得记:密码存的是哈希,不是明文,所以忘了只能重设,没法从文件里反查出来。重设就是再执行一遍上面那条命令,覆盖掉旧的。
还有一个常见困惑:设了密码之后,为什么有时候还是会跳到带 token 的地址?因为令牌认证和密码认证是两套并存的机制,只要 token 还有效,走哪个都行。想让行为统一,可以在配置里显式关掉令牌:
c.ServerApp.token = ''不过我得提醒一句,关令牌的前提是你已经设了密码,并且访问范围可控。两个都关掉等于门大开,这个后面第 3 节还会细说。
3. 配置文件与访问链路:端口、base_url、反向代理这三件事
3.1 生成并读懂配置文件(两代版本的差异)
命令行参数适合临时试,长期用还是得落到配置文件。生成命令:
jupyter notebook --generate-config它会在用户目录下的.jupyter文件夹里写一个模板文件,里面几百行全是注释。不建议逐行读,直接找你要改的那几项。我常用的最小配置大概是这样:
# Notebook 7 写法 c.ServerApp.ip = '0.0.0.0' c.ServerApp.port = 8899 c.ServerApp.open_browser = False c.ServerApp.root_dir = '/data/work' c.ServerApp.allow_remote_access = True c.ServerApp.base_url = '/'如果环境里是 Notebook 6,对应的写法是:
c.NotebookApp.ip = '0.0.0.0' c.NotebookApp.port = 8899 c.NotebookApp.open_browser = False c.NotebookApp.notebook_dir = '/data/work'两套配置项名字相似但不通用,写错前缀不会报错,只是静默不生效——这是最坑的地方。判断方法很简单:改完重启,看启动日志里打印的端口和根目录是不是你设的值,对不上就是前缀写错了。
配置文件除了端口和目录,还有几项值得留意:c.ServerApp.allow_root(以 root 身份运行时需要显式允许)、c.ServerApp.max_body_size(上传大文件时的限制)、c.ServerApp.iopub_data_rate_limit(输出数据速率,画大图时容易触发限制导致输出被截断)。
3.2 base_url 与反向代理:WebSocket 是第一个要检查的东西
如果只是局域网直连http://内网地址:8899,上面那些配置就够了。但如果要挂在一个统一的入口后面,就必须处理base_url。
假设你希望访问地址是http://your-host/nb/,配置要写成:
c.ServerApp.base_url = '/nb/'然后反向代理那边要成对配置。以常见的 Web 服务器为例:
location /nb/ { proxy_pass http://127.0.0.1:8899/nb/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 3600s; }这段配置里,最关键的是Upgrade和Connection那两行。Notebook 的页面和内核之间走的是 WebSocket 长连接,如果代理不转发升级头,表现就是:页面能正常打开,文件列表能看,单元格能编辑,但一按运行就卡住,状态一直是[*],永远不出结果。我见过不止一个人在这个现象上怀疑代码有问题,实际是代理少了这两行。
proxy_read_timeout也值得调大,默认值通常只有几十秒,跑一个耗时长一点的任务,代理会先断开,页面提示内核连接丢失,但内核其实还在跑,结果白等。
base_url还有一个容易忽略的细节:结尾的斜杠不能省。写成/nb而不是/nb/,页面里的静态资源路径会拼错,表现是打开一片白,控制台一堆 404。这个我踩过一次,改完斜杠立刻就好了。
3.3 把服务暴露出去之前必须想清楚的几件事
Notebook 的服务端有个特点:它本质上是给你一个能执行任意代码的入口。所以暴露范围这件事,性质上更接近于"给不给别人 SSH 权限",而不是"开不开一个网页"。
我的几条实际做法:
- 内网使用也设密码,别只靠令牌。令牌会出现在浏览器历史、终端日志、聊天记录里,泄露面比想象的大。
- 能限定来源就限定。反向代理层加一条来源地址限制,比在应用层做要直接得多。
- 不要在公开网络直接开端口。需要从外部访问时,走统一入口加身份校验这条路,别图省事。
- 定期看日志。
~/jupyter.log里能看到访问来源和异常请求,出问题时这是第一手材料。
4. 单元格与快捷键:把鼠标操作换成键盘流
4.1 两种模式的切换是效率的分水岭
Notebook 的键盘操作分成两套模式,这个设计一开始让我很困惑:明明光标在格子里,为什么按D没输入字母,反而删掉了一个单元格?
原因是它有两种模式:
- 编辑模式(Edit Mode):单元格边框是绿色,键盘输入进到代码里。
- 命令模式(Command Mode):单元格边框是蓝色,键盘输入被当作快捷指令。
切换很简单:Enter进编辑,Esc回命令。所有"快捷操作"都在命令模式里,这条记住,后面就不会乱。
我刚开始用的那阵子,几乎全靠鼠标点:点加号加格子、点垃圾桶删格子、点下拉框换类型。后来强迫自己用了一周键盘,效率提升非常明显,尤其是整理一个几十个单元格的长文件时。判断自己是不是在用鼠标,有个简单的检验:连续做十次"新增并运行一个单元格",如果每次都要移动鼠标,那就是还没切换过来。
4.2 我实际最常用的那十几个键
不用背全表,记住下面这些就够覆盖 90% 的操作。命令模式下的:
| 按键 | 作用 | 我的使用频率 |
|---|---|---|
Shift+Enter | 运行当前格,选中下一格 | 极高 |
Ctrl+Enter | 运行当前格,停留原地 | 高 |
Alt+Enter | 运行当前格,下方插入新格 | 高 |
A/B | 上方 / 下方插入单元格 | 极高 |
DD | 删除当前单元格 | 极高 |
M/Y | 切 Markdown / 切代码 | 高 |
Shift+M | 合并选中单元格 | 中 |
Ctrl+Shift+- | 在光标处拆分单元格 | 中 |
Z | 撤销删除 | 偶尔救命 |
Ctrl+Shift+P | 打开命令面板 | 中 |
编辑模式下常用的:Ctrl+]缩进、Ctrl+[反缩进、Ctrl+/注释切换、Ctrl+Z撤销、Ctrl+Shift+Z重做、Tab补全、Shift+Tab看函数签名。
Shift+Tab那个我要单独说,它是我用得最值的一个:光标停在函数名括号里,按一下显示最简签名,按两下展开完整文档,按三下变成一个小窗固定在页面上。查第三方库参数的时候,比切到浏览器搜文档快太多。
Z这个也值得一提。DD删单元格是直接删,没有二次确认,手滑连按两下DD就可能把正在写的一格删掉。这时候在命令模式按Z,能把最近删的恢复回来。但它只记最近几次,而且是会话级的,页面一刷新就没了,所以重要内容还是别靠它兜底。
4.3 单元格类型切换与 Markdown 里的隐藏能力
Markdown 单元格除了写标题和列表,还有几个用法我经常用。
第一个是用 Markdown 做章节导航。在长文件里,我给每一大块写#、##、###标题,再配合目录插件,就能生成可点击的大纲。没有大纲的百格文件翻起来是灾难。
第二个是用 Markdown 记录分析思路。我的习惯是每个代码块上面留一格 Markdown,写清楚"这里为什么要这么做、数据从哪来、预期结果是什么"。过两周回来再看,代码本身能读懂,但当时的判断依据只有记下来才不会丢。
第三个是LaTeX 公式。行内用$...$,独占一行用$$...$$,做算法推导或者统计公式说明时很直观,不用另开文档。
还有一个实用点:Markdown 单元格可以在渲染后的状态下双击进入编辑,改完Shift+Enter重新渲染。这个交互在整理文档时很顺手。
5. Magic 命令:把网页端用出命令行之外的效率
5.1 行魔法与单元魔法
Magic 命令是 Notebook 特有的语法糖,分两类:单%的行魔法作用于一行,双%%的单元魔法作用于整个单元格。
几个我几乎每个文件都会用的:
%matplotlib inline这行让图表直接嵌在页面里显示。虽然现在很多版本默认就是这个行为,但我还是会显式写上,因为换环境时行为不一定一致,写死了省心。
%load_ext autoreload %autoreload 2这两行解决一个很烦的问题:你写了一个辅助模块,import进来之后改了源码,Notebook 里还是旧的行为,得重启内核。加上这两行,每次执行前会自动重新加载模块,改完直接生效。代价是导入会慢一点,而且某些有状态的模块重载后行为可能不对,所以我在开发阶段开,交付前会把它关掉。
%%time放在单元格第一行,测整个格的耗时。%%timeit会跑多次取平均,适合比较两种写法的性能差异。我对比两种数据处理方式时基本都用%%timeit,比自己写time.time()前后夹要省事,而且它自动处理了预热和重复。
5.2 时间、内存与输出控制
除了计时,还有一类是控制输出规模。Notebook 最常见的卡死原因之一就是输出太多:一个循环打了几十万行,或者一张图渲染得巨大,浏览器直接卡住,甚至页面崩溃,未保存的内容一起没了。
几个应对手段:
%%capture # 这一格的输出全部被截获,不显示%%capture cap # 输出存进变量 cap,之后可以用 cap.stdout 取出来看如果只是想在循环里少打点,最简单的还是自己控制:
for i, item in enumerate(items): if i % 1000 == 0: print(i, item)另一个细节:单元格输出是存进.ipynb文件里的。一个带几十张高清图的笔记本,文件轻松上百 MB,打开慢、传输慢、版本比对更没法看。所以我在交付前会清一次输出,或者用后面第 7 节说的导出方式。
5.3 文件与环境的穿梭
几个让网页端和文件系统打通的命令:
%pwd # 当前工作目录 %ls # 列文件 %cd /data # 切换目录 %run script.py # 直接跑一个外部脚本,变量会进到当前命名空间 %load script.py # 把脚本内容加载成一个单元格(方便逐段调试)%run和%load这对组合我很喜欢:%load把脚本内容拉进单元格,你可以逐段执行、看中间变量;%run则是整体跑一遍,跑完变量留在命名空间里继续用。调试一个长脚本时,先用%load拆开看,定位到问题后再用%run验证。
还有环境相关的:
%env # 列出所有环境变量 %env MY_VAR=value # 临时设置一个 %pip install requests # 在当前内核所属环境里装包%pip这个要特别注意。它装到的是当前内核对应的那个 Python 环境里,而不是随便哪个环境。以前我吃过亏:命令行pip install装到系统环境,但内核用的是虚拟环境,import死活找不到,查了半天才发现装错地方了。用%pip就避免了这个问题,因为它明确知道自己在哪个环境里。
6. 内核才是真正干活的进程:生命周期、切换与故障排查
6.1 内核、环境、Notebook 文件三者的关系
这三个东西经常被混为一谈,但它们的生命周期完全独立:
.ipynb文件:磁盘上的文本文件,本质是 JSON,存单元格内容和历史输出。- 内核进程:内存里的解释器,存着所有变量的值。重启内核,变量全没,但文件内容不动。
- Python 环境:内核启动时所用的那个解释器和已装的包。
理解这个区分的实际价值在于:"我改了代码为什么没生效"和"我重启了为什么变量还在"这两个问题,答案都藏在三者关系里。
举个具体的:你在一个虚拟环境 A 里装了pandas,但 Notebook 启动时选的内核指向环境 B,那import pandas就会失败,尽管你明明"装过了"。解决办法是把环境注册成独立内核:
python -m ipykernel install --user --name=myenv --display-name="Python (myenv)"之后在页面右上角的内核选择里就能看到Python (myenv),选中它,用的就是这个环境的解释器。--display-name是给人看的名字,--name是内部标识,两个别写反,反了以后列表里全是看不懂的乱名。
查看已注册的内核:
jupyter kernelspec list不需要的可以删掉:
jupyter kernelspec remove myenv6.2 内核挂死与内存爆炸的现场还原
我遇到最多的一类问题,症状是:单元格左边显示[*],一直转,不出结果,点中断按钮也没反应。
这种情况按可能性从高到低排列,我遇到过的是这么几种:
第一种,真的在算。一个大的矩阵运算或者训练循环,本来就要跑几分钟。先看 CPU 占用,如果是满的,那就是正常在跑,等就行。判断方法是看机器负载,别急着中断。
第二种,内存吃满被系统杀掉。表现是内核突然"死"了,页面弹Kernel Restarting,之前的变量全丢。这个我遇到过一次典型的:读了一个几百 MB 的表,做了几次merge没及时释放中间结果,内存翻了几倍,进程被系统直接终止。这类问题的难点在于它不报错,只是进程消失,日志里可能只有一行Killed。
应对办法是养成随手释放的习惯:
del big_df import gc gc.collect()同时监控内存:
%load_ext memory_profiler %memit 你的操作%memit会告诉你这一步涨了多少内存,比事后猜要准。
第三种,输出去向被阻塞。这个比较隐蔽:如果代理的 WebSocket 通道断了,内核执行完的结果发不回前端,前端就一直等。表现和"卡住"一模一样,但 CPU 是空闲的。判断方法就是看 CPU,空闲 + 页面一直转 = 通道问题,去查反向代理配置。
6.3 一份按顺序走的排查清单
遇到"点了没反应",我现在的顺序是这样的,从外往内,一层层排除:
| 步骤 | 检查内容 | 判断依据 |
|---|---|---|
| 1 | 浏览器控制台有没有报错 | 有 404 是静态资源路径问题,多半和base_url有关 |
| 2 | 服务端日志有没有请求进来 | 没请求说明卡在前端或代理层 |
| 3 | 代理有没有转发升级头 | 页面正常但执行无响应,基本是这里 |
| 4 | 内核是否存活 | 右上角内核状态、jupyter server list |
| 5 | 机器 CPU / 内存 | 满了就是真在算或者被杀了 |
| 6 | 端口是否被占 | lsof -i:8899看有没有别的进程占着 |
这个顺序的好处是每一步都有明确的判断依据,不是靠猜。我最早的时候是倒着来的,先怀疑代码、再怀疑环境,最后才想到代理,绕了很大一圈。现在基本三分钟内能定位到层。
补一个具体的查看端口命令:
lsof -i:8899 # 或者 netstat -tlnp | grep 8899如果发现端口被一个残留进程占着,直接kill -9那个 PID 再重启。这种情况多发生在非正常退出之后,进程没清理干净。
7. 从 .ipynb 到可交付产物:导出、比对与清理
7.1 nbconvert 的常用姿势
.ipynb适合自己用,但给别人看就不一定了。转成静态格式是最常见的需求:
jupyter nbconvert --to html notebook.ipynb jupyter nbconvert --to markdown notebook.ipynb jupyter nbconvert --to script notebook.ipynb我按场景选格式:给不装环境的人看,用 HTML,样式完整、能离线打开;要进文档系统或者代码仓库,用 Markdown;要变成可复用的模块,转成.py最直接。
还有一种情况是交付时希望对方看到的是"重新跑一遍的结果",避免中间态残留:
jupyter nbconvert --to html --execute notebook.ipynb--execute会从头到尾重新执行一遍再导出。这个参数我第一次用的时候被坑过:笔记本里有一段代码依赖某个本地文件,重新执行时目录不对,直接报错中断了整个导出。所以用它之前,先确认笔记本是可以从头到尾无交互跑通的——这也是个好习惯,能自动跑通的笔记本才是真正可复现的。
转 PDF 需要额外的组件支持,环境里不一定有,如果只是要个文档,HTML 转一下更省事。
7.2 执行顺序混乱与输出膨胀这两个经典的烂摊子
执行顺序问题是 Notebook 最被诟病的一点,也确实是真问题。单元格编号[1] [2] [7] [3]这种状态意味着你的代码跑过好几轮,变量状态和文件里写的顺序完全对不上。别人拿到你的笔记本,从头执行一遍,很可能在第 5 格就报错,因为那里依赖了一个在第 7 格才创建的变量。
我的处理习惯是:交付前一定做一次"重启内核并全部运行"。页面菜单里就有这个操作,或者用命令行:
jupyter nbconvert --to notebook --execute --inplace notebook.ipynb跑完不报错,才算这个笔记本是干净的。跑不通的地方就是隐藏的依赖问题,早晚会暴露。
输出膨胀在前面提过,这里给个清理手段:
jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace notebook.ipynb这会把所有单元格的输出和execution_count清掉,文件立刻瘦下来。清之前记得留一份备份,因为图表的输出也一起没了,有些分析结果本身就是图,看着图才能理解结论。
还有一个细节:运行会在同目录下生成.ipynb_checkpoints文件夹,里面是自动保存的历史版本。它不占多少空间,但提交代码或者打包目录时最好排除掉,不然会带一堆冗余文件进去。
7.3 版本管理里怎么对待 .ipynb
.ipynb是 JSON,直接做文本比对基本没法读——你只想改一行代码,diff 出来可能是几百行,因为重点是元数据和输出的变化。
我自己的几条土办法:
第一,提交前清输出。这条能消掉 80% 的无意义 diff。上面那条清除命令可以写进一个脚本,提交前跑一下。
第二,关键的分析逻辑抽到.py文件里。Notebook 只保留调用和展示,逻辑放模块里。这样逻辑的变更在 diff 里是清晰的几行,而 Notebook 本身的变化就不重要了。这个习惯我坚持了挺长时间,好处是复用性也上来了——同一段处理逻辑,多个笔记本可以共用。
第三,用专门的比对工具。有些工具能按单元格粒度做 diff,比纯文本比对直观得多。如果团队里多人协作,这个投入是值得的。
第四,重要的笔记本配一份导出产物。比如 HTML 版本一起提交,别人不装环境也能看结论,代码本身的问题也不影响阅读。
8. 我用了几年之后沉淀下来的几条习惯
最后说几条不成体系但确实管用的习惯,都是一次次返工换来的。
第一个习惯:每个笔记本的头部留一个环境说明格。写清楚 Python 版本、关键包的版本、数据文件从哪来。换机器或者隔几个月回来,这一格能省掉大量考古时间。
第二个习惯:单元格不要太长。一个格超过一屏,说明它做了太多事,拆开更好调。拆的时候用Ctrl+Shift+-在光标处切,比重新建格再复制要快。
第三个习惯:不要在网页端做文件操作。重命名、移动、删除这些,我都在命令行里做。网页端的文件列表操作没有回收站,删了就是删了,而且浏览器卡顿的时候容易点错。
第四个习惯:长任务不要依赖页面连接。页面一关,前端的会话就断了。真正耗时的任务,我写成脚本用%run触发或者直接命令行后台跑,Notebook 里只留触发和结果读取的部分。这样断网、换设备都不影响。
第五个习惯:定期看一眼启动日志。~/jupyter.log里的信息量比想象的大,异常访问、内核崩溃、资源警告都在里面。我有个内核反复被杀的问题,就是日志里看到连续的资源相关告警才定位到的,光看页面永远只能看到"内核重启"这个结果,看不到原因。
这套东西说到底就一个核心:把浏览器当成一个远程终端来用,而不是当成一个本地编辑器。它真正的价值在于执行环境可以离你很远,界面可以随时打开。接受了这一点,很多设计和坑就都变得合理了。