正则表达式
正则表达式是一个特殊的字符序列,计算机科学的一个概念。通常被用来检索、替换那些符合某个模式(规则)的文本。
许多程序设计语言都支持利用正则表达式进行字符串操作。在Python中需要通过正则表达式对字符串进行匹配的时候,可以使用re模块。re 模块使 Python 语言拥有全部的正则表达式功能。
Python中的正则表达式
与大多数编程语言相同,正则表达式里也使用\作为转义字符,这就可能造成反斜
杠困扰。假如你需要匹配文本中的字符\,那么使用编程语言表示的正则表达式里
将需要4个反斜杠\:前两个和后两个分别用于在编程语言里转义成反斜杠,转换
成两个反斜杠后再在正则表达式里转义成一个反斜杠
import re
# 需要使用四个反斜杠来匹配一个 \
print(re.search('\\\\','\dyk')) #<re.Match object; span=(0, 1), match='\\'>
Python里的原生字符串很好地解决了这个问题,有了原生字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。在Python 字符串前面添加r即可将字符串转换成为原生字符串
import re
# 使用两个反斜杠即可匹配一个 \
print(re.search(r'\\','\dyk')) #<re.Match object; span=(0, 1), match='\\'>
查找方法的使用
在Python中的查找匹配方法,常见的有下面五种,他们的用法大致相同,但是匹配出的结果却不同。
match方法(只匹配字符串开头)
search方法(扫描整个字符串,找到第一个匹配)
findall方法(扫描整个字符串,找到所有的匹配)
finditer方法(扫描整个字符串,找到所有的匹配,并返回一个可迭代对象)
fullmatch方法: (完整匹配,字符串需要完全满足正则规则才会有结果,否则就是None)
match方法
re.match尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。
函数语法:
re.match(pattern,string,flags=0)
参数 | 描述 |
---|---|
pattern | 匹配的正则表达式 |
string | 要匹配的字符串 |
flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。 |
我们可以使用group(num)函数来获取匹配表达式。
group表示分组默认没有分组的话group(0)和group是一样的
import re
res1=re.match(r'd','dyk')
print(res1.group(0)) #d 匹配到的元素
print(res1.span()) #(0, 1) 匹配到的元素所在位置 左闭右开
res2=re.match(r'y','dyk')
print(res2) #None 必须是字符串的起始位置
search方法
re.search 扫描整个字符串并返回第一个成功的匹配。
函数语法:
re.search(pattern, string, flags=0)
import re
res1=re.search(r'dy','dyk666')
print(res1.group()) #dy
print(res1.group(0)) #dy
print(res1.span()) #(0, 2)
res2=re.search(r'6','dyk666')
print(res2.group(0)) #6
print(res2.span()) #(3, 4) 第一次出现6的位置
re.match与re.search的区别
共同点:
1. 只对字符串查询一次
2. 返回值类型都是 re.Match类型的对象
不同点:
re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;
而re.search匹配整个字符串,直到找到一个匹配。
result1 = re.search(r'天气','今天天气不错哟')
result2 = re.match(r'天气','今天天气不错哟')
print(result1) # <re.Match object; span=(2, 4), match='天气'>
print(result2) # None
findall 方法
在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。
注意: match 和 search 是匹配一次 findall 匹配所有。
语法格式:
re.findall(pattern,string,flags=0)
import re
#\d表示数字 +表示个数一个或一个以上
res1=re.findall(r'\d+','dyk666dyk123')
print(res1) #['666', '123']
res2=re.search(r'\d+','dyk666dyk123')
print(res2) #<re.Match object; span=(3, 6), match='666'> 只匹配第一个
注意事项:
findall方法匹配时,如果匹配规则里有分组,则只匹配分组数据
ret = re.findall(r'\w+@(qq|126|163)\.com','123@qq.com;aa@163.com;bb@126.com')
print(ret) # ['qq', '163', '126'] 只匹配到了分组里的内容
如果正则表达式里存在多个分组,则会把多个分组匹配成元组。
ret = re.findall(r'\w+@(qq|126|163)(\.com)','123@qq.com;aa@163.com;bb@126.com')
print(ret) #[('qq', '.com'), ('163', '.com'), ('126', '.com')]
如果想要让findall匹配所有的内容,而不仅仅只是匹配正则表达式里的分组,可以使用 ?:来将分组标记为非捕获分组。
ret = re.findall(r'\w+@(?:qq|126|163)\.com','123@qq.com;aa@163.com;bb@126.com')
print(ret) # ['123@qq.com', 'aa@163.com', 'bb@126.com']
finditer方法
和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回
import re
#\d表示数字 +表示一个或一个以上
res=re.finditer(r'\d+','dyk666dyk123') # 得到的结果是一个可迭代对象
for i in res: # 遍历 res 取出里面的每一项匹配
print(i)
# <re.Match object; span=(3, 6), match='666'>
# <re.Match object; span=(9, 12), match='123'>
fullmatch方法:
(完整匹配,字符串需要完全满足正则规则才会有结果,否则就是None)
import re
#\d表示数字 +表示一个或一个以上
res=re.fullmatch(r'dyk','dyk123')
print(res)# None
res1=re.fullmatch(r'dyk','dyk')
print(res1) #<re.Match object; span=(0, 3), match='dyk'>
正则表达式分组
group方法表示正则表达式的分组
1. 在正则表达式里使用 () 表示一个分组
2. 如果没有分组,默认只有一组
3. 分组的下标从 0 开始
import re
res=re.search(r'(dy)(k\d)','dyk666dyk123')
print(res) #<re.Match object; span=(0, 4), match='dyk6'>
# 分成3个组 即两个括号和整个3个分组
print(res.group()) #dyk6 默认就是拿第0组
print(res.group(0)) #dyk6 第 0 组就是把整个正则表达式当做一个整体
print(res.group(1)) #dy
print(res.group(2)) #k6
# groupdict 作用是获取到分组组成的字典
print(m1.groupdict()) # {}
# (?P<name>表达式) 可以给分组起一个名字
res1=re.search(r'(?P<first>dy)','dyk666dyk123')
print(res1.groupdict('first')) #{'first': 'dy'}
re.Match类
当我们调用re.match方法、re.search方法,或者对re.finditer方法的结果进行迭代时,拿到的数据类型都是re.Match对象
这个类里定义了相关的属性
属性和方法 | 说 明 |
---|---|
pos | 搜索的开始位置 |
endpos | 搜索的结束位置 |
string | 搜索的字符串 |
re | 当前使用的正则表达式的对象 |
lastindex | 最后匹配的组索引 |
lastgroup | 最后匹配的组名 |
group(index=0) | 某个分组的匹配结果。如果index等于0,便是匹配整个正则表达式 |
groups() | 所有分组的匹配结果,每个分组的结果组成一个列表返回 |
groupdict() | 返回组名作为key,每个分组的匹配结果座位value的字典 |
start([group]) | 获取组的开始位置 |
end([group]) | 获取组的结束位置 |
span([group]) | 获取组的开始和结束位置 |
expand(template) | 使用组的匹配结果来替换模板template中的内容,并把替换后的字符串返回 |
ret = re.search(r'(abc)+', 'xxxabcabcabcdef')
print(ret.pos) # 搜索开始的位置,默认是0
print(ret.endpos) # 搜索结束的位置,默认是字符串的长度
print(ret.group(0)) # abcabcabc 匹配整个表达式
print(ret.group(1)) # abc 第一次匹配到的结果
print(ret.span()) # (3, 12) 开始和结束位置
print(ret.groups()) # 表示当正则表达式里有多个分组时,多个分组的匹配结果
print(res.groups()) #('dy', 'k6')
re.compile方法
我们在使用正则表达式时,可以直接调用re 模块的 match,search,findall等方法,传入指定的正则表达式。同时,也可以调用re.compile方法,生成一个正则表达式对象,再调用这个正则表达式对象的相关方法实现匹配。
import re
re.match(r'h','hello') # 可以使用re.match方法直接匹配
# 也可以调用re模块的compile方法,生成一个 Pattern 对象,再调用 Pattern 对象的 match方法
res = re.compile(r'h')
res.match('hello')
re.search(r'l','hello')
res = re.compile(r'l')
res.match('hello')
res = re.compile(r'l')
res.findall('hello')
res = re.complie(r'l')
res.finditer('hello')
正则表达式修饰符
修饰符 | 描述 |
---|---|
re.I | 使匹配对大小写不敏感 |
re.M | 多行匹配,影响 ^ 和 $ |
re.S | 使 . 匹配包括换行在内的所有字符 |
import re
res=re.search(r'd','Dyk',re.I)
print(res) #不区分大小写 <re.Match object; span=(0, 1), match='D'>
# \w+$ 表示匹配以一个或者多个字母,数字,或下划线结尾
# re.M 可以进行多行匹配,每个换行都认为是一个结尾
res1=re.findall(r'\w+$','i am dyk\ndyk666\n dk')
print(res1) # 不实用re.M修饰符,只会匹配到最后的 ['dk']
res2=re.findall(r'\w+$','i am dyk\ndyk\n dyk666',re.M)
print(res2) #['dyk', 'dyk', 'dyk666']
print(re.search(r'.','\n')) # None . 匹配除了 \n 以外的所有字符
print(re.search(r'.','\n',re.S)) # '\n' 匹配到了 \n
正则表达式模式
模式字符串使用特殊的语法来表示一个正则表达式
字母和数字表示他们自身,一个正则表达式模式中的字母和数字匹配同样的字符串。
re.search(r'H','Hello') # 这里的 H 表示的就是字母 H 自身,没有特殊含义
多数字母和数字前加一个反斜杠时会拥有不同的含义
res = re.search(r'\d','dyk66') # 这里的 \d 表示的是匹配数字
res=re.search(r'\w+','dyk') #\w表示匹配字母,数字,下划线
标点符号只有被转义时才匹配自身,否则它们表示特殊的含义
#. 表示除了换行以外的任意字符
ret = re.search(r'.','hello') # 这里的 . 表示的是匹配任意字符
ret = re.search(r'\.','he.llo') # 这里的 \. 进行了转义,才表示标点符号自身。
反斜杠本身需要使用反斜杠转义。由于正则表达式通常都包含反斜杠,所以你最好使用原始字符串来表示它们。模式元素(如 r’\t’,等价于 \ \t )匹配相应的特殊字符
非打印字符
非打印字符也可以是正则表达式的组成部分。下表列出了表示非打印字符的转义序列
字符 | 描述 |
---|---|
\cx | 匹配由x指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符 |
\f | 匹配一个换页符。等价于 \x0c 和 \cL |
\n | 匹配一个换行符。等价于 \x0a 和 \cJ |
\r | 匹配一个回车符。等价于 \x0d 和 \cM |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。注意 Unicode 正则表达式会匹配全角空格符。 |
\S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v] |
\t | 匹配一个制表符。等价于 \x09 和 \cI |
\v | 匹配一个垂直制表符。等价于 \x0b 和 \cK |
import re
# \s 表示任意的空白字符
print(re.search(r'\s', 'hello world')) # 空格
print(re.search(r'\n', 'hello\nworld')) # 换行
print(re.search(r'\t', 'hello\tworld')) # 制表符
# \S 表示非空白字符
print(re.search(r'\S', '\t\n x')) #<re.Match object; span=(5, 6), match='x'>
特殊字符
所谓特殊字符,就是一些有特殊含义的字符。若要匹配这些特殊字符,必须首先使字符"转义",即,将反斜杠字符\ 放在它们前面。下表列出了正则表达式中的特殊字符
特殊字符 | 描述 |
---|---|
( ) | 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 ( 和 )。 |
. | 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 . |
[] | 里的值表示的是区间,而且是单个字符。要匹配 [,请使用 [ |
\ | 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\n’ 匹配换行符, \ 匹配 \,而 ( 则匹配 ( |
{ } | 用来限定前面元素出现的次数。要匹配 {,请使用 { |
| | |就是可选值,可以出现多个值。要匹配 |,请使用\ |。 |
\d | 匹配一个数字字符。等价于 [0-9] |
[0-9] | 匹配任何数字。等价于 \d |
\D | 匹配一个非数字字符。等价于 [^0-9] |
[a-z] | 匹配任何一个小写字母 |
[A-Z] | 匹配任何大写字母 |
[a-zA-Z0-9 | 匹配任何一个字母及数字。等价于\w |
\w | 匹配字母,数字,下划线。等价于[A-Za-z0-9_] |
\W | 匹配任何非单词字符。等价于 [^A-Za-z0-9_] |
很多大写的就是小写的功能取个非
定位符
定位符用来描述字符串或单词的边界,^ 和 $ 分别指字符串的开始与结束,\b 描述单词的前或后边界,\B 表示非单词边界。
特殊字符 | 描述 |
---|---|
^ | 匹配输入字符串的开始位置,例如:h匹配以h开头;在方括号表达式中时,它表示不接受该字符集合,例如[0-9]匹配除了数字以外的数据。要匹配 ^ 字符本身,请使用 ^ |
$ | 匹配输入字符串的结尾位置。要匹配 $ 字符本身,请使用 $ |
\b | 匹配一个单词边界,即字与空格间的位置 |
\B | 非单词边界匹配 |
限定符
限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 共6种。
字符 | 描述 |
---|---|
* | 匹配前面的子表达式零次或多次 |
+ | 配前面的子表达式一次或多次 |
? | 匹配前面的子表达式零次或一次 |
{n} | n 是一个非负整数。匹配确定的 n 次 |
{n,} | n 是一个非负整数。至少匹配n 次 |
{,n} | n 是一个非负整数。最多匹配n 次 |
{n,m} | m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次 |
# 标点符号的使用:
# ():用来表示一个分组
m = re.search(r'h(\d+)x', 'sh829xkflsa')
print(m.group(1)) #829
# 如果要表示括号,需要使用 \
m1 = re.search(r'\(.*\)', '(1+1)*3+5')
print(m1.group()) #(1+1)
# . 表示匹配除了换行以外的任意字符。如果想要匹配 . 需要使用 \.
# | 用来表示或者 和 [] 有一定的相似,但是有区别
# [] 里的值表示的是区间,而且是单个字符
# | 就是可选值,可以出现多个值
print(re.search(r'f(x|prz|t)m', 'pdsfprzm')) #<re.Match object; span=(3, 8), match='fprzm'>
# {} 用来限定前面元素出现的次数
# {n}:表示前面的元素出现 n 次
print(re.search('dyk6{3}','dyk666')) #<re.Match object; span=(0, 6), match='dyk666'>
# {n,}:表示前面的元素出现 n 次以上
print(re.search('dyk6{3}','dyk666')) #<re.Match object; span=(0, 6), match='dyk666'>
print(re.search('dyk6{1,}','dyk666')) #大于等于1次
#<re.Match object; span=(0, 6), match='dyk666'>
# {,n}:表示前面的元素出现 n 次以下
print(re.search('dyk6{,1}','dyk666')) #小于于等于1次
#<re.Match object; span=(0, 4), match='dyk6'>
# {m,n}:表示前面的元素出现m到n次
print(re.search('dyk6{1,2}','dyk666')) #1到2次
#<re.Match object; span=(0, 5), match='dyk66'>
# *:表示前面的元素出现任意次数(0次及以上) 等价于 {0,}
print(re.search('dyk6*','dyk666')) #大于等于0次
#<re.Match object; span=(0, 6), match='dyk666'>
# +:表示前面的元素至少出现一次,等价于 {1,}
print(re.search('dyk6+','dyk666')) #大于等于1次
#<re.Match object; span=(0, 6), match='dyk666'>
# ?:两种用法:
# 1.规定前面的元素最多只能出现一次,等价于 {,1}
# 2.将贪婪模式转换成为非贪婪模式
print(re.search(r'go?d', 'god'))
# ^:以指定的内容开头 $:指定内容结尾
print(re.search(r'^a.*i$', 'aofi'))
正则表达式练习
用户名匹配:由数字、大小写字母、下划线_和中横线-组成,
长度为4到14位,并且不能以数字开头。
res=re.search(r'^\D[\d\w\-]{3,13}$','dyk-123')
r'^\D[a-z0-9A-Z_\-]{3,13}'
匹配邮箱
r'^([A-Za-z0-9_\-\.])+@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$
匹配手机号
r'^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\d{8}$'
匹配身份证号
r'^[1-9]\d{5}(18|19|20|)\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$'
匹配URL地址
r'((ht|f)tps?):\/\/([\w\-]+(\.[\w\-]+)*\/)*[\w\-]+(\.[\w\-]+)*\/?(\?([\w\-\.,@?^=%&:\/~\+#]*)+)?'
匹配QQ号
r'^[1-9][0-9]{4,10}$'
匹配微信号
r'^[a-zA-Z]([-_a-zA-Z0-9]{5,19})+$'
匹配车牌号
r'^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$'
正则替换
Python中的re模块提供了re.sub用户替换字符串中的匹配项
语法:
re.sub(pattern,repl,string,count=0)
参数:
pattern : 正则中的模式字符串。
repl : 替换的字符串,也可为一个函数。
string : 要被查找替换的原始字符串。
count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配
import re
res=re.sub(r'\d','x','dyk123')
print(res) #dykxxx
res=re.sub(r'\d+','x','dyk123')
print(res) #dykx
res=re.sub(r'\D','','2018-1117-0000')
print(res) #201811170000 删除下划线
除了传入字符串还可以传入一个函数
import re
def mul(n):
x=int(n.group())
x*=2
return str(x) #必须返回字符串
# sub内部在调用 mul方法时,会把每一个匹配到的数据以re.Match的格式传参
print(re.sub(r'\d+',mul,'dyk123')) #dyk246
# mul函数是自动调用的
贪婪和非贪婪模式
Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;
非贪婪则相反,总是尝试匹配尽可能少的字符。
在*,?,+,{m,n}后面加上 ?使贪婪变成非贪婪
贪婪模式 正则表达式模式中使用到通配字,那它在从左到右的顺序求值时,会尽量“抓取”满足匹配最长字符串
import re
# 在Python的正则表达式里,默认是贪婪模式,尽可能多的匹配
# 在贪婪模式后面添加 ? 可以将贪婪模式转换成为非贪婪模式
m = re.search(r'm.*a', 'o3rjomjadas')
print(m.group()) # mjada
# 尽可能少的匹配
n = re.search(r'm.*?a', 'o3rjomjadas')
print(n.group()) # mja
x1 = re.match(r"aa(\d+)", "aa2343ddd")
print(x1.group(0)) # aa2343
print(x1.group(1)) # 2343
x2 = re.match(r"aa(\d{2,}?)", "aa2343ddd")
print(x2.group(0)) # aa23
print(x2.group(1)) # 23
x3 = re.match(r"aa(\d+)ddd", "aa2343ddd")
print(x3.group(0)) # aa2343ddd
print(x3.group(1)) # 2343
x4 = re.match(r"aa(\d+?)ddd", "aa2343ddd")
print(x4.group(0)) # aa2343ddd
print(x4.group(1)) # 2343
x5 = re.match(r"aa(\d??)(.*)", "aa2343ddd")
print(x5.group(0)) # aa2343ddd
print(x5.group(1)) # 空
print(x5.group(2)) # 2343ddd