DL专栏11-Jupyter Notebooks


Jupyter Notebooks基础

Jupyter Notebooks 是数据科学/机器学习社区内一款非常流行的工具。Analytics Vidhya 的 Pranav Dar 近日发表了一篇上手使用 Jupyter Notebooks 的指南,从安装到基本功能进行了简洁清晰的介绍。

引言

应该使用哪个 IDE/环境/工具?这是人们在做数据科学项目时最常问的问题之一。可以想到,我们不乏可用的选择——从 R Studio 或 PyCharm 等语言特定的 IDE 到 Sublime Text 或 Atom 等编辑器——选择太多可能会让初学者难以下手。

如果说有什么每个数据科学家都应该使用或必须了解的工具,那非 Jupyter Notebooks 莫属了(之前也被称为 iPython 笔记本)。Jupyter Notebooks 很强大,功能多,可共享,并且提供了在同一环境中执行数据可视化的功能。

Jupyter Notebooks 允许数据科学家创建和共享他们的文档,从代码到全面的报告都可以。它们能帮助数据科学家简化工作流程,实现更高的生产力和更便捷的协作。由于这些以及你将在下面看到的原因,Jupyter Notebooks 成了数据科学家最常用的工具之一。

目录

  • Jupyter Notebooks 是什么?
  • 如何安装 Jupyter Notebooks?
  • 开始上手!
  • 使用神奇的功能
  • 不只限于 Python——在 Jupyter Notebooks 中使用 R、Julia 和 JavaScript
  • Jupyter Notebooks 中的交互式仪表盘——何乐不为?
  • 键盘快捷键——节省时间且更有生产力!
  • 有用的 Jupyter Notebooks 扩展
  • 保存和共享你的笔记本
  • JupyterLab——Jupyter Notebooks 的进化
  • 最佳实践和技巧

Jupyter Notebooks 是什么?

Jupyter Notebooks 是一款开源的网络应用,我们可以将其用于创建和共享代码与文档。

其提供了一个环境,你无需离开这个环境,就可以在其中编写你的代码、运行代码、查看输出、可视化数据并查看结果。因此,这是一款可执行端到端的数据科学工作流程的便捷工具,其中包括数据清理、统计建模、构建和训练机器学习模型、可视化数据等等。

当你还处于原型开发阶段时,Jupyter Notebooks 的作用更是引人注目。这是因为你的代码是按独立单元的形式编写的,而且这些单元是独立执行的。这让用户可以测试一个项目中的特定代码块,而无需从项目开始处执行代码。很多其它 IDE 环境(比如 RStudio)也有其它几种方式能做到这一点,但我个人觉得 Jupyter 的单个单元结构是最好的。

正如你将在本文中看到的那样,这些笔记本非常灵活,能为数据科学家提供强大的交互能力和工具。它们甚至允许你运行 Python 之外的其它语言,比如 R、SQL 等。因为它们比单纯的 IDE 平台更具交互性,所以它们被广泛用于以更具教学性的方式展示代码。

如何安装 Jupyter Notebooks?

你可能已经猜到了,你首先需要在你的机器上安装 Python。Python 2.7 或 Python 3.3(或更新版本)都可以。

1.Anaconda

对新用户而言,一般的共识是你应该使用 Anaconda 发行版来安装 Python 和 Jupyter Notebooks。

Anaconda 会同时安装这两个工具,并且还包含相当多数据科学和机器学习社区常用的软件包。你可以在这里下载最新版的 Anaconda:https://www.anaconda.com/download

2.pip 方法

如果你因为某些原因不愿意使用 Anaconda,那么你需要确保你的机器运行着最新版的 pip。该怎么做呢?如果你已经安装了 Python,那么就已经有 pip 了。你可以使用以下代码升级到最新版的 pip:

#Linux and OSX
pip install -U pip setuptools

#Windows
python -m pip install -U pip setuptools

pip 安装好之后,继续安装 Jupyter:

#For Python2
pip install jupyter

#For Python3
pip3 install jupyter

你可以在这里查看官方的 Jupyter 安装文档:https://jupyter.readthedocs.io/en/latest/install.html

开始上手!

现在你已经知道这些笔记本是什么以及如何将其安装到你的机器上了。现在开始使用吧!

要运行你的 Jupyter Notebooks,只需在命令行输入以下命令即可!

jupyter notebook

完成之后,Jupyter Notebooks 就会在你的默认网络浏览器打开,地址是:

http://localhost:8888/tree

在某些情况下,它可能不会自动打开。而是会在终端/命令行生成一个 URL,并带有令牌密钥提示。你需要将包含这个令牌密钥在内的整个 URL 都复制并粘贴到你的浏览器,然后才能打开一个笔记本。

打开笔记本后,你会看到顶部有三个选项卡:Files、Running 和 Clusters。其中,Files 基本上就是列出所有文件,Running 是展示你当前打开的终端和笔记本,Clusters 是由 IPython 并行提供的。

要打开一个新的 Jupyter 笔记本,点击页面右侧的「New」选项。你在这里会看到 4 个需要选择的选项:

  • Python 3
  • Text File
  • Folder
  • Terminal

选择 Text File,你会得到一个空面板。你可以添加任何字母、单词和数字。其基本上可以看作是一个文本编辑器(类似于 Ubuntu 的文本编辑器)。你可以在其中选择语言(有很多语言选项),所以你可以在这里编写脚本。你也可以查找和替换该文件中的词。

选择 Folder 选项时,你会创建一个新的文件夹,你可以在其中放入文件,重命名或删除它。各种操作都可以。

Terminal 完全类似于在 Mac 或 Linux 机器上的终端(或 Windows 上的 cmd)。其能在你的网络浏览器内执行一些支持终端会话的工作。在这个终端输入 python,你就可以开始写你的 Python 脚本了!

但我们重点关注的是笔记本,所以我们从 New 选项中选择 Python 3。你会看到下面的屏幕:

然后你可以从导入最常见的 Python 库开始:pandas 和 numpy。在代码上面的菜单中,你有一些操作各个单元的选项:添加、编辑、剪切、向上和向下移动单元、运行单元内的代码、停止代码、保存工作以及重启 kernel。

在上图所示的下拉菜单中,还有 4 个选项:

  • Code——不言而喻,就是写代码的地方。
  • Markdown——这是写文本的地方。你可以在运行一段代码后添加你的结论、添加注释等。
  • Raw NBConvert——这是一个可将你的笔记本转换成另一种格式(比如 HTML)的命令行工具。
  • Heading——这是你添加标题的地方,这样你可以将不同的章节分开,让你的笔记本看起来更整齐更清晰。这个现在已经被转换成 Markdown 选项本身了。输入一个「##」之后,后面输入的内容就会被视为一个标题。

使用 Jupyter Notebooks 的神奇功能

Jupyter Notebooks 的开发者已经在其中内置了一些预定义的神奇功能,能让你的生活更轻松,让你的工作更具交互性。你可以运行下面的命令来查看功能列表(注:% 符号通常不需要,因为自动补齐功能通常是开启的):

%lsmagic

你会看到列出了很多选择,你甚至可能能认出其中一些!%clear、%autosave、%debug 和 %mkdir 等功能你以前肯定见过。现在,神奇的命令可以以两种方式运行:

  • 逐行方式
  • 逐单元方式

顾名思义,逐行方式是执行单行的命令,而逐单元方式则是执行不止一行的命令,而是执行整个单元中的整个代码块。

在逐行方式中,所有给定的命令必须以 % 字符开头;而在逐单元方式中,所有的命令必须以 %% 开头。我们看看下列示例以便更好地理解:

逐行方式:

%time a = range(10)

逐单元方式:

%%timeit a = range (10)
min(a)

我建议你运行这些代码,亲自看看它们的不同之处!

不只限于 Python——在 Jupyter Notebooks 中使用 R、Julia 和 JavaScript

神奇之处可不止这点。你甚至能在你的笔记本中使用其它语言,比如 R、Julia、JavaScript 等。我个人很喜欢 R 中的 ggplot2 软件包,所以使用它来进行探索性的数据分析具有很大很大的优势。

要在 Jupyter 中启用 R,你需要 IRKernel。这是针对 R 的专用 kernel,你可以在 GitHub 上获取。这需要 8 个步骤,已经有详细的解释了,另外还有截图指导,参阅:https://discuss.analyticsvidhya.com/t/how-to-run-r-on-jupyter-ipython-notebooks/5512

如果你是一位 Julia 用户,你也能在 Jupyter Notebooks 中使用 Julia!你可以查看这篇为 Julia 用户学习数据科学而编写的全面介绍文章,其中有一个章节就是关于如何在 Jupyter 环境中使用 Julia:https://www.analyticsvidhya.com/blog/2017/10/comprehensive-tutorial-learn-data-science-julia-from-scratch/

如果你更偏爱 JavaScript,那么我推荐使用 IJavascript kernel。这个 GitHub 库包含了在不同操作系统上安装这个 kernel 的各个步骤:https://github.com/n-riesco/ijavascript。注意,在使用它之前,你必需要先安装好 Node.js 和 npm。

Jupyter Notebooks 中的交互式仪表盘——何乐不为?

在你考虑添加小部件之前,你需要导入 widgets 软件包:

from ipywidgets import widgets

小部件的基本类型有典型的文本输入小部件、基于输入的小部件和按钮小部件。下面的例子来自 Dominodatalab,给出了交互式小部件的一些外观:

关于小部件的完整指南,请参阅:https://blog.dominodatalab.com/interactive-dashboards-in-jupyter/

键盘快捷键——节省时间且更有生产力!

快捷方式是 Jupyter Notebooks 最大的优势之一。当你想运行任意代码块时,只需要按 Ctrl+Enter 就行了。Jupyter Notebooks 提供了很多键盘快捷键,可以帮助我们节省很多时间。

下面是我们手动选择的一些对你的上手会有莫大帮助的快捷方式。

Jupyter Notebooks 提供了两种不同的键盘输入模式——命令和编辑。命令模式是将键盘和笔记本层面的命令绑定起来,并且由带有蓝色左边距的灰色单元边框表示。编辑模式让你可以在活动单元中输入文本(或代码),用绿色单元边框表示。

你可以分别使用 Esc 和 Enter 在命令模式和编辑模式之间跳跃。现在就试试看吧!

进入命令模式之后(此时你没有活跃单元),你可以尝试以下快捷键:

  • A 会在活跃单元之上插入一个新的单元,B 会在活跃单元之下插入一个新单元。
  • 连续按两次 D,可以删除一个单元。
  • 撤销被删除的单元,按 Z。
  • Y 会将当前活跃的单元变成一个代码单元。
  • 按住 Shift +上或下箭头可选择多个单元。在多选模式时,按住 Shift + M 可合并你的选择。
  • 按 F 会弹出「查找和替换」菜单。

处于编辑模式时(在命令模式时按 Enter 会进入编辑模式),你会发现下列快捷键很有用:

  • Ctrl + Home 到达单元起始位置。
  • Ctrl + S 保存进度。
  • 如之前提到的,Ctrl + Enter 会运行你的整个单元块。
  • Alt + Enter 不止会运行你的单元块,还会在下面添加一个新单元。
  • Ctrl + Shift + F 打开命令面板。

要查看键盘快捷键完整列表,可在命令模式按「H」或进入「Help > Keyboard Shortcuts」。你一定要经常看这些快捷键,因为常会添加新的。

有用的 Jupyter Notebooks 扩展

扩展/附加组件是一种非常有生产力的方式,能帮你提升在 Jupyter Notebooks 上的生产力。我认为安装和使用扩展的最好工具之一是 Nbextensions。在你的机器上安装它只需简单两步(也有其它安装方法,但我认为这个最方便):

第一步:从 pip 安装它:

pip install jupyter_contrib_nbextensions

第二步:安装相关的 JavaScript 和 CSS 文件:

jupyter contrib nbextension install –user

完成这个工作之后,你会在你的 Jupyter Notebook 主页顶部看见一个 Nbextensions 选项卡。点击一下,你就能看到很多可在你的项目中使用的扩展。

要启用某个扩展,只需勾选它即可。下面我给出了 4 个我觉得最有用的扩展:

  • Code prettify:它能重新调整代码块内容的格式并进行美化。
  • Printview:这个扩展会添加一个工具栏按钮,可为当前笔记本调用 jupyter nbconvert,并可以选择是否在新的浏览器标签页显示转换后的文件。
  • Scratchpad:这会添加一个暂存单元,让你可以无需修改笔记本就能运行你的代码。当你想实验你的代码但不想改动你的实时笔记本时,这会是一个非常方便的扩展。
  • Table of Contents (2):这个很棒的扩展可以收集你的笔记本中的所有标题,并将它们显示在一个浮动窗口中。

这只是少量几个扩展。我强烈建议你查看完整扩展列表并实验它们的功能。

保存和共享你的笔记本

这是 Jupyter Notebooks 最重要且最出色的功能之一。当我必须写一篇博客文章时,我的代码和评论都会在一个 Jupyter 文件中,我需要首先将它们转换成另一个格式。记住这些笔记本是 json 格式的,这在进行共享时不会很有帮助。我总不能在电子邮件和博客上贴上不同单元块,对不对?

进入「Files」菜单,你会看到「Download As」选项:

你可以用 7 种可选格式保存你的笔记本。其中最常用的是 .ipynb 文件和 .html 文件。使用 .ipynb 文件可让其他人将你的代码复制到他们的机器上,使用 .html 文件能以网页格式打开(当你需要保存嵌入在笔记本中的图片时会很方便)。

你也可以使用 nbconvert 选项手动将你的笔记本转换成 HTML 或 PDF 等格式。

你也可以使用 jupyterhub,地址:https://github.com/jupyterhub/jupyterhub。其能让你将笔记本托管在它的服务器上并进行多用户共享。很多顶级研究项目都在使用这种方式进行协作。

JupyterLab——Jupyter Notebooks 的进化

JupyterLab 是今年二月份推出的,被认为是 Jupyter Notebooks 的进一步发展。其支持更加灵活和更加强大的项目操作方式,但具有和 Jupyter Notebooks 一样的组件。JupyterLab 环境与 Jupyter Notebooks 环境完全一样,但具有生产力更高的体验。

JupyterLab 让你能在一个窗口中排布你的笔记本、终端、文本文件和输出结果工作区!你只需拖放你需要的单元即可。你也可以编辑 Markdown、CSV 和 JSON 等常用文件格式并实时预览修改所造成的影响。

如果你想在你的机器上试用 JupyterLab,可查看安装说明:http://jupyterlab.readthedocs.io/en/stable/getting_started/installation.html。JupyterLab 的开发者的长期目标是最终替代 Jupyter Notebooks。但目前来看这还需要一些时间。

最佳实践

尽管独自工作可能很有趣,但大多数时候你都是团队的一员。在这种情况下,遵循指导原则和最佳实践是很重要的,能确保你的代码和 Jupyter Notebooks 都有适当的注释,以便与你的团队成员保持一致。这里我列出了一些最佳实践指标,你在 Jupyter Notebooks 上工作时一定要遵守:

  • 对任何程序员而言都是最重要的事情之一——总是确保你为你的代码添加了适当的注释!
  • 确保你的代码有所需的文档。
  • 考虑一个命名方案并贯彻始终。这能让其他人更容易遵循。
  • 不管你的代码需要什么库,都在你的笔记本起始处导入它们。(并在旁边添加注释说明你载入它们的目的)
  • 确保你的代码有适当的行距。你不要将你的循环和函数放在同一行——否则如果后面要引用它们,会让人抓狂的!
  • 有时候你的文件中有非常大量的代码。看看能不能将你认为不重要的某些代码隐藏起来,之后再引用。这能让你的笔记本看起来整洁清晰,这是非常可贵的。
  • 查看这个在 matplotlib 上的笔记本,看看可以如何简练地进行呈现:http://nbviewer.jupyter.org/github/jrjohansson/scientific-python-lectures/blob/master/Lecture-4-Matplotlib.ipynb

另一个额外技巧!在你想创建一个演示文稿时,你可能首先想到的工具是 PowerPoint 和 Google Slides。其实你的 Jupyter Notebooks 也能创建幻灯片!还记得我说过 Jupyter Notebooks 很灵活吗?我可没有夸大其辞。

要将你的笔记本转换成幻灯片,进入「View→Cell Toolbar」,然后点击「Slideshow」。现在,每个代码块右边都显示了一个「Slide Type」下拉选项。你能看到下列的 5 个选项:

Jupyter Notebooks进阶

在启动Jupyter Notebook,并且创建了新的文件之后,我们便可以开始在里面进行编码和文档写作。下面讲讲如何使用Jupyter Notebook。

首先我们需要明白一点:在Jupyter Notebook中有两种模式

  • 命令模式:键盘输入运行程序命令,此时单元格是蓝色
  • 编辑模式:允许你向单元格中输入代码或者文本内容,此时单元格是绿色

使用

主页面

主页面分为4个部分:

  • notebook名称,可重命名
  • 菜单栏
  • 工具栏
  • 代码单元格

帮助文档

如果你是个新手,可以在notebook中直接查看帮助文档:

!jupyter notebook -h    # 感叹号!不能少

重命名

默认的名字是“未命名”,给文件重命名:图解Pandas的apply函数

文件

【文件】菜单中主要是对各种文件的输入和输出操作,特别是将ipynb文件下载成各种形式进行输出,十分方便:

运行代码

当我们在代码的单元格中输入了代码,如何运行?

1、单击【运行】按钮

2、快捷键

将光标放在我们需要运行的代码框中,然后 Shift+Enter 即可运行~

3、运行全部代码

如果我们想直接从头运行整个notebook中的全部代码,如下操作:【服务】到【重启&运行所有】

内省

在一个对象的前后使用问号?可以显示出关于该对象的概要信息,这个对象可以是自建的,也可以是Python中自带的:

print函数和自定义函数的自省概要信息:

使用双问号?还有个用途是显示函数的源代码:

%run

%run命令可以用来运行任意的python文件:

  • Python文件的后缀为“.py”
  • “%run”后跟的是Python文件的绝对路径
%run Python文件的绝对路径

假设我们在当前目录下有个python文件:

  • 定义了3个变量
  • 给定了一个返回值result

我们在notebook中运行py文件:查看3个变量和返回值的情况

扩展功能

安装

Jupyter notebook本身功能其实不是很多,所以通常会给它安装一个扩展,安装之后就可以起飞了。

安装和启动命令如下:

# 安装Jupyter的配置器
pip install jupyter_nbextensions_configurator

# 启动配置器
jupyter nbextensions_configurator enable --user

显示代码行数

当一个单元格中的代码很多行,我们想显示行数:

编码Markdown文档

当Peter需要在notebook中做笔记的时候,都是使用的Markdown。在Jupyter notebook中是可以直接Markdown语法来编写内容的,两种方式可以实现。

方法1:下拉选项中选择【标记】

方法2:快捷键的使用:【左侧蓝+M键】,Peter常用

生成目录

在使用Markdown的时候是有不同层级的目录,可以显示在notebook中。首先在扩展中需要开启这个功能:

点击启用目录生成:目录默认是在左边,也是按照层级来实现的

我们把光标放在目录上,呈现带+的符号,可以拖到右边:

除了在左侧生成目录,还可以在整个ipynb文件的最上面生成目录:

不管是左侧还是最上面的目录,当我们点击其中某个标题的时候,都会自动跳转到相应的位置,查看、定位文档或者代码超级方便。

标题高亮

安装了扩展之后,我们还可以对标题进行高亮显示:

  • 有3种颜色可供选择
  • 设置了高亮之后标题中便会高亮

LaTex公式

既然Jupyter notebook能够使用Markdown,那么我们可以是Jupyter Notebook中编辑LaTex公式,举一个例子:在Markdown格式下的cell中输入如下内容:

$$ P(A \mid B) = \frac{P(B \mid A) \, P(A)}{P(B)} $$

便会生成相应的LaTeX公式:

输出多个结果

一般在notebook中只会输出一个结果,通过设置能够同时输出多个。

默认情况下只会输出最近一个变量的值,例子中是b。

通过设置InteractiveShell.astnodeinteractivity参数为all,就可以让所有的变量或者声明都能显示出来,添加两行代码便可实现输出多个变量的效果:

from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = 'all'

拼写检查

这是检查拼写错误的一个扩展,能够快速的发现我们拼写中的错误。需要主要的是这个插件是在Markdown格式下作用的:

本意表达的是:This is wrong question

隐藏输入

当我们只想查看输出out部分的内容,可以将输入的代码进行隐藏:

快捷键

1、命令模式快捷键(按 Esc 键开启):

  • 命令模式将键盘命令与Jupyter Notebook笔记本命令相结合,可以通过键盘不同键的组合运行笔记本的命令。
  • esc键进入命令模式。
  • 命令模式下,单元格边框为灰色,且左侧边框线为蓝色粗线条
快捷键 作用
Enter 转入编辑模式
Shift-Enter 运行本单元,选中下个单元
Ctrl-Enter 运行本单元
Alt-Enter 运行本单元,在其下插入新单元
Y 单元转入代码状态
M 单元转入 markdown 状态
R 单元转入 raw 状态
1、2、6 设定 1 、2、6级标题
Up 选中上方单元
K 选中上方单元
Down 选中下方单元
J 选中下方单元
Shift-K 连续选择上方单元
Shift-J 连续选择下方单元
A 在上方插入新单元
B 在下方插入新单元
X 剪切选中的单元
C 复制选中的单元
Shift-V 粘贴到上方单元
V 粘贴到下方单元
Z 恢复删除的最后一个单元
D,D 删除选中的单元
Shift-M 合并选中的单元
Ctrl-S 保存当前 NoteBook
S 保存当前 NoteBook
L 开关行号
O 转换输出
Shift-O 转换输出滚动
Esc 关闭页面
Q 关闭页面
H 显示快捷键帮助
I,I 中断 NoteBook 内核
0,0 重启 NoteBook 内核
Shift 忽略
Shift-Space 向上滚动
Space 向下滚动

2、编辑模式快捷键( 按 Enter 键启动)

  • 编辑模式使用户可以在单元格内编辑代码或文档。
  • enterreturn键进入编辑模式。
  • 编辑模式下,单元格边框和左侧边框线均为绿色
快捷键 作用
Tab 代码补全或缩进
Shift-Tab 提示
Ctrl-] 缩进
Ctrl-[ 解除缩进
Ctrl-A 全选
Ctrl-Z 撤销
Ctrl-Shift-Z 重做
Ctrl-Y 重做
Ctrl-Home 跳到单元开头
Ctrl-Up 跳到单元开头
Ctrl-End 跳到单元末尾
Ctrl-Down 跳到单元末尾
Ctrl-Left 跳到左边一个字首
Ctrl-Right 跳到右边一个字首
Ctrl-Backspace 删除前面一个字
Ctrl-Delete 删除后面一个字
Esc 切换到命令模式
Ctrl-M 切换到命令模式
Shift-Enter 运行本单元,选中下一单元
Ctrl-Enter 运行本单元
Alt-Enter 运行本单元,在下面插入一单元
Ctrl-Shift– 分割单元
Ctrl-Shift-Subtract 分割单元
Ctrl-S 保存当前 NoteBook
Shift 忽略
Up 光标上移或转入上一单元
Down 光标下移或转入下一单元
Ctrl-/ 注释整行/撤销注释

魔术命令

IPython的特殊命令被称为魔术命令,这些命令被设计用于简化常见任务。

官网地址:https://ipython.readthedocs.io/en/stable/interactive/magics.html

魔术命令的前缀符号是百分号%,例如%timeit用来检查一段Python代码执行的时间。魔术命令可以直接使用,也可以赋值给变量再使用:

下面👇记录的是IPython的几个常用的魔法命令:

魔术命令 作用
%pwd 查看当前的工作目录
%cd 更改当前的工作目录
%ls 查看目录文件列表
%writefile 写入文件
%run 运行脚本;%run可以从.py文件中执行python代码,也可以指定ipynb文件
%whos 查看当前变量
%reset 清楚当前变量
%timeit 测试单行代码运行时间
%hist 打印命令输入历史
%paste 从剪贴板中执行已经预先格式化的Python代码
%cpaste 打开一个提示符,手动粘贴执行的python代码
%debug 从最后发生报错的底部进入交互式调试器

Jupyter Notebook换主题

在上面的图形中都是使用的默认主题:白色,实际上主题是可以更换的:

在jupyter notebook中安装并使用不同的主题

# 安装
pip install jupyterthemes
pip install --upgrade jupyterthemes

切换不同主题

# 查看所有颜色主题 --list
jt -l

# 选择主题 --theme
jt -t 主题名称

# 恢复默认主题 --recover
jt -r

1、查看主题

$jt -t monokai  # 选择某个主题
$jt -t oceans16

# 然后重新启动jupyter notebook

$jupyter notebook   # 启动命令

如果想改回原来的主题输入 jt -r 即可


文章作者: 杰克成
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 杰克成 !
评论
  目录