Python学习笔记(8)-正则表达式学习笔记

1. 什是正则表达式?

答:正则表达式又称规则表达式,英语是Regular Expression,在代码中常简写为regex、regexp或RE,这计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。 许多程序设计语言都支持利用正则表达式进行字符串操作。正则表达式包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为”元字符(metacharacter)”)。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。它的使用类似于通配符(wildchars),例如在Linux的命令行中输入ls *.py就会列出所有以”.py”结尾的文件。

有时候我们要处理一些字符串,例如,我要提取一些位于<pre></pre>之间的广西,或者是从一个文件中清除一些非ATCG的字符,都要用到正则表达式。

2. 正则表达式在生物学方面有什么用处?

答:正则表达式的这种特征在生物学方面使用极为,广泛,利用正则表达式,可以很好地定位蛋白质的结构域,DNA的一些序列模式,例如CpG岛,重复序列,限制性内切酶位点,核酸识别位点。

3. 不同编程语言之间的正则表达式相同不?

答:许多编程语言都支持正则表达式,每种编程语言都有自己的正则表达式语法,不过大体上是相同的。Python的正则表达式与Perl接近,如果会使用Perl,那么Python的正则表达式学起来也非常容易。

4. 正则表达式是按照哪些规则来匹配文本的?

答:在Python中,通常情况下,字母与单词匹配他们本身,例如“Python”就匹配“Python”,而不是”python”,还有一些特殊的字符用于特殊的匹配,以. ^ $ * + ? { [ ] \ | ( )为例说明一下,如下表所示:

  1. .号:匹配除除换行符 \n 之外的任何字符,例如“ATT.T”会匹配“ATTCT”,”ATTFT”,但不会匹配“ATTTCT”。

  2. ^(克拉符号):匹配输入字符串的开始位置,例如“AUG”会匹配“AUGAGC”,但不匹配“AAUGC”。如果是在方括号里使用,例如AUG表示相反的意思,即不匹配以AUG开头的字符串。

  3. $(美元符号):在一行字符串中,匹配输入字符串的结尾位置,例如“UAA$”匹配“AGCUAA”,但不匹配“ACUAAG”。

  4. *(星号):匹配前面的子表达式零次或多次重复。例如“AT*”会匹配“AAT”,“A”,但不匹配“TT”。

  5. +(加号):匹配前面的子表达式一次或多次重复。“AT+”会匹配“ATT”,但不匹配“A”。

  6. ?匹配前面的子表达式零次或一次重复,“AT?”会匹配“A”或“AT”。

  7. (...)(括号):匹配圆括号中的表达式,用于表明匹配的开始与结构,如果只匹配括号,例如匹配“(”或“)”时,可以使用(或),或者是将它们放到中括号里[(][)]。

  8. (?:...):匹配但不捕获该匹配的子表达式。

  9. {n}:n是一个非负整数,匹配重复的某字符串的n次。例如,“(ATTG){3}”匹配ATTGATTGATTG,但是不匹配“ATTGATTG”。(第1个是重复了3次,第2个是重复了2次)。

  10. {m,n}:m和n均为非负整数,其中m<=n,它表示最少匹配n次且最多匹配m次。例如“(AT){3,5}”会匹配“ATATTATATAT”,但是不匹配“ATATTATAT”,如果不加m,则是从匹配0次的开始,如果没有n,则匹配任意重复的某字符串。

  11. []:表示一组字符串。“[A-Z]”会匹配任何大写的字母,而“[a-z0-9]”则会匹配任何小写的字母以及数字。“[AT]”匹配“A”,“T”,或“(星号)”。在中括号里,^表示相反的含义,例如“R”会匹配任何除了“R”之外的字母。

  12. \:将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\n’ 匹配换行符。序列 ‘\\’ 匹配 “\”,而 ‘(‘ 则匹配 “(“。

  13. |:表达“或”的意思,指明两项之间的一个选择,例如“A|T”会匹配“A”,“T”或“AT”

    Python中的一些特殊字符总结如下所示:

    | 元字符 | 功能说明 |
    | ——————————————- | ———————————————————— |
    | () | 将位于()中的内容作为一个整体来对待 |
    | {} | 按{}中的次数进行匹配 |
    | [] | 匹配位于[]中的任意一个字符 |
    | xyz | ^放在[]内表示反向字符集,匹配除x、y、z之外的任何字符 |
    | [a-z] | 字符范围,匹配指定范围内的任何字符 |
    | a-z | 反向范围字符,匹配除小写英文字母之外的任何字符 |
    | . | 匹配除换行符以外的任意单个字符 |
    | | 匹配位于“”之前的字符或子模式的0次或多次出现 |
    | + | 匹配位于“+”之前的字符或子模式的1次或多次出现 |
    | - | 用在[]之内用来表示范围 |
    | | | 匹配位于“|”之前或之后的字符 |
    | ^ | 匹配行首,匹配以^后面的字符开头的字符串 |
    | $ | 匹配行尾,匹配以$之前的字符结束的字符串 | |
    | ? | 匹配位于“?”之前的0个或1个字符,当此字符紧随任何其他限定符(*,+,?,{n},{n,},{n,m}之后时,匹配模式是“非贪心的“。“非贪心的“模式匹配搜索到的、尽可能短的字符串,而默认的”贪心的“模式匹配搜索一的,尽可能长的字符串。例如在字符串“oooo”中,“o+?”只匹配单个o,而“o+”匹配所有o |
    | \ | 表示位于\之后的为转义字符 |
    | \num | 此处的num是一个正整数,例如,“(.)\1”匹配两个连续的相同字符 |
    | \f | 换页符匹配 |
    | \n | 换行符匹配 |
    | \r | 匹配一个回车符 |
    | \b | 匹配单词头或单词尾 |
    | \B | 与\b含义相反 |
    | \d | 匹配任何数字,相当于[0-9] |
    | \D | 代表非数字,等效于[^0-9 |
    | \s | 匹配任何空白字符,包括空格、制表符、换页符,与[\f\n\r\t\v]等效 |
    | \S | 与\s含义相反,代表非空白字符 |
    | \w | 匹配要任何字母、数字以及下画线,相当于[a-zA-Z0-9] |
    | \W | 与\w相反,与[^A-Za-z0-9]等效 |

5. Python中有没有正则表达式专门用的模块?

答:在Python中,它的标准库re提供了正则表达式操作所需要的功能。它的基础用法如下所示,用search来说明一下,re.search()方法的功能:在给定字符串中寻找第一个匹配给定正则表达式的子字符串。

1
2
import re # 导入python的标准re库
mo = re.search("hello","hello world, hello Python!")

其中search方法的具体用法:使用公式为re.search(pattern,string,flags=0),其中flags的值可以是re.I(大写的字母I,不是1),大写表示忽略查找时的大小写,re.L(支持本地字符集的字符),re.M(多行匹配区栖),re.S(使用元字符“.”,匹配单字)。它可以在字符串内查找模式匹配,只要找到第一个匹配,然后就返回一个match object对象,如果字符串没有匹配,就返回None。mathch object对象有以下的方法:

group() 返回被 RE 匹配的字符串

start() 返回匹配开始的位置

end() 返回匹配结束的位置

span() 返回一个元组包含匹配 (开始,结束) 的位置

group() 返回re整体匹配的字符串,可以一次输入多个组号,对应组号匹配的字符串。

上述的代码运行如下所示:

mark

个结果非常类似于index()方法,如下所示:

“hello”,”hello world, hello Python!”.index(“hello”)

(‘hello’, 0)

不过index()与re.search()方法的不同之处在于所选用的是正则表达式,还是普通的字符串,再看一下例子:

1
2
3
import re
mo = re.search("[Hh]ello","Hello world, hello Python!")
mo.group()

运行结果如下所示:

mo.group()
‘Hello’

其中以大写H开头的字符串是第一个被匹配的模式,返回。

6. Python中的re库还有哪些常用的方法?

答:可以查看地一下re库中的方法,用命令``即可,如下所示:

1
2
impor re # 导入re标准库
dir(re) # 查看re库中所有的方法

可以看到有这些方法:

dir(re)
[‘A’, ‘ASCII’, ‘DEBUG’, ‘DOTALL’, ‘I’, ‘IGNORECASE’, ‘L’, ‘LOCALE’, ‘M’, ‘MULTILINE’, ‘RegexFlag’, ‘S’, ‘Scanner’, ‘T’, ‘TEMPLATE’, ‘U’, ‘UNICODE’, ‘VERBOSE’, ‘X’, ‘_MAXCACHE’, ‘all‘, ‘builtins‘, ‘cached‘, ‘doc‘, ‘file‘, ‘loader‘, ‘name‘, ‘package‘, ‘spec‘, ‘version‘, ‘_alphanum_bytes’, ‘_alphanum_str’, ‘_cache’, ‘_compile’, ‘_compile_repl’, ‘_expand’, ‘_locale’, ‘_pattern_type’, ‘_pickle’, ‘_subx’, ‘compile’, ‘copyreg’, ‘enum’, ‘error’, ‘escape’, ‘findall’, ‘finditer’, ‘fullmatch’, ‘functools’, ‘match’, ‘purge’, ‘search’, ‘split’, ‘sre_compile’, ‘sre_parse’, ‘sub’, ‘subn’, ‘template’]

常用的方法还有re.findall(),re.finditer(),re.match(),

re.findall():列出字符串中模式的所有匹配项,与re.search()不同的是,re.findall()是查找所有的匹配结果,而后者只是找到第1个匹配结果,如下所示:

1
2
3
4
5
>>> result = re.findall("[Hh]ello","Hello world, hello Python,!")
>>> print(result)
['Hello', 'hello']
>>> print(type(result))
<class 'list'>

从上述结果可以看出,re.findall()返回的结果是一个list,而不是一个match object。

re.finditer():列出字符串中模式的所有匹配项,返回一个match object,这是re.finditer()与re.findall()的区别,re.finditer()返回来的是一个迭代器,后者返回来的是一个list,如下所示:

1
2
3
4
5
>>> result = re.finditer("[Hh]ello","Hello world, hello Python,!")
>>> print(result)
<callable_iterator object at 0x00000276F7ABEC18>
>>> print(type(result))
<class 'callable_iterator'>

如果要查看结果,可以按下操作,如下所示:

1
2
3
4
5
6
7
8
9
10
>>> result = re.finditer("[Hh]ello","Hello world, hello Python,!")
>>> for x in result:
... print(x.group())
... print(x.span())
...
Hello
(0, 5)
hello
(13, 18)
>>>

re.match():从字符串的开始处匹配,返回match对象或None,它与re.search()类似,不同之处在于,re.match()只在字符串的开头进行匹配,而re.search()会在整个字符串中进行匹配,如下所示:

1
2
3
>>> result = re.match("hello","Hello world, hello Python,!")
>>> print(result)
None

如果找到了匹配结果,如下所示:

1
2
3
>>> result = re.match("Hello","Hello world, hello Python,!")
>>> print(result)
<_sre.SRE_Match object; span=(0, 5), match='Hello'>

可以通过group()和span()来查看匹配的结果:

1
2
3
4
5
6
7
>>> result = re.match("Hello","Hello world, hello Python,!")
>>> print(result)
<_sre.SRE_Match object; span=(0, 5), match='Hello'>
>>> result.group()
'Hello'
>>> result.span()
(0, 5)

7. 在Python中,如何构建一个模式对象?

答:在Python的re模块中,可以使用compile()方法将正则表达式编译生成正则表达式对象,然后再使用正则表达式对象提供的方法进行字符串处理,使用编译后的正则表达式对象不仅可以提高字符串处理速度,还提供了更加强大的字符串处理功能,尤其是对于大文本文件来说,这一步不是强制的,但推荐这么做。先来看一下findall的匹配,然后再来看一下“创建”的模式匹配。

1
2
3
4
5
6
>>> re.findall("[Hh]ello","Hello world, hello Python,!")
['Hello', 'hello']
>>> reg = re.compile("[Hh]ello")
>>> reg.findall("Hello world, hello Python,!")
['Hello', 'hello']
>>>

编译好的模式可以使用re库中所有的方法,如下所示:

1
2
3
4
5
6
7
>>> rgx = re.compile("[Hh]ello")
>>> rgx.search("Hello world, hello Python,!")
<_sre.SRE_Match object; span=(0, 5), match='Hello'>
>>> rgx.match("Hello world, hello Python,!")
<_sre.SRE_Match object; span=(0, 5), match='Hello'>
>>> rgx.findall("Hello world, hello Python, !")
['Hello', 'hello']

Example1:搜索第一个”TAT“重复序列

1
2
3
4
5
6
7
8
9
10
11
import re
seq = "ATATAAGATGCGCGCGCTTATGCGCGCA"
rgx = re.compile("TAT") # 编译了模式(TAT),编译好的模式就可以使用re中的方法,例如finditer
i = 1
for mo in rgx.finditer(seq): # finditer()方法返回了一个“匹配”类型的对象,就是mo
print('Ocurrence{}:{}'.format(i, mo.group())) # 用group()方法操作了mo对象
print('Position: From {} to {}'.format(mo.start(),mo.end())) # 用span()方法操作了mo对象,
# 这里使用的是mo.start()与mo.end(),它们等价于mo.span[0]和mo.span()[1]
# 即价于 print('Position: From {} to {}'.format(mo.span()[0],mo.span()[1]))
i += 1

运行后,结果如下所示(具体代码的解释看注释):

Ocurrence1:TAT

Position: From 1 to 4

Ocurrence2:TAT

Position: From 18 to 21

8. 如果我要匹配超过一种模式,如何操作?

答:如果要匹配超过一种模式,则需要用到组操作(grouping)。用括号()来将标一个组,。组可以被“捕获(capturing)或非捕获(non-capturing),按照这种分类方法,组合就有捕获组和非捕获组。捕获组就是把正则表达式中子表达式匹配的内容,保存到内存中以数字编号或显式命名的组里,方便检索。groups()方法可以用于捕获组,需要注意的是groups()group()是两种不同的方法。group()返回匹配的字符串,groups()返回的是元组,它们的用法可以用几个案例来说明,如下所示:

Example2: 常规匹配

下面的代码匹配的是“单词,逗号,单词”这个模式,里面有2个括号,group()默认会返回整个匹配结果,它等同于group(0),而group(1)会返回第1个亚组,group(2)会返回第2个亚组,关于亚组的划分,如下图所示:
mark

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import re
m = re.search(r"(\w+)\,(\w+)", "Isaac Newton,physicist")
# 匹配一个“单词,逗号,一个单词”
m.group()
m.group(0)
print(type(m.group(0)))
# m.group(0)和group()都是返回整个匹配结果,就是group(0)的内容
# m.group(0)的结果是一个字符串
m.group(1)
# 返回第1个亚组(就是group(1)的内容
m.group(2)
# 返回第2个亚组(就是group(2)的内容
m.group(10)
# group一共就0,1,2三个,如果是gruop(10)则会出错
m.group(1,2)
# 输出多个匹配结果,m.gorup(1,2)表示输出
m.groups()
# 返回一个包含所有亚组字符串的元组,从1 到所含的小组号,具体的案例可以往后看

结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> import re
>>> m = re.search(r"(\w+)\,(\w+)", "Isaac Newton,physicist")
>>> # 匹配一个"单词,逗号,一个单词"
... m.group()
'Newton,physicist'
>>> m.group(0)
'Newton,physicist'
>>> print(type(m.group(0)))
<class 'str'>
>>> # m.group(0)和group()都是返回整个匹配结果,就是group(0)的内容
... # m.group(0)的结果是一个字符串
...
>>> m.group(1)
'Newton'
>>> # 返回第1个亚组(就是group(1)的内容
...
>>> m.group(2)
'physicist'
>>> # 返回第2个亚组(就是group(2)的内容
>>>
>>> m.groups()
('Newton', 'physicist')

Example 3: gruop(1)的返回值

如果有多个匹配结果,gruop(1)则返回最后1个匹配结果,如下所示:

1
2
3
m0 = re.match(r"(..)+", "a1b2c3")
m0.group(0)
m0.group(1)

结果如下所示:

1
2
3
4
5
>>> m0 = re.match(r"(..)+", "a1b2c3")
>>> m0.group(0)
'a1b2c3'
>>> m0.group(1)
'c3'

需要注意的是,m0.group(0)返回的是整个匹配结果,而group(1)返回的是则是最后1个匹配结果,因为m0 = re.match(r”(..)+”, “a1b2c3”)中只有1个括号,它会逐步地进行匹配,第1次匹配的是a1,第2次匹配的是b2,此时匹配的结果是b2,覆盖掉a1,接着再匹配,是c3,然后c3覆盖b2,这是最后的匹配结果,因此m0.group(1)返回的是就是c3,如果有3个括号,就会连续匹配a1b2c3,有3个亚组,即如下代码:

1
2
3
4
5
m01 = re.match(r"(..)(..)(..)+", "a1b2c3")
m01.group()
m01.group(1)
m01.group(2)
m01.group(3)

运行结果如下所示:

1
2
3
4
5
6
7
8
9
10
>>> m01 = re.match(r"(..)(..)(..)+", "a1b2c3")
>>> m01.group()
'a1b2c3'
>>> m01.group(1)
'a1'
>>> m01.group(2)
'b2'
>>> m01.group(3)
'c3'
>>>

Example4: group()与groups()返回的数值类型

group()返回的结果是一个字符串,groups()返回的结果是一个元组,如下所示:

1
2
3
4
import re
m = re.search(r"(\w+)\,(\w+)", "Isaac Newton,physicist")
print(type(m.group()))
print(type(m.groups()))

结果如下所示:

1
2
3
4
5
6
7
8
9
>>> m02.group(2)
'2c'
>>> import re
>>> m = re.search(r"(\w+)\,(\w+)", "Isaac Newton,physicist")
>>> print(type(m.group()))
<class 'str'>
>>> print(type(m.groups()))
<class 'tuple'>
>>>

Example5: group()与groups()的区别

1
2
3
4
5
6
7
m2 = re.match(r"(\d)(\d)(\d)(\d)","1234")
m2.group()
m2.group(1)
m2.group(2)
m2.group(3)
m2.group(4)
m2.groups()

结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> m2 = re.match(r"(\d)(\d)(\d)(\d)","1234")
>>> m2.group()
'1234'
>>> m2.group(1)
'1'
>>> m2.group(2)
'2'
>>> m2.group(3)
'3'
>>> m2.group(4)
'4'
>>> m2.groups()
('1', '2', '3', '4')

Example6: group()和groups()的应用。

现在一条碱基,它的序列是ATATAAGATGCGCGCGCTTATGCGCGCA,现在找到它的GC序列,代码如下:

1
2
3
4
5
6
7
8
9
10
11
import re
seq = "ATATAAGATGCGCGCGCTTATGCGCGCA"
rgx = re.compile("(GC){3,}")
# (GC){3,}表示至少匹配3次,如果有4个连续的GC也匹配,5个也是如此
result = rgx.search(seq)
result.group()
result.groups()
result.group(0)
result.group(1)
result.group(0,1)

结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> import re
>>> seq = "ATATAAGATGCGCGCGCTTATGCGCGCA"
>>> rgx = re.compile("(GC){3,}")
>>> # (GC){3,}表示至少匹配3次,如果有4个连续的GC也匹配,5个也是如此
...
>>> result = rgx.search(seq)
>>> result.group()
'GCGCGCGC'
>>> result.groups()
('GC',)
>>> result.group(0)
'GCGCGCGC'
>>> result.group(1)
'GC'
>>> result.group(0,1)
('GCGCGCGC', 'GC')

从结果中可以看出,group()如果不填写参数,或者是填写了参数0,就会返回完全匹配的子字符串,如果写了参数1,会返回第2个匹配结果,如果填写了0,1,则返回所有的匹配结果。它的原理如下所示:

(GC){3,}表示表示至少匹配3次,ATATAAGATGCGCGCGCTTATGCGCGCA序列中有GCGCGCGC,就匹配上了,groups()返回的是一个包含唯一或者全部子组的元组。

捕获组 正则表达式 结果
group(0) (GC){3,} GCGCGCGC
group(1) (GC) GC

接着再来看一下groups()的使用方法,groups()方法返回的是匹配的所有亚组的结果,在这种情况下,由于search返回的是一个匹配对象(match object),在模式中有一个组(即GC),返回的匹配结果是一个元组(tuple),如下所示:

这种情况,就是常规的使用方法,跟前面第5个问题中描述的情况一样。如果是groups()方法,它返回的是匹配的所有亚组的结果,在这种情况下,由于search返回的是一个匹配对象(match object),在模式中只有一个组(即GC),返回的匹配结果是一个元组(tuple),即(“GC”,),如果想让groups返回整个模式,需要在编译模式中添加上括号,像下面这个案例一样操作:如下所示:

1
2
3
4
5
6
import re
seq = "ATATAAGATGCGCGCGCTTATGCGCGCA"
rgx = re.compile("((GC){3,})")
result = rgx.search(seq)
result.groups()
result.group()

结果如下所示:

1
2
3
4
5
6
>>> import re
>>> seq = "ATATAAGATGCGCGCGCTTATGCGCGCA"
>>> rgx = re.compile("((GC){3,})")
>>> result = rgx.search(seq)
>>> result.groups()
('GCGCGCGC', 'GC')

在这种模式下,group()与groups()都能被检索(从左向右),因为这两种组都是默认能“捕获”的。如果不需要内部的这个子组(“CG”组),可以将其标为非捕获组(non-capturing),方法就是在组的开始添加上?:,如下所示:

1
2
3
4
5
6
import re
seq = "ATATAAGATGCGCGCGCTTATGCGCGCA"
rgx = re.compile("((?:GC){3,})")
result = rgx.search(seq)
result.groups()
result.group()

结果如下:

1
2
3
4
5
6
7
8
>>> import re
>>> seq = "ATATAAGATGCGCGCGCTTATGCGCGCA"
>>> rgx = re.compile("((?:GC){3,})")
>>> result = rgx.search(seq)
>>> result.groups()
('GCGCGCGC',)
>>> result.group()
'GCGCGCGC'

9. re.search()方法匹配的是都是第一个结果,如何匹配多个结果?

答:如果要进行所有的匹配,需要用到re.findall()方法,它的用法如下:

findall(pattern, string, flags=0)

re.findall()方法能够以列表的形式返回能匹配的子字符串。如果模式中存在一个组,那么findall也有不同的作用。如果没有组,那么findall返回的是匹配字符串的列表。如果模式中存在组,它会返回含有组的列表,如果有超过1组,返回一个元组列表,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import re
seq = "ATATAAGATGCGCGCGCTTATGCGCGCA"
rgx = re.compile("TAT")
# 没有组
rgx.findall(seq)
# 返回匹配的字符串的列表
type(rgx.findall(seq))
rgx = re.compile("(GC){3,}")
# 模式中含有1个组,返回1个列表
rgx.findall(seq)
# 每个匹配对应的组
rgx = re.compile("((GC){3,})")
# 含有2个组,为每个匹配返回一个元组,整体就是一个元组列表
rgx.findall(seq)
rgx = re.compile("((?:GC){3,})")
# 利用非捕获组得到唯一的匹配
rgx.findall(seq)

结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> import re
>>> seq = "ATATAAGATGCGCGCGCTTATGCGCGCA"
>>> rgx = re.compile("TAT")
>>> # 没有组,反回字符串列表
... rgx.findall(seq)
['TAT', 'TAT']
>>> # 返回匹配的字符串的列表
... type(rgx.findall(seq))
<class 'list'>
>>> rgx = re.compile("(GC){3,}")
>>> # 模式中含有1个组,返回1个列表
...
>>> rgx.findall(seq)
['GC', 'GC']
>>> # 每个匹配对应的组
...
>>> rgx = re.compile("((GC){3,})")
>>> # 含有2个组,为每个匹配返回一个元组,整体就是一个元组列表
... rgx.findall(seq)
[('GCGCGCGC', 'GC'), ('GCGCGC', 'GC')]
>>> rgx = re.compile("((?:GC){3,})")
>>> # 利用非捕获组得到唯一的匹配
... rgx.findall(seq)
['GCGCGCGC', 'GCGCGC']

案例第六:寻找多个亚模式(sub-patterns)

不同的组可以用于标记,其格式为?P<组名字>,如下所示:

1
2
3
4
5
6
import re
rgx = re.compile("(?P<TBX>TATA..).*(?P<CGislands>(?:GC){3,})")
seq = "ATATAAGATGCGCGCGCTTATGCGCGCA"
result = rgx.search(seq)
print(result.group('CGislands'))
print(result.group('TBX'))

结果如下所示:

1
2
3
C:\Users\20161111>python t.py
GCGCGC
TATAAG

Example7:用户支持的模式匹配行

这行代码主要是为了统计某个单词在文本文件中有多少个,代码如下:

1
2
3
4
5
6
7
8
9
import re, sys
myregex = re.compile(sys.argv[2])
counter = 0
fh = open(sys.argv[1])
for line in fh:
if myregex.search(line):
counter += 1
fh.closer()
print(counter)

代码解释如下:

第一,第2行代码:关于sys.argv[2]的用法,举1个小例子,在C盘的C:\Users\20161111目录下建立一个.py脚本,命名为test.py,输入下面的代码:

1
2
3
import sys
a = sys.argv[0]
print(a)

保存,快捷键Win+R,打开cmd,输入python test.py,结果如下所示:

1
2
C:\Users\20161111>python test.py
test.py

由此可见,sys.argv[0]表示的就是test.py这个文件本身,现在将tetst.py代码中的sys.argv[0]更改为sys.argv[1],再次运行,输入python test.py what,如下所示:

1
2
C:\Users\20161111>python test.py what
what

运行的结果变成了what,再次修改,将sys.argv[1]改为sys.argv[2:],运行,输入python test.py test0 what1 how3 apple4,如下所示:

1
2
3
4
C:\Users\20161111>python test.py test0 what1 how3 apple4
['what1', 'how3', 'apple4']
C:\Users\20161111>

得到的结果是:’what1’, ‘how3’, ‘apple4’。再倒回去想一下,sys.argv[0]表示的是test.py本身,sys.argv[1]表示的是输入的第1个参数,sys.argv[2:]表示的是第2个参数往后,这与切片的表示形式是一样的。

结论就是,sys.argv[ ]其实就是一个列表,里边的项为用户输入的参数,关键就是要明白这参数是从程序外部输入的,而非代码本身的什么地方,要想看到它的效果就应该将程序保存了,从外部来运行程序并给出参数(参考资料:Python中 sys.argv[]的用法简明解释)。

第二,第4行代码,fh = open(sys.argv[1])表示的就是打开某个文件。

第三,现在运行一下这个脚本,将脚本保存到C盘的根目录下,命名为count_line.py,同时在同一个目录新建一个文本文件,输入以下文字:

1
2
3
4
line1
line2
line3
line4

将文件命名为line_test.txt,打开cmd,输入命令python count_line.py count_test,line,一共是2个参数(前面的python不算),参数1是脚本名称,即count_line.py,参数2是文本文件,即count_test,参数3是要统计的文字,即line,运行结果如下所示:

1
2
C:\Users\20161111>python count_line.py line_test.txt line
4

可以发现,这个文本文件中有4个line这个单词。但是这个脚本有个缺陷,就是,如果一行中有两个line这个单词,它还是会统计为1个,如下所示,将文本文件更改为这样的:

1
2
3
4
line1
line2
line3
line4 line5

继续运行上述脚本,结果如下所示:

1
2
C:\Users\20161111>python count_line.py line_test.txt line
4

因此,需要对脚本进行更改,更改后的版本如下所示:

1
2
3
4
5
6
7
8
9
10
import re, sys
myregex = re.compile(sys.argv[2])
counter = 0
fh = open(sys.argv[1])
for line in fh:
counter += len(myregex.findall(line))
fh.close()
print(counter)

运行结果如下所示:

1
2
C:\Users\20161111>python count_line.py line_test.txt line
5

10. 在Python中,正则表达式的规则非常难记,有没有工具可以用来检测正则表达式的正确与否?

答:有个工具是Kodos,这个工具是用Python写成的,输入正则表达式,它能够在相应的输出框显示出正则表达式的结查,官网是:http://kodos.sourceforge.net/,软件是在WIndows下运行的,打开后界面如下所示:

mark

11. 如何通过正则表达式替换一些字符串?

答:如果要实现这个目的,需要用到re.sub()方法,sub就是substitution的简写,它的用法是sub(rpl,str[,count=0])

具体参数的含义如下所示:

  1. rpl:原始的字符串;
  2. str,需要替换原始字符串的字符串。
  3. [,count=0],可选参数,表示替换的最大次数,默认是0,表示替换所有匹配。

re.sub的使用非常类似于replace方法,参见下面的案例,代码如下所示:

这个案例展示的是:删除GC重复(该行有超过3个GC),代码解释参见注释

1
2
3
4
5
import re
regex = re.compile("(?:GC){3,}") # 匹配至少3个GC结边的字符串
seq = "ATGATCGTACTGCGCGCTTCATGTGATGCGCGCGCGCAGACTATAAG"
print("Before:", seq)
print("After:", regex.sub("",seq))

结果如下所示:

1
2
3
C:\Users\20161111>python test.py
Before: ATGATCGTACTGCGCGCTTCATGTGATGCGCGCGCGCAGACTATAAG
After: ATGATCGTACTTTCATGTGATAGACTATAAG

第二个方法:subn(rpl,str[,count=0])也能实现re.sub的功能。

不同之处在于,subn会返回替换后的字符串和替换的次数,将上述代码用subn演示,就如下所示:

1
2
3
4
5
import re
regex = re.compile("(?:GC){3,}") # 匹配至少3个GC结边的字符串
seq = "ATGATCGTACTGCGCGCTTCATGTGATGCGCGCGCGCAGACTATAAG"
print("Before:", seq)
print("After:", regex.subn("",seq))

结果如下所示:

1
2
3
C:\Users\20161111>python test.py
Before: ATGATCGTACTGCGCGCTTCATGTGATGCGCGCGCGCAGACTATAAG
After: ('ATGATCGTACTTTCATGTGATAGACTATAAG', 2)

12. 正则表达式在生物信息学方面有什么用处?

答:正则表达式可以用于搜索PROSITE(这是一个蛋白质拉点和序列模式数据库),这个数据库的信息模式就是序列与描述序列的字符串。例如下面的这段字符串:

K-[KR]-C-G-H-[LMQR],这段字符串表示的是异柠檬酸酶的活性位点。具体的每个字母含义如下:

位于第1个位置的是K,位于第2个位置提是K或R,随后是CGH,最后是一段氨基酸序列,即L,M,Q或R。如果要在这个序列中搜索,必须要把PROSITE的格式转化为符合Python正则表达式的表示方式,就像这样K[KR]CGH[LMQR]

为了将PROSITE转化为REGEX的基本格式,这个过程中包括移除连接符(“-”),将括号之间的数字替换为大括号之间的数字,并将“x”替换为点号,接着看一个腺苷酸环化酶相关蛋白2( adenylyl cyclase associated protein 2)的例子。

此蛋白在PROSITE中的版本如下:

[LIVM](2)-x-R-L-[DE]-x(4)-R-L-E,这种序列是一致性模式(Consensus pattern)。

其中,L是亮氨酸,I是异亮氨酸,V是缬氨酸,M是甲硫氨酸,x代表任意氨基酸,R是精氨酸,D是天冬氨酸,E是谷氨酸。

REGEX(正则表达式)版本如下:

[LIVM]{2}.RL[DE].{4}RLE

Example8:在FASTA格式中搜索蛋白质的模式

现在假设我们要在一个FASTA格式的文件中寻找这个模式。除了要找到这个模式外,我们有可能还需要在上下文中检索它的位置,即位于在这个模式之前与之后的10个氨基酸序列(代码中用小写字母表示),下面是一个Fasta格式的文件:

1
2
>Q5R5X8|CAP2_PONPY CAP 2 - Pongo pygmaeus (Orangutan).
MANMQGLVERLERAVSRLESLSAESHRPPGNCGEVNGVIGGVAPSVEAFDKLMDSMVAEFLKNSRILAGDVETHAEMVHSAFQAQRAFLLMASQYQQPHENDVAALLKPISEKIQEIQTFRERNRGSNMFNHLSAVSESIPALGWIAVSPKPGPYVKEMNDAATFYTNRVLKDYKHSDLRHVDWVKSYLNIWSELQAYIKEHHTTGLTWSKTGPVASTVSAFSVLSSGPGLPPPPPPPPPPGPPPLLENEGKKEESSPSRSALFAQLNQGEAITKGLRHVTDDQKTYKNPSLRAQGGQTRSPTKSHTPSPTSPKSYPSQKHAPVLELEGKKWRVEYQEDRNDLVISETELKQVAYIFKCEKSTLQIKGKVNSIIIDNCKKLGLVFDNVVGIVEVINSQDIQIQVMGRVPTISINKTEGCHIYLSEDALDCEIVSAKSSEMNILIPQDGDYREFPIPEQFKTAWDGSKLITEPAEIMA

下面的代码实现的就是这个功能,即在1个FASTA文件中寻找这个模式,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import re
pattern = "[LIVM]{2}.RL[DE].{4}RLE"
fh = open('D:/prot.fas')
fh.readline()
seq = ""
for line in fh:
seq += line.strip()
rgx = re.compile(pattern)
result = rgx.search(seq)
patternfound = result.group()
span = result.span()
leftpos = span[0]-10
if leftpos < 0:
leftpos = 0
print(seq[leftpos:span[0]].lower() + patternfound + seq[span[1]:span[1]+10].lower())
fh.close()

运行代码如下所示:

1
2
C:\Users\20161111>python test.py
manmqgLVERLERAVSRLEslsaeshrpp

Example9: 清洗序列

正则表达式在清除序列方面更加常用,需要清理的序列通常是一些非标准的序列,就像下面的这样:

1
2
3
1 ATGACCATGA TTACGCCAAG CTCTAATACG ACTCACTATA GGGAAAGCTT GCATGCCTGC
61 AGGTCGACTC TAGAGGATCT ACTAGTCATA TGGATATCGG ATCCCCGGGT ACCGAGCTCG
121 AATTCACTGG CCGTCGTTTT

这个序列里面有空格,有数字,这些都不是我们所需要的,需要把它们的清除掉,清除序列的代码如下所示:

1
2
3
4
5
6
import re
regex = re.compile(' |\d|\n|\t') # 有一个空格
seq = ''
for line in open('D:/pMOSBlue.txt'):
seq += regex.sub('',line)
print(seq)

结果如下所示:

1
2
C:\Users\20161111>python test.py
ATGACCATGATTACGCCAAGCTCTAATACGACTCACTATAGGGAAAGCTTGCATGCCTGCAGGTCGACTCTAGAGGATCTACTAGTCATATGGATATCGGATCCCCGGGTACCGAGCTCGAATTCACTGGCCGTCGTTTT

代码解释:最重要的就是这个模式:regex = re.compile(' |\d|\n|\t'),其中’ |\d|\n|\t’表示的是,数字或换行,或空格。