第 0 章:新手上路 —— Python 与 Jupyter 基础¶
本教程假设你完全没有 Python 或 EEG(脑电图)基础。第 1–8 章讲解 EEG/MNE 相关内容;本章只教你读懂后面所有代码所需的最基本 Python 知识,不会更多。如果你已经写过 Python,可以直接跳到 第 1 章。(想在写代码之前先点点鼠标试试看?也可以看看可选的 MNELAB 配套指南,它讲解的是同一套工作流程。)
整个教程的路线图,帮助你了解全貌:
什么是 Jupyter notebook?¶
这个文件就是一个 notebook(笔记本)—— 由若干单元格(cell)组成的文档。单元格分两种:
- Markdown 单元格(就像这一格):排版过的文字,用来做说明。不会被“运行”,只用来阅读。
- 代码单元格:真正的 Python 指令。你可以运行它们,运行结果会直接显示在单元格下方。
在 VS Code 中,点进一个代码单元格,按 Shift+Enter 即可运行它(运行后还会自动跳到下一个单元格)。在下面这个单元格上试一试吧。
print("如果你能在这个单元格下方看到这行文字,就说明成功了。")
如果你能在这个单元格下方看到这行文字,就说明成功了。
重要提示: 各个单元格之间共享同一份内存,共享的顺序取决于你运行它们的先后(不一定是它们在页面上出现的顺序)。如果单元格 A 创建了某个东西,而下面的单元格 B 要用到它,你就必须先运行 A 再运行 B。如果输出结果看起来不对,或者提示某个名字“不存在”,通常的解决办法是:从头重新运行所有单元格。在 VS Code 的 notebook 工具栏里,“Restart”(重启)会把一切重置为空白状态;随后的“Run All”(全部运行)会按顺序从头到尾重新运行每个单元格 —— 这是确保所有内容保持一致的最可靠方法。
什么是 Python?¶
Python 是一门编程语言 —— 一种用来精确地、逐步地给计算机下达指令的方式。你把指令写成文字,计算机会从上到下逐行读取并严格照做。这基本上就是编程的全部核心思想。下面的内容,只是帮你读懂这些指令所需的“词汇表”。
变量:装着值的带名字的盒子¶
x = 5 的意思是:创建一个标着 x 的盒子,把 5 放进去。从此以后,在代码里写 x,就代表“这个盒子里当前装的东西”。
x = 5
y = 3
print(x + y) # `#` 后面的内容都是注释 —— 写给人看的说明,Python 会忽略它
8
本教程中会用到的几种数据类型¶
- 数字(Numbers):
5、1.0、40.0 - 字符串(Strings,文本):总是用引号包起来,例如
"eeg"、"auditory/left" - 列表(Lists):一个有顺序的序列,用方括号书写:
["Fz", "Cz", "Pz"]。用[索引]取出其中一项,索引从 0 开始计数,所以第一项是mylist[0]。 - 字典(Dictionaries):由若干
键: 值(key: value)对组成的查找表,用花括号书写:{"auditory/left": 1, "auditory/right": 2}。用mydict["键"]取出对应的值。第 6 章中反复出现的event_id就是这种写法。
channel_names = ["Fz", "Cz", "Pz"]
print("第一个通道:", channel_names[0])
event_id = {"auditory/left": 1, "auditory/right": 2}
print("auditory/left 对应的编码:", event_id["auditory/left"])
第一个通道: Fz
auditory/left 对应的编码: 1
调用函数¶
函数是一个预先打包好的动作,写出它的名字加上一对括号就能触发它,例如 print(...)。括号里的内容就是这个动作的输入 —— 称为参数(argument)。
很多函数还接受关键字参数(keyword argument)—— 以 名字=值 的形式给参数命名,这样它们的含义就不会有歧义,顺序也不重要:
print("hello", "world", sep=" ... ") # `sep` 是一个关键字参数,用来控制多个值之间显示什么分隔内容
hello ... world
后面章节里你会反复见到这种写法,例如 raw.filter(l_freq=1.0, h_freq=40.0) —— l_freq=1.0 和 h_freq=40.0 都是关键字参数,明确指出了每个数值分别对应哪个截止频率,不会产生“这个数字到底是什么意思”的歧义。
对象与方法(最重要的一个代码模式)¶
你之后写的几乎每一行 MNE 代码,看起来都会是 某个东西.做某件事(...) 这种形式。这个点号(.)代表的意思很明确:某个东西 是一个对象(object)—— 一个既携带数据、又自带一些“知道如何对自己执行”的动作的组合体。像这样挂在对象上的动作,叫做方法(method),调用方式和函数一样,只是要写在点号后面。
用一个你已经熟悉的东西来展示这个模式 —— 文本(字符串)本身也是一个对象,它也有自己的方法:
greeting = "hello world"
print(greeting.upper()) # .upper() 是字符串对象自带的一个方法,能把自己转换成大写
HELLO WORLD
在后面的章节中,raw 是一个代表你的 EEG 记录的对象,它拥有 raw.filter(...)(“给自己滤波”)、raw.plot()(“画出自己”)、raw.copy()(“给我一份自己的副本”)这样的方法。一旦你这样去理解,像下面这样的代码:
raw_filtered = raw.copy().filter(l_freq=1.0, h_freq=40.0)
raw,先复制一份,再对这份副本做滤波,并把结果命名为 raw_filtered。”
导入库¶
库(library)是别人已经写好并发布出来的一大堆现成代码,这样你就不用从头造轮子了。mne 是本教程从头到尾都在使用的 EEG/MEG 库;matplotlib 负责绘图。用 import 把一个库引入你的 notebook,之后就可以用点号访问它内部的任何东西,例如 mne.io.read_raw_edf(...) 的意思是:“在 mne 库里,进入它的 io 子模块,使用其中的 read_raw_edf 函数。”
import mne
print("mne 库的版本号:", mne.__version__)
mne 库的版本号: 1.12.1
看到报错也别慌¶
你一定会遇到红色的报错信息。这很正常,并不代表你把什么东西永久弄坏了。Python 会显示一个回溯信息(traceback):一条导致问题发生的代码链条,最后落到真正的错误上。要养成的习惯是:先读最后一行 —— 它写明了错误类型,通常也说清楚了到底出了什么问题。上面的那些行只是显示问题发生在代码的哪个位置,一般要等你先弄懂错误本身之后,它们才有参考价值。
报错信息大致长这样(这只是示例输出,不需要你自己运行):
Traceback (most recent call last):
File "<cell>", line 1, in <module>
event_id["typo_condition"]
KeyError: 'typo_condition'
KeyError: 'typo_condition' 的意思是“你向这个字典查询了一个不存在的键,名叫 typo_condition” —— 几乎总是因为打错字,或者对字典里有什么内容判断有误,这两种情况都很容易修正。
速查表¶
阅读第 1–8 章时可以把这张表放在手边 —— 它基本涵盖了你会遇到的所有语法模式。
| 你会看到 | 它的意思是 |
|---|---|
x = 5 |
把一个值存入名为 x 的变量 |
# 一些文字 |
注释 —— 写给人看的说明,Python 会忽略它 |
"一些文字" |
一个字符串(文本)值 |
[a, b, c] |
一个列表 —— 有顺序的序列;mylist[0] 取出第一项 |
{"key": value} |
一个字典 —— 查找表;mydict["key"] 取出对应条目的值 |
some_function(arg) |
调用函数,把 arg 作为输入传进去 |
some_function(name=value) |
用带名字的(“关键字”)参数调用函数 |
object.method(...) |
让 object 执行它自带的 method 动作 |
object.copy() |
获得一份独立的副本,原对象保持不变 |
import library |
加载一个预先写好的代码库,例如 import mne |
library.thing |
访问 library 内部的 thing |
| 红色的 traceback 文字 | 一个错误 —— 先读最后一行 |
现在你已经掌握了足够读懂本教程所有代码单元格的 Python 知识。从这里开始,后续的讲解会完全聚焦在 EEG 和 MNE 本身 —— 如果再遇到不熟悉的语法,多半就是上面速查表里的某种模式。
下一章:第 1 章 —— EEG 基础