Skip to content

第 0 章:新手上路 —— Python 与 Jupyter 基础

本教程假设你完全没有 Python 或 EEG(脑电图)基础。第 1–8 章讲解 EEG/MNE 相关内容;本章只教你读懂后面所有代码所需的最基本 Python 知识,不会更多。如果你已经写过 Python,可以直接跳到 第 1 章。(想在写代码之前先点点鼠标试试看?也可以看看可选的 MNELAB 配套指南,它讲解的是同一套工作流程。)

整个教程的路线图,帮助你了解全貌:

EEG 到 ERP 的处理流程

什么是 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)51.040.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.0h_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 基础