在格式化文本文件中查找文本块

2 投票
5 回答
5288 浏览
提问于 2025-04-18 16:33

我经常用Python来解析格式化的文本文件(主要是为了生物研究,但我会尽量把问题说得简单,让你不需要生物方面的背景知识)。我处理的一种文件叫做pdb文件,它里面包含了蛋白质的3D结构,格式是文本的。下面是一个例子:

HEADER    CHROMOSOMAL PROTEIN                     02-JAN-87   1UBQ              
TITLE     STRUCTURE OF UBIQUITIN REFINED AT 1.8 ANGSTROMS RESOLUTION
REMARK   1                                                                      
REMARK   1 REFERENCE 1                                                          
REMARK   1  AUTH   S.VIJAY-KUMAR,C.E.BUGG,K.D.WILKINSON,R.D.VIERSTRA,           
REMARK   1  AUTH 2 P.M.HATFIELD,W.J.COOK                                        
REMARK   1  TITL   COMPARISON OF THE THREE-DIMENSIONAL STRUCTURES OF HUMAN,     
REMARK   1  TITL 2 YEAST, AND OAT UBIQUITIN                                     
REMARK   1  REF    J.BIOL.CHEM.                  V. 262  6396 1987              
REMARK   1  REFN                   ISSN 0021-9258

ATOM      1  N   MET A   1      27.340  24.430   2.614  1.00  9.67           N  
ATOM      2  CA  MET A   1      26.266  25.413   2.842  1.00 10.38           C  
ATOM      3  C   MET A   1      26.913  26.639   3.531  1.00  9.62           C  
ATOM      4  O   MET A   1      27.886  26.463   4.263  1.00  9.62           O  
ATOM      5  CB  MET A   1      25.112  24.880   3.649  1.00 13.77           C  
ATOM      6  CG  MET A   1      25.353  24.860   5.134  1.00 16.29           C  
ATOM      7  SD  MET A   1      23.930  23.959   5.904  1.00 17.17           S  
ATOM      8  CE  MET A   1      24.447  23.984   7.620  1.00 16.11           C  
ATOM      9  N   GLN A   2      26.335  27.770   3.258  1.00  9.27           N  
ATOM     10  CA  GLN A   2      26.850  29.021   3.898  1.00  9.07           C  
ATOM     11  C   GLN A   2      26.100  29.253   5.202  1.00  8.72           C  
ATOM     12  O   GLN A   2      24.865  29.024   5.330  1.00  8.22           O  
ATOM     13  CB  GLN A   2      26.733  30.148   2.905  1.00 14.46           C  
ATOM     14  CG  GLN A   2      26.882  31.546   3.409  1.00 17.01           C  
ATOM     15  CD  GLN A   2      26.786  32.562   2.270  1.00 20.10           C  
ATOM     16  OE1 GLN A   2      27.783  33.160   1.870  1.00 21.89           O  
ATOM     17  NE2 GLN A   2      25.562  32.733   1.806  1.00 19.49           N  
ATOM     18  N   ILE A   3      26.849  29.656   6.217  1.00  5.87           N  
ATOM     19  CA  ILE A   3      26.235  30.058   7.497  1.00  5.07           C  
ATOM     20  C   ILE A   3      26.882  31.428   7.862  1.00  4.01           C  
ATOM     21  O   ILE A   3      27.906  31.711   7.264  1.00  4.61           O  
ATOM     22  CB  ILE A   3      26.344  29.050   8.645  1.00  6.55           C  
ATOM     23  CG1 ILE A   3      27.810  28.748   8.999  1.00  4.72           C  
ATOM     24  CG2 ILE A   3      25.491  27.771   8.287  1.00  5.58           C  
ATOM     25  CD1 ILE A   3      27.967  28.087  10.417  1.00 10.83           C
TER      26      ILE A   3

HETATM  604  O   HOH A  77      45.747  30.081  19.708  1.00 12.43           O  
HETATM  605  O   HOH A  78      19.168  31.868  17.050  1.00 12.65           O  
HETATM  606  O   HOH A  79      32.010  38.387  19.636  1.00 12.83           O  
HETATM  607  O   HOH A  80      42.084  27.361  21.953  1.00 22.27           O   
END

ATOM表示一行的开始,这一行包含了原子的坐标。TER表示坐标的结束。我想要提取包含原子坐标的整个文本块,所以我使用了:

import re

f = open('example.pdb', 'r+')
content = f.read()

coor = re.search('ATOM.*TER', content) #take everthing between ATOM and TER

但是它什么都没有匹配到。肯定有办法通过正则表达式提取整个文本块。我也不明白为什么这个正则表达式的模式不管用。希望能得到一些帮助。

5 个回答

1

我不会使用正则表达式,而是会用工具库里的 dropwhiletakewhile,这样比把整个文件都加载到内存里去做正则匹配要高效得多。比如,我们可以先忽略文件开头的内容,直到遇到 ATOM,然后在遇到 TER 之后就不再读取文件了。

from itertools import dropwhile, takewhile

with open('example.pdb') as fin:
    until_atom = dropwhile(lambda L: not L.startswith('ATOM'), fin)
    atoms = takewhile(lambda L: L.startswith('ATOM'), until_atom)
    for atom in atoms:
        print atom,

这样做的意思是,先忽略那些不以 ATOM 开头的行,等到遇到 ATOM 后,就继续读取以 ATOM 开头的行。如果你想的话,可以把这个条件改成 lambda L: not L.startswith('TER')

如果不想打印出来,你可以使用:

all_atom_text = ''.join(atoms)

这样就能得到一大块文本。

1

有没有一种不使用正则表达式的替代方法呢?其实可以通过一个相对简单的循环和一点状态管理来实现。下面是一个例子:

# Gather all sets of ATOM-TER in all_coors (if there are multiple per file).
all_coors = []

f = open('example.pdb', 'w+')
coor = None
in_atom = False
for line in f:
    if not in_atom and line.startswith('ATOM'):
        # Found first ATOM, start collecting results.
        in_atom = True
        coor = []

    elif in_atom and line.startswith('TER'):
        # Found TER, stop collecting results.
        in_atom = False
        # Save collected results.
        all_coors.append(''.join(coor))
        coor = None

    if in_atom:
        # Collect ATOM result.
        coor.append(line)
1

你需要使用 (?s) 这个修饰符:

import re

f = open('example.pdb', 'w+')
content = f.read()

coor = re.search('(?s)ATOM.*TER', content)
print coor;

这样可以匹配所有内容,包括换行符,使用 .*

注意,如果你只想匹配中间的内容(包括 ATOM,但不包括 TER),只需把它改成一个正向前瞻来匹配 TER

'(?s)ATOM.*(?=TER)'
1
     import re
     pattern=re.compile(r"ATOM(.*?)TER")
     print pattern.findall(string)

这样做就可以了。

5

这个应该可以匹配(不过我还没实际测试过):

coor = re.search('ATOM.*TER', content, re.DOTALL)

如果你查看一下关于 DOTALL文档,你就会明白为什么之前的代码没有起作用。

还有一种更好的写法是:

coor = re.search(r'^ATOM.*^TER', content, re.MULTILINE | re.DOTALL)

在这里,ATOMTER 必须出现在换行符之后,并且使用了原始字符串表示法,这在正则表达式中是常见的做法(虽然在这个例子中其实没有什么区别)。

你也可以完全不使用正则表达式:

start = content.index('\nATOM')
end = content.index('\nTER', start)
coor = content[start:end]

(这样做实际上不会把 TER 包含在结果中,这可能更好)。

撰写回答