4. 残基与原子对象操作:残基类型、链标识、原子名称与坐标
嗯,进入正题前我想先说说为什么单独拿出这一章。
你在PDB文件里看到的是一行行文本。但在程序里,我们需要把它们变成有结构的对象。说白了,就是要把「人眼能看懂的表格」翻译成「电脑能操作的数据」。这个翻译过程,我称之为:从扁平到分层。
一个PDB结构,本质上是一个多级容器:
模型(Model)→ 链(Chain)→ 残基(Residue)→ 原子(Atom)。
其中残基和原子是最常打交道的两层。
4.1 残基类型:标准与非标准
每个残基都有一个残基名(Residue Name),三个字母的代码。比如ALA是丙氨酸,GLY是甘氨酸。
但这只是表面。我见过不少新手直接拿残基名当作唯一标识,结果在对接非标准残基时翻了车。为什么?因为PDB文件里还有非标准残基,例如配体、修饰氨基酸或水分子。
非标准残基:HOH(水)、LIG(通用配体)、SEP(磷酸化丝氨酸)、ATP、NAD等
如何区分?大多数工具包(如BioPython、ProDy)会提供一个标准残基列表。我个人习惯在读取时做一次过滤:
standard_residues = set([
'ALA','ARG','ASN','ASP','CYS','GLN','GLU','GLY',
'HIS','ILE','LEU','LYS','MET','PHE','PRO','SER',
'THR','TRP','TYR','VAL',
'DA','DC','DG','DT', # DNA
'A','C','G','U' # RNA
])
if residue.name not in standard_residues:
# 可能是配体、水或修饰残基
handle_nonstandard(residue)
4.2 链标识:别被单字母骗了
链标识(Chain ID)通常是单个字母,比如A、B、C。但这里面有几个坑我需要讲清楚。
- 同一个PDB文件可能出现相同的链标识吗?不会。但可能链标识是空格,或者包含数字。
- 在组装多聚体时,我建议始终显式指定链标识。即使结构只有一个链,也写成
chain_id = "A",而不是chain_id = " "。 - 不连续链?有些PDB文件在第1个模型中有链A、链B,在第2个模型中链标识又重新从A开始。这在NMR结构中很常见。
代码层面,提取特定链的残基通常是这样的:
def get_chain_residues(structure, chain_id):
chain = structure[0][chain_id]
residues = [res for res in chain if res.id[0] == ' ']
return residues
注意res.id[0] == ' '这个条件,它过滤掉了水分子和异常插入码的残基。我最初做这个工作时漏掉了这个判断,结果坐标矩阵里混进了几十个水分子……对,就是这种低级错误。
| 场景 | 链标识格式 | 注意事项 |
|---|---|---|
| 单链蛋白 | "A" | 直接使用 |
| 多链复合物 | "A", "B", "C" | 按链拆分后分别处理 |
| 多糖/配体 | 常为"X"或"Z" | 检查原子类型是否为非标准 |
| NMR结构 | 多种 | 模型索引优先于链标识 |
4.3 原子名称与坐标:细节决定成败
原子名称,比如CA、CB、N、O、C。注意大小写是有意义的。在PDB格式里,原子名占据4列,位置靠右对齐。
举个例子:
ATOM N ALA A 1 -3.840 12.341 5.013 1.00 23.57 N
ATOM CA ALA A 1 -2.746 11.560 5.640 1.00 20.23 C
CA是Cα,不是钙。钙的原子名在PDB里通常写成"CA "(CA后跟一个空格)或者直接写"CA"。这个问题让我当初在解析金属蛋白时头大了好几天。
坐标提取,核心就是取出x、y、z三个浮点数。以BioPython为例:
atom_coords = {
'N': [atom.x, atom.y, atom.z]
for atom in residue.get_atoms()
if atom.name in ['N', 'CA', 'C', 'O']
}
如果你用NumPy存储坐标,我推荐直接构建(N_atoms, 3)的矩阵。这样后续计算距离矩阵或做PCA都方便。
import numpy as np
coords = np.array([
[atom.x, atom.y, atom.z]
for atom in structure.get_atoms()
])
4.4 综合示例:构建残基-原子索引
好了,把上面这些串起来。假设我们要做一个函数,输入一个PDB结构,输出一个包含每个残基的链标识、残基名、残基序号、原子坐标的字典。
我会这么写:
def parse_residue_atoms(structure):
data = {}
for model in structure:
for chain in model:
for residue in chain:
if residue.id[0] != ' ':
continue # 跳过水和异质原子
key = f"{chain.id}:{residue.id[1]}:{residue.resname}"
atoms = {}
for atom in residue:
atoms[atom.name] = {
'x': atom.x, 'y': atom.y, 'z': atom.z,
'element': atom.element
}
data[key] = atoms
return data
这个字典的key像"A:42:ALA",一看就知道是链A的第42个丙氨酸。value里是原子名到坐标的映射。代码跑起来很快。但解析逻辑要清楚——我见过有同学把residue.id[0]当作链标识来用,结果当然不对。
4.5 知识结构图
下面这张图概括了本章的核心脉络:
4.6 避坑快查表
这一节用来查缺补漏。如果你在跑代码时发现结果不对,先看看是不是下面的问题:
| 常见问题 | 原因 | 解决方案 |
|---|---|---|
| 残基名明明是标准氨基酸,却一直报错 | 残基名末尾有空格或插入码 | 使用 residue.id[1] 取序号,residue.resname.strip() 取残基名 |
| 链标识取了'A'却提取不到任何残基 | 链标识可能包含尾随空格 | 加 strip() 后比较 |
| 原子名CA的坐标不等于Cα | 残基不是氨基酸(例如配体单元也有CA) | 优先判断 residue.resname 是否在标准残基列表里 |
| 坐标矩阵形状不对 | 水分子或异质原子混入 | 加过滤条件:if residue.id[0] == ' ' |
| NMR模型导致坐标数量翻倍 | 多模型结构未指定模型索引 | 显式选择第一个模型:structure[0] |
这一章的知识,说白了就是围绕三个单词展开:残基名、链标识、原子坐标。把这三件事搞清楚,后面不管是算RMSD、做相互作用分析还是构建残基图模型,底层的逻辑都会顺手很多。