python核心编程笔记——正则表达式(二)

正则表达式与Python语言

. Python当前通过使用re模块来支持正则表达式,这里将介绍两个主要的函数——match()和serarch(),以及compile()函数。其他常用函数/方法还有:
在这里插入图片描述

使用compile()函数编译正则表达式

. compile函数的形式如:compile(pattern,flags = 0),它表示使用任何可选的标记来编译正则表达式的模式,然后返回一个正则表达式对象。
  推荐使用预编译的原因是调用一个代码对象往往比调用一个字符串的性能要高,因为对前者而言,编译过程不会重复执行,在正则表达式的执行过程中通常将进行多次比较操作,因此强烈建议使用预编译。
  值得注意的是,尽管推荐使用预编译,但是它并不是必需的,如果需要编译,就使用编译过的方法;如果不需要编译,就使用函数。
  对于一些特殊的正则表达式的编译,可选的标记可能以参数的形式给出,即flags的值可以使用其他值,常用的标记如下:
  在这里插入图片描述

匹配对象以及group()和groups()方法

. 匹配对象是成功调用match()或search()后返回的对象。其有两个主要的方法:group()和groups()。
  group要么返回整个匹配对象,要么根据要求返回特定的子组;groups则返回一个包含唯一或全部子组的元祖。

使用match()方法匹配字符串

. match()函数试图从字符串的起始部分对模式进行匹配,如果匹配成功就返回一个匹配对象,如果匹配失败,就返回None,匹配对象的group方法能够用于显示那个成功的匹配。例如:

>>> import re
>>> m = re.match('foo','foo')
>>> m.group()
'foo'
>>> n = re.match('foo','bar')
>>> n.group()
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    n.group()
AttributeError: 'NoneType' object has no attribute 'group'
>>> 

使用search()在一个字符串中查找模式

. 通常想要搜索的模式出现在字符串的中间部分的概率要比出现在起始部分的概率要大,这就是search派上用处的原因。search的工作方式和match的工作方式完全相同,不同的地方在于search()会用它的字符串参数,在任意位置对给定正则表达式模式搜索第一次出现的情况。如果成功匹配九返回一个匹配对象,匹配失败就返回None。以下是两者的区别:

>>> m = re.match('foo','seafood')
>>> m.group()
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    m.group()
AttributeError: 'NoneType' object has no attribute 'group'
>>> n = re.search('foo','seafood')
>>> n.group()
'foo'
>>> 

匹配多个字符串

. 即择一匹配符号(|)的使用:

>>> bt = 'bat|bet|bit'   #正则表达式模式
>>> m = re.match (bt,'bat')
>>> m.group()
'bat'
>>> m = re.match(bt,'he bit me')
>>> m
>>> m.group()
Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    m.group()
AttributeError: 'NoneType' object has no attribute 'group'
>>> m = re.search(bt,'he bit me')
>>> m
<_sre.SRE_Match object; span=(3, 6), match='bit'>
>>> m.group()
'bit'

. 其他的类似点号(.),创建字符集([])的使用与择一匹配符形式差不多。

分组符号

. 之前讲group和groups可以用来访问子组,那用来匹配和保存子组的符号就是一对圆括号:

>>> a = '(\w\w\w)-(\d\d\d)'
>>> m = re.match(a,'asd-123')
>>> m.groups()
('asd', '123')
>>> m.group(1)
'asd'
>>> m.group(2)
'123'
>>> m.group()
'asd-123'

使用findall()和finditer()查找每一次出现的位置

. findall查询字符串中某个正则表达式模式全部的非重复出现情况。这与search()在执行的时候类似,但不同的地方在于,findall返回的是一个列表,如果没有找到匹配的部分,就返回一个空列表,但如果匹配成功,列表将包含所有成功的匹配部分(从左往右按顺序排列)。

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

. 这还是挺有用的,如果模式复杂一点,比如邮箱啊,电话呀,可以在后面的内容中都查找出来。
  finditer()函数是在Python2.2版本中添加回来的,这是一个与findall()函数类似但是更节省内存的变体。其和findall以及其他变体函数之间的差异在于finditer()在匹配对象中迭代:

>>> s = 'This and that'
>>> re.findall(r'(th\w+) and (th\w+)',s,re.I)
[('This', 'that')]
>>> re.finditer(r'(th\w+) and (th\w+)',s,re.I)
<callable_iterator object at 0x0000000002F48898>
>>> re.finditer(r'(th\w+) and (th\w+)',s,re.I).__next__()
<_sre.SRE_Match object; span=(0, 13), match='This and that'>
>>> re.finditer(r'(th\w+) and (th\w+)',s,re.I).__next__().groups()
('This', 'that')
>>> [g.groups() for g in re.finditer(r'(th\w+)',s,re.I)]
[('This',), ('that',)]

使用sub()和subn()搜索和替换

. 有两个函数用于搜索和替换:sub()和subn()。两者几乎一模一样,都是将某字符串中所有匹配正则表达式的部分进行替换,最大的不同在于subn()还返回一个替换的总数:

>>> print(re.sub('X','Mr.Smith','attn: X\n\nDear X\n'))
attn: Mr.Smith

Dear Mr.Smith
>>> re.subn('[ae]','x','abcdefg')
('xbcdxfg', 2)

. 使用匹配对象的 group()方法除了能够取出匹配分组编号外,还可以使用\N,其中 N 是在替换字符串中使用的分组编号:

>>> re.sub(r'(\d{1,2})/(\d{1,2})/(\d{2})',r'\2/\1/\3','2/20/91')
'20/2/91'

. 第二个参数表示将第二子组和第一子组做一个调换。

在限定模式上使用split()分割字符串

. split()基于正则表达式的模式分隔字符串,为字符串分隔功能添加
一些额外的威力。例如,一个用于 Web 站点的简单解析器,用户需要输入城市和州名,或者城市名加上 ZIP 编码,还是三者同时输入?

>>> import re
>>> DATA = (
	'Mountain View, CA 94040',
	'Sunnyvale, CA',
	'Los Altos, 94023',
	'Cupertino 95014',
	'Palo Alto CA',
)
>>> for datum in DATA:
	print(re.split(', |(?= (?:\d{5}|[A-Z]{2})) ', datum))

	
['Mountain View', 'CA', '94040']
['Sunnyvale', 'CA']
['Los Altos', '94023']
['Cupertino', '95014']
['Palo Alto', 'CA']

. 要注意的是正则表达式模式中最后是有一个空格的,表示如果空格紧跟在五个数字或者两个大写字母(美国联邦州缩写)之后,就用 split 语句分割该空格。这就允许在城市名中放置空格。

扩展符号

re.I/IGNORECASE

. 表示对匹配的模式不区分大小写:

>>> re.findall(r'(?i)yes', 'yes? Yes. YES!!')
['yes', 'Yes', 'YES']

re.S/DOTALL

. 表示表明点号(.)能够用来表示\n 符号:

>>> re.findall(r'(?s)th.+', '''
	The first line
	the second line
	the third line
''')
['the second line\n\tthe third line\n']

re.X/VERBOSE

. 这个标记可将正则表达式模式写成多行,并且忽略掉空格,也允许添加注释,这样就能使得正则表达式更加易读:

>>> re.search(r'''(?x)
	\((\d{3})\)	 	#  区号
	[ ]           	#  空白符
	(\d{3})  		#  前缀
	-              	#  横线
	(\d{4}) 	  	#  终点数字
''', '(800) 555-1212').groups()
('800', '555', '1212')

. 上面有个空白符,不是指的忽略这个,而是说忽略字符到#(注释)之间的空白。

(?: )

. 通过使用该符号,可以对部分正则表达式进行分组,但是并不会保存该分组用于后续的检索或者应用。当不想保存今后永远不会使用的多余匹配时,这个符号就非常有用。

(?P<name>) 和 (?P=name)

. 前者通过使用一个名称标识符而不是使用从 1 开始增加到 N 的增量数字来保存匹配:

>>> re.sub(r'\((?P<a>\d{3})\) (?P<b>\d{3})-(?:\d{4})','(\g<a>) \g<b>-xxxx','(800) 555-1212')
'(800) 555-xxxx'

. 上面的sub函数的作用是替换匹配正则表达式的字符串。
  使用后者,可以在一个相同的正则表达式中重用模式,而不必稍后再次在相同的正则表达式中指定相同的模式,例如:

>>> bool(re.match(r'''(?x)
\((?P<num1>\d{3})\)[ ](?P<num2>\d{3})-(?P<num3>\d{4})
[ ]
(?P=num1)-(?P=num2)-(?P=num3)
[ ]
1(?P=num1)(?P=num2)(?P=num3)''','(800) 555-1212 800-555-1212 18005551212'))
True

(?=…) 和 (?!..)

. 可以在目标字符串中实现一个前视匹配,而不必实际上使用这些字符串。前者是正向前视断言,后者是负向前视断言。例如假设只对姓氏为“Tom”的人感兴趣:

>>> re.findall(r'\w+(?= Tom)','''
	Guido Tom
	Tim Peters
	Alex Martelli
	Mike Tom
	Raymond Hettinger''')
['Guido', 'Mike']

. 另一个示例中假设忽略以“tom”和“jerry”开头的email地址:

>>> re.findall(r'(?m)^\s+(?!tom|jerry)(\w+)','''
	[email protected]
	[email protected]
	[email protected]
	[email protected]
	[email protected]''')
['sales', 'eng', 'admin']
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章