python正则表达式快速基础教程

正则表达式,这个术语不太容易望文生义(没有去考证是如何被翻译为正则表达式的),其实其英文为Regular Expression,直接翻译就是:有规律的表达式。这个表达式其实就是一个字符序列,反映某种字符规律,用(字符串模式匹配)来处理字符串。很多高级语言均支持利用正则表达式对字符串进行处理的操作。

python提供的正则表达式文档可参见:https://docs.python.org/3/library/re.html


In [1]:
import re
  • 首先引入python正则表达式库re

1. 初识


In [4]:
s = 'Blow low, follow in of which low. lower, lmoww oow aow bow cow 23742937 dow kdiieur998.'
p = 'low'
  • 假设要在字符串s中查找单词low,由于该单词的规律就是low,因此可将low作为一个正则表达式,命名为p

In [3]:
m = re.findall(p, s)
m


Out[3]:
['low', 'low', 'low', 'low', 'low']
  • findall(pattern, string)是re模块中的函数,会在字符串string中将所有匹配正则表达式pattern模式的字符串提取出来,并以一个list的形式返回。该方法是从左到右进行扫描,所返回的list中的每个匹配按照从左到右匹配的顺序进行存放。
  • 正则表达式low能够将所有单词low匹配出来,但是也会将lowerBlow等含有low字符串中的low也匹配出来。

In [ ]:
p = input('请输入字符模式,回车结束!\n')
m = re.findall(p,s)
if not m:
    print('没有找到匹配字符!')
else:
    print('成功匹配!')

如果不存在可以匹配的字符模式,则返回空列表。可以利用列表是否为空作为分支条件。


In [4]:
p = r'\blow\b'
m = re.findall(p, s)
m


Out[4]:
['low', 'low']
  • \b,即boundary,是正则表达式中的一种特殊字符,表示单词的边界。正则表达式r'\blow\b'就是要单独匹配low,该字符串两侧为单词的边界(边界为空格等,但是并不对边界进行匹配)

In [5]:
p = r'[lmo]ow'
m = re.findall(p, s)
m


Out[5]:
['low', 'low', 'low', 'low', 'low', 'mow', 'oow']
  • [lmo],匹配lmo字母中的任何一个

In [6]:
p = r'[a-d]ow'
m = re.findall(p, s)
m


Out[6]:
['aow', 'bow', 'cow', 'dow']
  • [a-d],匹配abcd字母中的任何一个

In [7]:
p = r'\d'
m = re.findall(p, s)
m


Out[7]:
['2', '3', '7', '4', '2', '9', '3', '7', '9', '9', '8']
  • \d,即digit,表示数字,\d表示数字(一个数字字符)

In [8]:
p = r'\d+'
m = re.findall(p, s)
m


Out[8]:
['23742937', '998']
  • +,表示一个或者重复多个对象,对象为+前面指定的模式
  • 因此\d+可以匹配长度至少为1的任意正整数字符。

2. 基本匹配与实例

字符模式 匹配模式内容 等价于
[a-d] One character of: a, b, c, d [abcd]
[^a-d] One character except: a, b, c, d [^abcd]
abc丨def abc or def
\d One digit [0-9]
\D One non-digit [^0-9]
\s One whitespace [ \t\n\r\f\v]
\S One non-whitespace [^ \t\n\r\f\v]
\w One word character [a-zA-Z0-9_]
\W One non-word character [^a-zA-Z0-9_]
. Any character (except newline) [^\n]
固定点标记 匹配模式内容
^ Start of the string
$ End of the string
\b Boundary between word and non-word characters
数量词 匹配模式内容
{5} Match expression exactly 5 times
{2,5} Match expression 2 to 5 times
{2,} Match expression 2 or more times
{,5} Match expression 0 to 5 times
* Match expression 0 or more times
{,} Match expression 0 or more times
? Match expression 0 or 1 times
{0,1} Match expression 0 or 1 times
+ Match expression 1 or more times
{1,} Match expression 1 or more times
字符转义 转义匹配内容
\. . character
\|\ character
\* * character
\+ + character
\? ? character
\{ { character
\) ) character
\[ [ character

In [9]:
m = re.findall(r'\d{3,4}-?\d{8}', '010-66677788,02166697788, 0451-22882828')
m


Out[9]:
['010-66677788', '02166697788', '0451-22882828']
  • 匹配电话号码,区号可以是3或者4位,号码为8位,中间可以有-或者没有。

In [10]:
m = re.findall(r'[\u4e00-\u9fa5]', '测试 汉 字,abc,测试xia,可以')
m


Out[10]:
['测', '试', '汉', '字', '测', '试', '可', '以']
  • 匹配汉字
  • 几个组合实例
正则表达式 匹配内容
[A-Za-z0-9] 匹配英文和数字
[\u4E00-\u9FA5A-Za-z0-9_] 中文英文和数字及下划线
^[a-zA-Z][a-zA-Z0-9_]{4,15}$` 合法账号,长度在5-16个字符之间,只能用字母数字下划线,且第一个位置必须为字母

3. 进阶

3.1 python正则表达式常用函数

函数 功能 用法
re.search Return a match object if pattern found in string re.search(r'[pat]tern', 'string')
re.finditer Return an iterable of match objects (one for each match) re.finditer(r'[pat]tern', 'string')
re.findall Return a list of all matched strings (different when capture groups) re.findall(r'[pat]tern', 'string')
re.split Split string by regex delimeter & return string list re.split(r'[ -]', 'st-ri ng')
re.compile Compile a regular expression pattern for later use re.compile(r'[pat]tern')
re.sub Replaces all occurrences of the RE pattern in string with repl, substituting all occurrences unless max provided. This method returns modified string re.sub(r'[pat]tern', repl, 'string')

In [43]:
m = re.search(r'\d{3,4}-?\d{8}', '010-66677788,02166697788, 0451-22882828')
m


Out[43]:
<_sre.SRE_Match object; span=(0, 12), match='010-66677788'>

In [12]:
m.group()


Out[12]:
'010-66677788'
  • search总是返回第一个成功匹配,如果没有匹配,则返回None
  • 利用group()函数,取出match对象中的内容

In [15]:
m.span()


Out[15]:
(0, 12)

span()函数返回匹配字符串的起始和结束位置


In [76]:
ms = re.finditer(r'\d{3,4}-?\d{8}', '010-66677788,02166697788, 0451-22882828')
for m in ms:
    print(m.group())


010-66677788
02166697788
0451-22882828
  • finditer()是返回所有匹配,放置在一个元组中,每个匹配都是类似search()函数所返回的match对象,内含每个匹配的详细信息
  • 可以对该元组进行迭代,取得每个match对象,进一步可以取得其详细信息
  • 与findall()的区别是,findall()只取得所有匹配字符串,返回包含所有匹配字符串的列表,不关心匹配字符串在原字符串中的各项信息。

In [14]:
words = re.split(r'[,-]', '010-66677788,02166697788,0451-22882828')
words


Out[14]:
['010', '66677788', '02166697788', '0451', '22882828']

正则下的split(),是一般split()函数的增强版本,可以对字符串以正则表达式匹配的字符进行切割,返回切割后的列表。


In [9]:
p = re.compile(r'[,-]')
p.split('010-66677788,02166697788,0451-22882828')


Out[9]:
['010', '66677788', '02166697788', '0451', '22882828']
  • 利用compile()函数将正则表达式编译,如以后多次运行,可加快程序运行速度

3.2 分组与引用

Group Type Expression
Capturing ( ... )
Non-capturing (?: ... )
Capturing group named Y (?P<Y> ... )
Match the Y'th captured group \Y
Match the named group Y (?P=Y)
  • (...) 将括号中的部分,放在一起,视为一组,即group。以该group来匹配符合条件的字符串。
  • group,可被同一正则表达式的后续,所引用,引用可以利用其位置,或者利用其名称,可称为反向引用。

In [65]:
p = re.compile('(ab)+')
p.search('ababababab').group()


Out[65]:
'ababababab'

In [17]:
p.search('ababababab').groups()


Out[17]:
('ab',)
  • 有分组的情况,用groups()函数取出匹配的所有分组

In [18]:
p=re.compile('(\d)-(\d)-(\d)')
p.search('1-2-3').group()


Out[18]:
'1-2-3'

In [19]:
p.search('1-2-3').groups()


Out[19]:
('1', '2', '3')

In [20]:
s = '喜欢/v 你/x 的/u 眼睛/n 和/u 深情/n 。/w'
p = re.compile(r'(\S+)/n')
m = p.findall(s)
m


Out[20]:
['眼睛', '深情']
  • 按出现顺序捕获名词(/n)。

In [21]:
p=re.compile('(?P<first>\d)-(\d)-(\d)')
p.search('1-2-3').group()


Out[21]:
'1-2-3'
  • 在分组内,可通过?P<name>的形式,给该分组命名,其中name是给该分组的命名

In [22]:
p.search('1-2-3').group('first')


Out[22]:
'1'
  • 可利用group('name'),直接通过组名来获取匹配的该分组

In [23]:
s = 'age:13,name:Tom;age:18,name:John'
p = re.compile(r'age:(\d+),name:(\w+)')
m = p.findall(s)
m


Out[23]:
[('13', 'Tom'), ('18', 'John')]

In [24]:
p = re.compile(r'age:(?:\d+),name:(\w+)')
m = p.findall(s)
m


Out[24]:
['Tom', 'John']
  • (?:\d+),匹配该模式,但不捕获该分组。因此没有捕获该分组的数字

In [25]:
s = 'abcdebbcde'
p = re.compile(r'([ab])\1')
m = p.search(s)
print('The match is {},the capture group is {}'.format(m.group(), m.groups()))


The match is bb,the capture group is ('b',)
  • 此即为反向引用
  • 当分组([ab])内的ab匹配成功后,将开始匹配\1\1将匹配前面分组成功的字符。因此该正则表达式将匹配aabb
  • 类似地,r'([a-z])\1{3}',该正则将匹配连续的4个英文小写字母。

In [26]:
s = '12,56,89,123,56,98, 12'
p = re.compile(r'\b(\d+)\b.*\b\1\b')
m = p.search(s)
m.group(1)


Out[26]:
'12'
  • 利用反向引用来判断是否含有重复数字,可提取第一个重复的数字。
  • 其中\1是引用前一个分组的匹配。

In [27]:
s = '12,56,89,123,56,98, 12'
p = re.compile(r'\b(?P<name>\d+)\b.*\b(?P=name)\b')
m = p.search(s)
m.group(1)


Out[27]:
'12'
  • 与前一个类似,但是利用了带分组名称的反向引用。

3.3 贪婪与懒惰

数量词 匹配模式内容
{2,5}? Match 2 to 5 times (less preferred)
{2,}? Match 2 or more times (less preferred)
{,5}? Match 0 to 5 times (less preferred)
*? Match 0 or more times (less preferred)
{,}? Match 0 or more times (less preferred)
?? Match 0 or 1 times (less preferred)
{0,1}? Match 0 or 1 times (less preferred)
+? Match 1 or more times (less preferred)
{1,}? Match 1 or more times (less preferred)
  • 当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。(贪婪匹配)
  • 而懒惰匹配,是匹配尽可能少的字符。方法是在重复的后面加一个?

In [2]:
p = re.compile('<.*>')
p.search('<python>perl>').group()


Out[2]:
'<python>perl>'
  • 贪婪匹配(默认)将匹配尽可能多的重复

In [5]:
p = re.compile('<.*?>')
p.search('<python>perl>').group()


Out[5]:
'<python>'
  • 懒惰匹配(非贪婪匹配),将匹配尽可能少的重复

In [28]:
p = re.compile('(ab)+')
p.search('ababababab').group()


Out[28]:
'ababababab'

In [29]:
p = re.compile('(ab)+?')
p.search('ababababab').group()


Out[29]:
'ab'

4. 文本处理中的一些应用实例

4.1 提取文本中的符合某种模式的字串并进行处理后替换


In [64]:
with open(r'test_re.txt', encoding = 'utf-8') as f:
    text = f.read()

读入文本文件


In [54]:
p = '[\S]+/t[^/]+/t'
lines = re.findall(p, text)
lines[:10]


Out[54]:
['一九九八年/t 新年/t',
 '一九九七年/t 十二月/t',
 '12月/t 31日/t',
 '1998年/t 新年/t',
 '1999年/t 12月/t',
 '12月/t 31日/t',
 '12月/t 31日/t',
 '十二月/t 三十一日/t',
 '今天/t 上午/t',
 '上午/t 九时/t']
  • 提取词性标记模式为连续2个时间标记t的子串
  • 模式可构造为:1或多个非空白字符 /t 一或者多个不含/的字符 /t
  • 利用findall()函数,返回所有符合模式的子串,并查看前10个子串

In [42]:
import re
p = '((?:[\S]+/t\s){2,})'
matchs = re.findall(p, text)
matchs[:10]


Out[42]:
['一九九八年/t 新年/t ',
 '一九九七年/t 十二月/t 三十一日/t ',
 '12月/t 31日/t ',
 '1998年/t 新年/t ',
 '1999年/t 12月/t ',
 '12月/t 31日/t ',
 '12月/t 31日/t ',
 '十二月/t 三十一日/t ',
 '今天/t 上午/t ',
 '上午/t 九时/t 二十分/t ']
  • 捕获连续两个以上的时间标记词汇
  • findall()函数在应用分组时,将捕获所有分组,不是捕获符合模式的字符串,因此这里将整体作为一个分组
  • ?:表示不捕获该分组内容

In [69]:
def express(line):
    return line.group().replace('/t ', '')+'/t '

txt = re.sub(p, express, text)
txt.split('\n')[:5]


Out[69]:
['19980101-01-001-001/m 迈向/v 充满/v 希望/n 的/u 新/a 世纪/n ——/w 一九九八年新年/t 讲话/n (/w 附/v 图片/n 1/m 张/q )/w ',
 '19980101-01-001-002/m 中共中央/nt 总书记/n 、/w 国家/n 主席/n 江/nr 泽民/nr ',
 '19980101-01-001-003/m (/w 一九九七年十二月三十一日/t )/w ',
 '19980101-01-001-004/m 12月31日/t ,/w 中共中央/nt 总书记/n 、/w 国家/n 主席/n 江/nr 泽民/nr 发表/v 1998年新年/t 讲话/n 《/w 迈向/v 充满/v 希望/n 的/u 新/a 世纪/n 》/w 。/w (/w 新华社/nt 记者/n 兰/nr 红光/nr 摄/Vg )/w ',
 '19980101-01-001-005/m 同胞/n 们/k 、/w 朋友/n 们/k 、/w 女士/n 们/k 、/w 先生/n 们/k :/w ']
  • express()为一个自定义函数,参数为match对象,可以将match对象中所有的'/t'替换为空,并在结尾加上'/t '
  • re.sub()函数,第二个参数可以是函数,当其为函数时,会将pattern每一次匹配到的结果作为match对象,传参给该函数,接受该函数的返回值来进行字符串替换
  • 本例中将所有连续的时间标记词汇合成为一个

4.2 网页文件(HTML)处理示例

5. 常用正则表达式(待补充)

进一步学习可参考官方文档以及《精通正则表达式(第3版)》