core python application programming - QLGQ/learning-python GitHub Wiki

正则表达式 (Regular Expression)

Introduction

正则表达式(RE)为高级文本模式匹配,以及搜索-替代等功能提供了基础。正则表达式(RE)是一些由字符和特殊符号组成的字符串,它们描述了这些字符和字符的某种重复方式,因此能按某模式匹配一系列有相似特征的字符串。
Python通过标准库的re模块支持正则表达式(RE)。

Searching vs Mathching

当我们完全讨论与字符串中模式有关的正则表达式时,我们会用术语“匹配”(matching),指的是术语“模式匹配”(pattern-matching)。在Python专门术语中,有两种主要方法完成模式匹配:搜索(searching)和匹配(matching)。

  • 搜索:指在字符串任意部分中搜索匹配的模式,搜索通过search()函数或方法来实现。
  • 匹配:指判断一个字符串能否从起始处全部或部分的匹配某个模式,匹配是以调用match()函数或方法实现的。

总之,当我们说模式的时候,我们全部使用术语“匹配”(matching);我们按照Python如何完成模式匹配的方式来区分“搜索”和“匹配”。
正则表达式速查表

group() vs groups()

除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。 如果正则表达式中定义了组,就可以在Match对象上用group()方法提取出子串来。注意到group(0)永远是原始字符串,group(1)、group(2)……表示第1、2、……个子串。

  • group(): group()方法或者返回所有匹配对象或是根据要求返回某个特定子组。
  • groups(): groups()方法则很简单,它返回一个包含唯一或所有子组的元组。如果正则表达式中没有子组的话,groups()将返回一个空元组,而group()仍会返回全部匹配对象。
m = re.match('ab', 'ab')      #无子组
m.group()                                 #完全匹配
'ab'
m.groups()                                #所有匹配的子组
()

m = re.match('(ab)', 'ab')    #一个子组
m.group()                                  #所有匹配'ab'
'ab'
m.group(1)                                #匹配的子组1
'ab'
m.groups()                                #所有匹配子组
('ab')

m = re.match('(a)(b)', 'ab')  #两个子组
m.group()                                    #完全匹配
'ab'
m.group(1)                                  #匹配的子组1
'a'
m.group(2)                                  #匹配的子组2
'b'
m.groups()                                   #所有匹配子组的元组
('a', 'b')

m = re.match('(a(b))', 'ab')  #两个子组
m.group()                                    #所有匹配部分
'ab'
m.group(1)                                  #匹配的子组1
'ab'
m.group(2)                                  #匹配的子组2
'b'
m.groups()                                   #所有匹配的子组的元组
('ab', 'b')                   

^ vs \b

从字符串的开头或结尾匹配以及在单词边界上的匹配。 下面的例子强调了锚点性正则表达式操作符。这些锚点性正则表达式操作符主要被用于搜索而不是匹配,因为match()总是从字符串的开头进行匹配的。

m = re.search('^The', 'The end.')            #匹配
if m is not None: m.group()
...
'The'

m = re.search('^The', 'end. The')             #不在开头
if m is not None: m.group()
...

m = re.search(r'\bthe', 'bite the dog')    #在词边界
if m is not None: m.group()
...
'the'

m = re.search(r'\bthe', 'bitethe dog')     #无边界
if m is not None: m.group()
...

m = re.search(r'\Bthe', 'bitethe dog')     #无边界
if m is not None: m.group()
...
'the'

findall()

findall()用于非重叠地搜索某字符串中一个正则表达式模式出现的情况。findall()和search()相似之处在于二者都执行字符串搜索,但findall()和match()与search()不同之处是,findall()总返回一个列表。如果findall()没有找到匹配的部分,会返回空列表;如果成功找到匹配部分,则返回所有匹配部分的列表(按从左到右出现的顺序排列)。

re.findall('car', 'car')
['car']

re.findall('car', 'scary')
['car']

re.findall('car', 'carry the barcardi to the car')
['car', 'car', 'car']

正则表达式仅有一个子组时,findall()返回子组匹配的字符串组成的列表;如果表达式有多个子组,返回的结果是一个元组的列表,元组中每个元素都是一个子组的匹配内容,像这样的元组(每一个成功的匹配对应一个元组)构成了返回列表中的元素。

sub() vs subn()

有两种函数/方法用于完成搜索和代替的功能:sub()和subn()。二者几乎是一样的,都是将某字符串中所有匹配正则表达式模式的部分进行替换。用来替换的部分通常是一个字符串,但也可能是一个函数,该函数返回一个用来替换的字符串。subn()和sub()一样,但它还返回一个表示替换次数的数字,替换后的字符串和表示替换次数的数字作为一个元组的元素返回。

re.sub('[ae]', 'X', 'abcdef')
'XbcdXf'

re.subn('[ae]', 'X', 'abcdef')
('XbcdXf', 2)

re.split() vs string.split()

re模块和正则表达式对象的方法split()与字符串的split()方法相似,前者是根据正则表达式模式分隔字符串,后者是根据固定的字符串分割,因此与后者相比,显著提升了字符分割的能力。如果你不想在每个模式匹配的地方都分割字符串,你可以通过设定一个值参数(非零)来指定分割的最大次数。
如果分隔符没有使用由特殊符号表示的正则表达式来匹配多个模式,那re.split()和string.split()的执行过程是一样的。

who命令输出所有已登陆系统的用户的信息:

qiang    :0           2016-09-05 09:34 (:0)
qiang    pts/6        2016-09-06 10:54 (:0)
# !/usr/bin/env python

from os import popen
from re import split

f = popen('who', 'r')
for eachline in f.readlines():
    print split('\s\s+|\t', eachline.strip())
f.close()

运行脚本,我们得到如下结果:

['qiang', ':0', '2016-09-05 09:34 (:0)']
['qiang', 'pts/6', '2016-09-06 10:54 (:0)']
  • 要从我们写的脚本里调用另一个程序,可以用os.popen()命令。
  • Python strip() 方法用于移除字符串头尾指定的字符(默认为空格)。

raw strings

原始字符串的产生正是由于有正则表达式的存在,原因是ASCII字符和正则表达式特殊字符间所产生的冲突。比如,特殊符号“\b”在ASCII字符中代表退格键,但同时“\b”也是一个正则表达式的特殊符号,代表“匹配一个单词边界”。为了让RE编译器把两个字符“\b”当成你想要表达的字符串,而不是一个退格键,你需要用另一个反斜线对它进行转义,即可以这样写:“\b”。

m = re.match('\bblow', 'blow')                    #退格键,没有匹配
if m is not None: m.group()
...

m = re.match('\\bblow', 'blow')                   #用\转义后,现在匹配了
if m is mot None: m.group()
...
'blow'

m = re.match(r'\bblow', 'blow')                   #改用原始字符串
if m is not None: m.group()
...
'blow'

Greediness

正则表达式本身默认是贪心匹配的。也就是说,如果正则表达式模式中使用到通配字,那它在按照从左到右的顺序求值时,会尽量“抓取”满足匹配的最长字符串。

import  re
data = 'Thu Feb 15 17:46:04 2007 ::[email protected]::1171590364-6-8'
patt = '.+(\d+-\d+-\d+)'
re.match(patt, data).group(1)
'4-6-8'

**一个解决方法是用“非贪婪”操作符“?”**这个操作符可以用在“*”、“+”或“?”的后面。它的作用是要求正则表达式引擎匹配的字符越少越好。因此,如果我们把“?”放在“+”的后面,我们就得到了想要的结果。

import re
data = 'Thu Feb 15 17:46:04 2007 ::[email protected]::1171590364-6-8'
patt = '.+?(\d+-\d+-\d+)'
re.match(patt, data).group(1)
'1171590364-6-8'

Instance

import re
data = 'Thu Feb 15 17:46:04 2007 ::[email protected]::1171590364-6-8'
patt = '^(\w+{3})'
m = re.match(patt, data)
if m is not None: m.group()
...
'Thu'
m.group(1)
'Thu'
m.groups()
('Thu',)
import re
data = 'Thu Feb 15 17:46:04 2007 ::[email protected]::1171590364-6-8'
patt = '^(\w+){3}'
m = re.match(patt, data)
if m is not None: m.group()
...
'Thu'
m.group(1)
'u'
m.groups()
('u',)

访问子组1的数据时,只看到“u”是因为子组1中的数据被不断地替换成下一个字符。也就是说,m.group(1)开始的结果是“T”,然后是“h”,最后又被替换成“u”。它们是三个独立(而且重复)的组,每个组是由字符或数字所组成的字符,而不是由连续的三个字符或数字组成的字符所形成的单个组。