1. List:Python 中最常用的序列容器
1.1 创建和读取 list
genes = [”TP53”, ”EGFR”, ”KRAS”]print(genes)
print(genes[0])print(genes[-1])print(genes[1:3])
1.2 List 和 R vector 很像,但绝对不能画等号
对于 R 用户,很自然会先把:
genes = [”TP53”, ”EGFR”, ”KRAS”]
理解成:
genes <- c(”TP53”, ”EGFR”, ”KRAS”)
初期这样类比没有问题。
但 Python list 实际上更接近:
一个有顺序、可以修改、可以存放不同类型对象的通用容器。
例如:
record = [”TP53”, 19419, 0.52, True]print(record)
可以同时存在一个 list 中。
还有一个 R 用户一定要记住的差异
因为这里的 * 2 表示:
把 list 重复两次。
真正类似 R 向量化运算的是以后学习的 NumPy:
import numpy as npx = np.array([1, 2, 3])x*2
才得到:
所以以后看到:
Python list ≠ R numeric vector
一定要有这个意识。
1.3 修改 list:append、extend、insert、remove、pop
它们本质上只有两类操作:
往 list 中加入东西,或者从 list 中删除东西。
最常用的是:
genes = [”TP53”, ”EGFR”]genes.append(”KRAS”)print(genes)
genes = [”TP53”, ”EGFR”]genes.extend([”BRCA1”, ”MTC”])print(genes)
genes = [”TP53”, ”EGFR”]genes.append([”BRCA1”, ”MTC”])print(genes)
genes = [”TP53”, ”EGFR”]genes.extend([”BRCA1”, ”MTC”])print(genes)
append相当于把整个list当成一个元素,extend才是加入两个元素。
这是初学 list 时最值得真正搞懂的区别之一。
insert在index=1插入BRCA1
genes = [”TP53”, ”EGFR”]genes.insert(1,”BRCA1”)print(genes)
remove去除
genes = [”TP53”, ”EGFR”]genes.remove(”EGFR”)print(genes)
pop:删除最后一个元素,还会把被删除的元素返回给你。
以后数据结构和算法里会再次遇到这种模式。
genes = [”TP53”, ”EGFR”]print(genes.pop())print(genes)
1.4 List 是 mutable
因为seq是immutable,下方代码会报错
然而list却可以
bases = [”A”, ”T”, ”C”, ”G”]bases[0] = ”G”print(bases)
所以 append():
不是创建一个与原 list 无关的新 list。
它是在原来的 list 对象上修改内容。
这也解释了为什么很多 list method 的写法是:
而不是:
genes = genes.append(”KRAS”)
事实上,后者会导致一个非常经典的错误:
genes = [”TP53”, ”EGFR”]genes = genes.append(”KRAS”)print(genes)
因为 append() 的主要工作是原地修改 list,并不返回修改后的 list。
这和 Lesson 02:
形成非常好的对照。
String:
创建新的 string。
List:
修改原 list。
你现在应该开始真正理解:
mutable / immutable 不只是一个定义,它会直接改变我们写代码的方式。
2. Reference 与 Copy:为什么 b 改了,a 也会变?
这是本课最值得花时间理解的地方。
看下面:
如果仍然把变量理解成“盒子”,很容易想象成:
a = 一个盒子:[1, 2, 3]b = 复制一个新盒子:[1, 2, 3]
但这不是实际发生的事情。
Lesson 01 的模型现在终于真正派上用场:
a ─────┐ ↓ [1, 2, 3] ↑b ─────┘
也就是说:
并没有复制 list。
它只是让:
两个名字指向同一个 list 对象。
所以:
修改的是那个共同的 list 对象。
现在:
得到:
而:
当然也是:
这不是 Python 偷偷“同步了两个变量”。
而是:
从头到尾实际上就只有一个 list。
如果你希望真正复制一个 list
a = [1, 2, 3]b = a.copy()b.append(4)print(b)print(a)
也可以使用 slicing:
b = a[:]b.append(4)print(b)print(a)
初期使用a.copy()作为最明确的写法即可。
以后我们学 nested list 时,还会回来讨论:
== 和 is 的区别
这里顺便认识一个以后 debug 很有用的概念。
a = [1, 2, 3]b = aprint(a == b)print(a is b)
a = [1, 2, 3]b = [1, 2, 3]print(a == b)print(a is b)
因为它们是两个不同对象。
所以可以先记成:
这也解释了 Lesson 01 为什么判断 None 推荐:
而不是:
3. Tuple:一个不能修改的 sequence
region = (”chr17”, 7661779, 7687550)type(region)
print(region[0])print(region[-1])print(region[1:3])
会报错因为
list → mutabletuple → immutable
于是现在前三课的逻辑已经串起来了:
为什么 Python 需要 tuple?
你可能会问:
已经有 list 了,为什么还需要 tuple?
因为有些数据在逻辑上就是:
一组固定结构、不希望随便改变的值
例如 genomic interval:
region = (”chr17”, 1000, 2000)position = (”chr1”, 123456)
很多 Python package 也会返回 tuple。
更常见的是:
chrom, start, end = region
这叫:
tuple unpacking
region = (”chr17”, 1000, 2000)chrom, start, end = regionprint(chrom)print(start)print(end)
这种写法以后你会频繁看到。
甚至:
start, end = (100, 200)
也很常见。
一个容易漏掉的小语法
空 tuple:
两个元素:
但是只有一个元素时:
其实只是字符串。
真正的单元素 tuple 要写:
关键不是括号,而是逗号,
4. 把 List 用到生物信息学中
现在我们已经可以开始保存“一批”生物信息学对象。
比如多个 reads:
reads = [ ”ATGCGT”, ”ATCCGT”, ”GGGAAA”]
或者多个 k-mer:
kmers = [ ”ATG”, ”TGC”, ”GCG”, ”CGT”]
访问第一个 k-mer:
最后一个
增加一个:
下一课只要加入:
for
我们就可以真正开始:
对每一条 read 做某件事
例如:
for read in reads: ...
这就是后面 FASTA、FASTQ、k-mer counting 乃至 assembly 的基本程序结构。
一个 assembly 的提前联系
Lesson 02 我们已经知道:
那么:
print(f”prefix = {kmer[:-1]}”)print(f”suffix = {kmer[1:]}”)
下一课学会 loop 后,就可以对每一个 k-mer 自动计算:
再过几课学 dictionary 后:
就会自然出现。
因此现在的知识链已经变成:
String ↓slice ↓List of strings ↓for loop ↓Dictionary ↓k-mer graph ↓de Bruijn graph
这正是我们这套课程要走的方向,而不是学一堆互不相关的 Python 语法。
5. 本课练习:只练真正重要的东西
Exercise A|List 基础
给定:
genes = ["TP53", "EGFR", "KRAS", "BRCA1"]
请写代码得到:
第一个 gene 最后一个 gene 中间两个 gene gene 的数量
然后加入:
MYC
genes = [”TP53”, ”EGFR”, ”KRAS”, ”BRCA1”]print(genes[0])print(genes[-1])print(genes[1:-1])print(len(genes))genes.append(”MYC”)print(genes)
Exercise B|append 还是 extend?
先不要运行,判断:
genes = ["TP53", "EGFR"]
genes.append(["KRAS", "MYC"])
结果应该是什么?
然后再判断:
genes = ["TP53", "EGFR"]
genes.extend(["KRAS", "MYC"])
结果又是什么?
你真正需要解释的是:
为什么二者不同?
第一个结果是["TP53", "EGFR",["KRAS", "MYC"]] 第二个结果是["TP53", "EGFR","KRAS", "MYC"] 因为append是把参数作为一个元素添加到列表末尾
genes = [”TP53”, ”EGFR”]genes.append([”KRAS”, ”MYC”])print(genes)
genes = [”TP53”, ”EGFR”]genes.extend([”KRAS”, ”MYC”])print(genes)
Exercise C|这一课最重要的一题
判断下面最终的 a 和 b:
a = ["A", "C", "G"]
b = a
b.append("T")
然后再改成:
a = ["A", "C", "G"]
b = a.copy()
b.append("T")
两种情况下分别会发生什么?
如果你能不用运行代码就解释清楚,说明 mutable / reference 已经基本理解了。
第一种情况:a和b都是["A", "C", "G", "T"] 第二种情况,a不变,b是["A", "C", "G, "T"]
Exercise D|Genomic tuple
创建:
region = ("chr7", 55019017, 55211628)
然后使用 unpacking:
chrom, start, end = region
计算:
length = end - start
这里不要自动加 +1。
先回答:
为什么仅仅看到 start/end,我们还不能确定应该用 end - start 还是 end - start + 1?
这个问题考的是 genomic coordinate convention,而不是 Python。
region = (”chr7”, 55019017, 55211628)chrom, start, end = regionlength = end - startprint(length)
end-start之后是否加1,取决于是否0-based position
Lesson 03 最终只需要留下四个心智模型
1. list 是 ordered + mutable2. tuple 是 ordered + immutable3. b = a 不等于 copy 对 mutable object 来说尤其重要4. Python: name → object
特别是下面这一组对照,我希望到这里你已经能真正理解,而不是死记:
# immutable stringseq = ”atgc”seq = seq.upper()
和:
# mutable listgenes = [”TP53”, ”EGFR”]genes.append(”KRAS”)
为什么一个通常需要重新赋值,而另一个直接修改原对象。