source:Regular Expression HOWTO

简介

正则表达式(Regular expressions, REs,regexes regex patterns)在python中主要通过re包实现。使用正则表达式,你可以匹配任意你想匹配的字符串, 比如email, 电话号码,英文单词,$\TeX$命令等。

正则表达式的pattern被编译成一系列的bytecode,然后通过C语言写的引擎执行匹配。高级的使用者应当考虑不同的写法的执行速度。

简单的patterns

匹配字符

元字符(metacharacters)

  • . 匹配除换行符以外的任意字符
  • [] 中括号里的任一字符出现一次
  • \d 匹配数字,相当于[0-9]
  • \D 匹配非数字
  • \s 匹配任意的空白符
  • \S 匹配任意的非空白符
  • \w 匹配字母或数字或下划线或汉字
  • \W 匹配非字母或数字或下划线或汉字
  • ^ 匹配字符串的开始
  • $ 匹配字符串的结束u

重复

  • * 重复0次或多次
  • + 重复一次或更多次
  • ? 重复0次或1词
  • {n} 重复n次
  • {n,} 重复n次或更多次
  • {n, m} 重复n次到m次

python中使用正则表达式

python中,re模块为我们提供了使用正则表达式的引擎。

编译正则表达式

1
2
3
import re
p = re.compile('ab*')
p
re.compile(r'ab*', re.UNICODE)

re.compile函数能产生pattern对象,我们可以用pattern匹配字符实现查找或者替代等功能。re.compile还有其它一些语法变量,比如忽略大小写等。

1
2
p = re.compile('ab.*', re.IGNORECASE)
p
re.compile(r'ab.*', re.IGNORECASE|re.UNICODE)

进行匹配

如果RE是某个pattern

  • match() 是否是以RE开头的
  • search() 扫描整个字符串,检查是否存在RE
  • findall() 寻找所有的子字符串, 返回一个list
  • finditer() 类似上, 返回一个iterator
1
2
a = p.match('XYZ')
a # 返回None
1
2
1
2
m = p.match('abcde')
m
<_sre.SRE_Match object; span=(0, 5), match='abcde'>

使用match()匹配,返回的结果是一个match对象,如果想调用结果的话,参考下面的几种方法:

  • group(): 返回RE匹配的字符串
  • start(): 返回匹配结果的开始位置
  • end(): 返回匹配结果的末尾位置
  • span(): 返回字符位置的区间
1
m.group()
'abcde'
1
m.start(), m.end()
(0, 5)
1
m.span()
(0, 5)
1
2
m = p.match('xabcde')
m # match只能匹配以`RE`开始的字符串
1
2
m = p.search("xabcde")
m.group()
'abcde'

在实际编程时, 往往把match对象存成一个变量, 然后检查是否是空, 如下

1
2
3
4
5
6
p = re.compile('[a-z]+', re.IGNORECASE)
m = p.match("Abdedfscf")
if m:
print('Matched found:', m.group())
else:
print('Matched not found')
Matched found: Abdedfscf

如果想要返回所有的匹配字符串的话, 可以使用findall()

1
2
p = re.compile('[\d.]+')
p.findall('1.4 billions people in 34 provinces')
['1.4', '34']

当然也可以返回一个迭代对象, 以便进行迭代操作

1
2
3
4
p = re.compile('[\d.]+')
iterator = p.finditer('1.4 billions people in 34 provinces')
for it in iterator:
print(it.span())
(0, 3)
(23, 25)

Module-Level Functions

如果嫌每次都使用compile()生成RE太麻烦, 你也可以像下面这种方式进行匹配:

1
print(re.findall(r'[\d.]+', '1.4 billions people in 34 provinces'))
['1.4', '34']

这里的r表示regex的意思

Compilation Flags

允许你修改一些参数, 实现大小写忽略, 多行匹配等功能

I IGNORECASE

1
re.findall(r'\ba[a-z]+', "abc, abandon, banana, Ana, AB",re.I)
['abc', 'abandon', 'Ana', 'AB']

A ASCII

1
re.findall(r'\w+', "hello! Hey! 你好",re.A)
['hello', 'Hey']
1
re.findall(r'\w+', "hello! Hey! 你好")
['hello', 'Hey', '你好']

更多元字符

  • | 或 操作符 需要注意的时abc|xyz匹配的时abcxyz, 而不是cx
1
re.findall(r'Bill|Jim', "Tom, Jimmy, Zhang&Bill")
['Jim', 'Bill']

分组(Grouping)

有时候我们进行匹配的时候会需要把匹配的结果区分为不同的组成部分,这我们就用得到分组grouping, 我们使用’()‘作为分组的标记

1
2
3
p = re.compile('(a(b)c)d')
m = p.match('abcd')
m.group(0)
'abcd'
1
m.group(1)
'abc'
1
m.group(2)
'b'
1
2
3
mail = "mayun@alibaba.com.cn mail of Jack Ma"
p = re.compile("(^[a-z][a-z1-9._]+)@([a-z1-9][a-z1-9._]+)", re.I)
m = p.match(mail)
1
m.group(0)
'mayun@alibaba.com.cn'
1
m.group(1)
'mayun'
1
m.group(2)
'alibaba.com.cn'
1
m.groups()
('mayun', 'alibaba.com.cn')

分组命名

分组命名是Python独有的正则的功能, 通过(?P<name>...)可以为分组命名

1
2
3
mail = "mayun@alibaba.com.cn mail of Jack Ma"
p = re.compile("(?P<User>^[a-z][a-z1-9._]+)@(?P<Company>[a-z1-9][a-z1-9._]+)", re.I)
m = p.match(mail)
1
m.group('User')
'mayun'
1
m.group('Company')
'alibaba.com.cn'

字符修改

  • split() 在正则匹配的位置分离
  • sub() 查找替代
  • subn()
1
re.split(r"\W+", 'This is a test, short and sweet, of split().')
['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']
1
re.split(r"\W+", 'This is a test, short and sweet, of split().',3) # 3 是分割次数
['This', 'is', 'a', 'test, short and sweet, of split().']
1
re.split(r"(\W+)", 'This is a test, short and sweet, of split().') # 保留符合条件的分割项
['This',
 ' ',
 'is',
 ' ',
 'a',
 ' ',
 'test',
 ', ',
 'short',
 ' ',
 'and',
 ' ',
 'sweet',
 ', ',
 'of',
 ' ',
 'split',
 '().',
 '']

查找并替代

1
2
p = re.compile('(blue|white|red)')
p.sub('colour', 'blue socks and red shoes')
'colour socks and colour shoes'
1
p.sub('colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'
1
p.subn('colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)

常见问题

使用正则还是字符串方法

1
2
string1 = "swordfish"
string1.replace("word", 'deed')
'sdeedfish'
1
2
p = re.compile('word')
p.sub(string1, 'deed')
'deed'

match()search

前者只匹配从0位置开始的结果, search()会匹配不从0开始的结果

1
print(re.match(r'super', 'superstition').span())
(0, 5)
1
print(re.match('super', 'insuperable'))
None
1
print(re.search('super', 'insuperable').span())
(2, 7)

贪婪和非贪婪

前者是指匹配尽可能多的符合条件的字符, 后者则会匹配尽可能少的或指定的符合条件的字符

1
2
s = '<html><head><title>Title</title>'
len(s)
32
1
print(re.match('<.*>', s).span())
(0, 32)
1
print(re.match('<.*>', s).group())
<html><head><title>Title</title>

如果想要非贪婪可以使用?

1
print(re.match('<.*?>', s).group())
<html>

使用re.VERBOSE

有时候我们要匹配比较复杂的文本时,正则表达式要写很长,如果携程一行的话,可读性就比较差,可以使用re.VERBOSE增加pattern的易读性

1
2
3
4
5
6
7
8
pat = re.compile(r"""
\s* # Skip leading whitespace
(?P<header>[^:]+) # Header name
\s* : # Whitespace, and a colon
(?P<value>.*?) # The header's value -- *? used to
# lose the following trailing whitespace
\s*$ # Trailing whitespace to end-of-line
""")
1
pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")