引入标准库模块re
match() 和 search():查找到一个匹配后就停止
- match(pattern, string, flags=0):从字符串开头开始匹配
# 做一次简单的文本匹配/搜索操作时:
# match():从字符串开头开始匹配,匹配失败时返回None
m = re.match('My', 'My name is xuebi')
print(m)
print(m.group()) # 匹配到的内容
print(m.start()) # 开始位置
print(m.end()) # 结束位置
print((m.start(), m.end())) # (开始位置, 结束位置)
print(m.span()) # (开始位置, 结束位置)
# m.span() == (m.start(), m.end())
# <re.Match object; span=(0, 2), match='My'>
# My
# 0
# 2
# (0, 2)
# (0, 2)
# 做大量的匹配和搜索操作时:
# 编译(预编译模式):
# 功能上一样,执行速度更快
p = re.compile('[a-z]+')
m2 = p.match('aaaaMy name is xuebi')
m3 = p.match('xuebi is me')
print(m2)
print(m3)
if p.match('#xuebi is me'):
print('yes')
else:
print('no')
# <re.Match object; span=(0, 4), match='aaaa'>
# <re.Match object; span=(0, 5), match='xuebi'>
# no
- search(pattern, string, flags=0):寻找首次匹配
m = re.search('xuebi', 'kele and xuebi are person? xubi is not.') # 只找第一次出现的
print(m)
# —— <re.Match object; span=(9, 14), match='xuebi'>
findall() 和 finditer():查找字符串中所有的匹配项
# 预编译模式
text = 'Today is 2/12/2019. Python starts 23/10/2019. Yesterday is 1-12-2019.'
p = re.compile('\d+[-/]\d+[-/]\d') # [-/]:要么-,要么/(-或/)
- findall(pattern, string, flags=0):寻找所有匹配项,返回列表
lis = p.findall(text)
print(lis)
# ['2/12/2', '23/10/2', '1-12-2']
- finditer(pattern, string, flags=0):寻找所有匹配项,返回迭代器对象
lis = p.finditer(text)
print(lis)
for item in lis:
print(item)
print(item.group())
# <callable_iterator object at 0x030CAC50>
# <re.Match object; span=(9, 15), match='2/12/2'>
# 2/12/2
# <re.Match object; span=(34, 41), match='23/10/2'>
# 23/10/2
# <re.Match object; span=(59, 65), match='1-12-2'>
# 1-12-2
- split(pattern, string, maxsplit=0, flags=0)
msg = 'xuebi@qq.com'
lis = msg.split('@')
print(lis)
# —— ['xuebi', 'qq.com']
line = 'asdf fjdk; afed, fjek,asdf, foo'
lis1 = re.split(r'[,;\s]+', line) # 按逗号或分号或空白字符中一次或多次进行分割
lis2 = re.split(r'[,;\s]\s*', line) # 按逗号或分号或空白字符且他们的后面再接0个或多个空白字符进行分割
print(lis1) # 按原生字符串分割,\s匹配任意一个空白字符包括[ \t\n\r\f\v]
print(lis2)
# —— ['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
# —— ['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
str.replace() 和 sub() 和 subn():
# 对于简单的替换,可以用str.replace()方法
text = 'yeah, but no, but yeah, but no, but yeah'
text = text.replace('yeah', 'yes')
print(text)
# —— yes, but no, but yes, but no, but yes
- sub(pattern, repl, string, count=0, flags=0)
# 对于复杂的替换,可以用正则表达式中的sub()方法
# sub()替换匹配
p = re.compile(r'(\d+)/(\d+)/(\d+)')
text = 'Today is 12/2/2019. PyCon starts 10/23/2019.'
text = p.sub(r'\3-\1-\2', text) # \3\2\1为group()对应的组元素
print(text)
# —— Today is 2019-12-2. PyCon starts 2019-10-23.
# 对于更复杂的替换,可以传递一个替换回调函数来代替。
# 一个替换回调函数的参数是一个Match对象,也就是match()或者find()返回的对象。
# 使用group()方法来提取特定的匹配部分。
# 回调函数最后返回替换字符串。
from calendar import month_abbr
def change_date(m):
mon_name = month_abbr[int(m.group(1))]
return '{} {} {}'.format(m.group(2), mon_name, m.group(3))
text = 'Today is 12/2/2019. PyCon starts 10/23/2019.'
text = p.sub(change_date, text)
print(text)
# —— Today is 2 Dec 2019. PyCon starts 23 Oct 2019.
- subn(pattern, repl, string, count=0, flags=0)
# subn():可查出替换了多少次
newtext, n = p.subn(change_date, text)
print(newtext)
print(n)
# Today is 2 Dec 2019. PyCon starts 23 Oct 2019.
# 0
类似的还有:
fullmatch(pattern, string, flags=0)
compile(pattern, flags=0)
purge()
template(pattern, flags=0)
......
【拓展】回调函数
解决模块之间不能循环调用的问题
=================================
普通字符集
# 普通字符:普通的文本值代表自身,用于匹配非特殊字符
print(re.search('My', 'My name is xuebi')) # My匹配My
# —— <re.Match object; span=(0, 2), match='My'>
# .:可匹配换行符\n以外的任意一个字符。
print(re.search('.', 'My name is xuebi')) # 匹配一个非换行符
# —— <re.Match object; span=(0, 1), match='M'>
print(re.search('ab.', 'abCMy name is xuebi')) # 匹配ab和一个非换行符
print(re.search('...', 'ab CMy name is xuebi')) # 匹配三个非换行符
print(re.search('ab.', 'ab')) # 匹配ab和一个非换行符
# —— <re.Match object; span=(0, 3), match='ab '>
# —— <re.Match object; span=(0, 3), match='ab '>
# —— None
# \:转义字符
print(re.search('xuebi.', 'xuebi.com')) # 匹配xuebi和一个非换行字符
print(re.search('xuebi\.', 'xuebi.com')) # 匹配xuebi和一个.字符
print(re.search('xuebi\.', 'xuebicom')) # 匹配xuebi和一个.字符
# —— <re.Match object; span=(0, 6), match='xuebi.'>
# —— <re.Match object; span=(0, 6), match='xuebi.'>
# —— None
# []:匹配中括号内的一个字符(括号里内容不限)
# 1. 中括号内的字符可以全部列出,如[abc]表示匹配字符a或b或c
# 2. 也可以使用-表示范围,如[a-z]表示匹配所以小写字母中的任意一个字符
# 3. 本文后续要介绍的如*、+等特殊字符在中括号内将失去特殊含义,如[*+()]表示匹配字符*或+或(或)
# 4. 本文后续要介绍的特殊字符集如\d、\w等也可以放入此中括号内,继续保持特殊含义
# 5. 如果中括号内的字符序列前面有一个^,表示不匹配中括号内的任何一个字符,如[^0-9]表示不匹配数字,a[^0-9]c不匹配a1c,但会匹配abc
# 6. 要匹配字符],可以转义它,或者把它放在中括号内的首位,如a[()[\]{}]c或a[]()[{}]c都可以匹配到a]c
print(re.search('[abc]', 'a_xuebicom')) # 匹配a或b或c
print(re.search('[a-z]', 'a_xuebicom')) # 匹配a到z中任意一个
print(re.search('[*+()]', '(_xuebicom)')) # 匹配字符 * 或 + 或 ( 或 )
print()
print(re.search('[^*+()]', '(_xuebicom)')) # 匹配非字符 * 或 + 或 ( 或 )
print(re.search('[a[()[\]{}]]c', '(a]c_xuebicom)')) # 匹配a或]或(或)或[或{或} 和 ]和c
# 等价于下一句
print(re.search('a[]()[{}]c', '(a]c_xuebicom)')) # 匹配a 和]或(或)或[或{或} 和 c
# <re.Match object; span=(0, 1), match='a'>
# <re.Match object; span=(0, 1), match='a'>
# <re.Match object; span=(0, 1), match='('>
# 特殊字符集如\d、\w等例子请往下看
# <re.Match object; span=(1, 2), match='_'>
# <re.Match object; span=(1, 4), match='a]c'>
# <re.Match object; span=(1, 4), match='a]c'>
特殊字符集
# \d:匹配任意一个数字字符,等价于[0-9]
# \D:匹配任意一个非数字字符,等价于[^0-9]
# \s:匹配任意一个空白字符,等价于[ \t\n\r\f\v]
# \S:匹配任意一个非空字符,等价于[^ \t\n\r\f\v]
# \w:匹配任意一个alphanumeric character,等价于[a-zA-Z0-9_]
# \W:匹配任意一个non-alphanumeric character,等价于[^a-zA-Z0-9_]
print(re.search('a\db', 'xba1b)')) # 匹配a和一个数字字符和b
# a\db == a[0-9]b
print(re.search('a\Db', 'xbaAb)')) # 匹配a和一个非数字字符和b
# a\Db == a[^0-9]b
print(re.search('a\sb', 'xba b)')) # 匹配a和一个空白字符和b
# a\sb == a[ \t\n\r\f\v]b
print(re.search('a\Sb', 'xbaCb)')) # 匹配a和一个非空白字符和b
# a\Sb == a[^ \t\n\r\f\v]b
print(re.search('a\wb', 'xba_b)')) # 匹配a和一个alphanumeric character和b
# a\wb == a[a-zA-Z0-9_]b
print(re.search('a\Wb', 'xba b)')) # 匹配a和一个non-alphanumeric character和b
# a\Wb == a[^a-zA-Z0-9_]b
# <re.Match object; span=(2, 5), match='a1b'>
# <re.Match object; span=(2, 5), match='aAb'>
# <re.Match object; span=(2, 5), match='a b'>
# <re.Match object; span=(2, 5), match='aCb'>
# <re.Match object; span=(2, 5), match='a_b'>
# <re.Match object; span=(2, 5), match='a b'>
# 【例子】
import string
printable = string.printable
print(re.findall('\d', printable))
# ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
print(re.findall('\w', printable), end='')
# ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
# 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
# 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
# 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
# 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
# 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
# 'Y', 'Z', '_']
print(re.findall('\s', printable))
# [' ', '\t', '\n', '\r', '\x0b', '\x0c']
print(re.findall('\w', 'abc' + '-/*' + '\u00ea' + '\u0115'))
# ['a', 'b', 'c', 'ê', 'ĕ']
数量
# prev* :匹配0个或多个prev,尽可能多地匹配,贪婪模式
# prev*? :匹配0个或多个prev,尽可能少地匹配,非贪婪模式
# prev+ :匹配1个或多个prev,尽可能多地匹配,贪婪模式
# prev+? :匹配1个或多个prev,尽可能少地匹配,非贪婪模式
# prev? :匹配0个或多个prev,尽可能多地匹配,贪婪模式
# prev?? :匹配0个或多个prev,尽可能少地匹配,非贪婪模式
# prev{m} :匹配m个连续的prev
# prev{m,n} :匹配m个到n个连续的prev,尽可能多地匹配,贪婪模式
# prev{m,n}? :匹配m个到n个连续的prev,尽可能少地匹配,非贪婪模式
print(re.findall('ab*', 'ab'))
print(re.findall('ab*', 'a_ab_abbb'))
# ['ab']
# ['a', 'ab', 'abbb']
print(re.findall('ab*?', 'ab'))
print(re.findall('ab*?', 'a_ab_abbb'))
# ['a']
# ['a', 'a', 'a']
print(re.findall('ab+', 'abb'))
print(re.findall('ab+', 'a_ab_abbb'))
# ['abb']
# ['ab', 'abbb']
print(re.findall('ab+?', 'abb'))
print(re.findall('ab+?', 'a_ab_abbb'))
# ['ab']
# ['ab', 'ab']
print(re.findall('ab?', 'abb'))
print(re.findall('ab?', 'a_ab_abbb'))
# ['ab']
# ['a', 'ab', 'ab']
print(re.findall('ab??', 'abb'))
print(re.findall('ab??', 'a_ab_abbb'))
# ['a']
# ['a', 'a', 'a']
print(re.findall('a{3}', 'aaaa'))
print(re.findall('a{3}', 'a_aa_aaa'))
# ['aaa']
# ['aaa']
print(re.findall('a{1,3}', 'aaaaa'))
print(re.findall('a{1,3}', 'aaaaaaa'))
print(re.findall('a{1,3}', 'a_aa_aaa'))
# ['aaa', 'aa']
# ['aaa', 'aaa', 'a']
# ['a', 'aa', 'aaa']
print(re.findall('a{1,3}?', 'aa'))
print(re.findall('a{1,3}?', 'a_aa_aaa'))
# ['a', 'a']
# ['a', 'a', 'a', 'a', 'a', 'a']
# 可以在*或+或?的后面再添加一个?,此时表示非贪婪模式匹配,Python中的正则表达式默认是贪婪模式匹配,它会在满足整个表达式要求的前提下,尽可能多地去匹配字符
边界
# ^prev:匹配以prev开头的字符串
# prev$:匹配以prev结尾的字符串
# \b:单词边界。\b在Python中默认会被转义为\x08表示退格,需要将整个正则表达式指定为原始字符串(在前面加个r)
# \B:非单词边界
# \Aprev:匹配以prev开头的字符串
# prev\Z:匹配以prev结尾的字符串
print(re.findall('^ab_', 'ab_cd'))
print(re.findall('^.*_ab', '1+x_ab_cd_ab'))
# ['ab_']
# ['1+x_ab_cd_ab']
print(re.findall('_ab$', 'ab_cd_ab'))
print(re.findall('.*_ab$', '1+x_ab_cd_ab'))
# ['_ab']
# ['1+x_ab_cd_ab']
print(re.findall(r'\bfoo\b', ' foo foo.(foo)'))
print(re.findall(r'\bfoo\b', ' foobar _foo1'))
# ['foo', 'foo', 'foo']
# []
print(re.findall(r'py\B', 'pythonpy3py2'))
print(re.findall(r'py\B', 'py py.py!'))
# ['py', 'py', 'py']
# []
print(re.findall(r'\Aab', 'abcde'))
print(re.findall(r'\Aab', '_abcde'))
# ['ab']
print(re.findall(r'ab\Z', '123ab'))
print(re.findall(r'ab\Z', '123ab_'))
# ['ab']
# []
# 【例】
import re
s = 'I wish I may, I wish I might have a dish of fish tonight.'
m = re.search(r'(. dish\b).*(\bfish)', s)
print(m.group())
print(m.groups())
print(m.group(0))
print(m.group(1))
print(m.group(2))
# a dish of fish
# ('a dish', 'fish')
# a dish of fish
# a dish
# fish
分组
# (expr):将小括号内的表达式作为一个分组,后面可以接表示数量的特殊字符
# expr1|expr2:匹配expr1或expr2,等价于[expr1expr2]
# \1:引用编号为1的分组匹配到的字符串。类似的还有\0\2\3...
# (?P<NAME>expr):类似于(expr),同时给分组制定了一个别名NAME,注意时大写的字母P
# (?P=NAME):引用别名为NAME的分组,当然也可以继续使用编号形式引用分组,此时要加r制定为原始字符串。
print(re.findall('a(bc)?d', 'abcdd'))
print(re.findall('a(bc)?d', 'abcbcd'))
# ['bc']
# []
print(re.findall('p(i|u)g', 'pug_pig'))
# ['u', 'i']
print(re.findall(r'a(\d)b\1c\1d', 'a3b3c3d'))
# ['3']
print(re.findall(r'a(?P<quote>\d)b\1c(?P=quote)d', 'a3b3c3d'))
# ['3']
# 【例】
m2 = re.search(r'(?P<DISH>. dish\b).*(?P<FISH>\bfish)', s)
print(m2.groups())
print(m2.group())
print(m2.group('DISH'))
print(m2.group('FISH'))
print(m2.group(1))
print(m2.group(2))
# ('a dish', 'fish')
# a dish of fish
# a dish
# fish
# a dish
# fish
拓展语法
# (?:expr):非捕获组(成为整体但不成组)
# prev(?=next):环视,如果后面为next,则返回prev
# prev(?!next):环视,如果后面不是next,则返回prev
# (?<=prev)next:环视,如果前面为prev,则返回next
# (?<!\d)next:环视,如果前面不是prev,则返回next
print(re.findall('a(?:\d)+bc', 'a333bc'))
# print(re.findall(r'a(?:\d)+b\1c', 'a333bc')) # 语法错误
# ['a333bc']
print(re.findall('ab(?=\d)', 'ab3_+')) # ab后紧跟一个数字时,输出ab
# ['ab']
print(re.findall('ab(?!\d)', 'abc_+')) # ab后紧跟一个非数字时,输出ab
# ['ab']
print(re.findall('(?<=\d)ab', '_+1ab')) # ab前紧跟一个数字时,输出ab
# ['ab']
print(re.findall('(?<!\d)ab', '_+ab')) # ab前紧跟一个非数字时,输出ab
# ['ab']
# 【例】
import re
line = 'asdf fjdk; afed, fjek,asdf, foo'
print(re.split(r'[;,\s]\s*', line)) # 使用多个分割符或者是分隔符周围不确定的空格时,来分割一个字符串
print(re.split(r'(;|,|\s)\s*', line)) # 使用括号时被匹配的文本(那些分隔符)也将出现在结果列表中
print(re.split(r'(?:,|;|\s)\s*', line)) # 不想保留分割字符串到结果列表中去,但仍然需要使用到括号来分组正则表达式的话, 确保你的分组是非捕获分组,形如(?:...)
# ['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
# ['asdf', ' ', 'fjdk', ';', 'afed', ',', 'fjek', ',', 'asdf', ',', 'foo']
# ['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
实例1 — — 字符串忽略大小写的搜索替换
# 【实例1-1】为了在文本操作时忽略大小写,你需要在使用re模块的时候给这些操作提供re.IGNORECASE标志参数
import re
text = 'UPPER PYTHON, lower python, Mixed Python'
print(re.findall('python', text))
print(re.findall('python', text, flags=re.IGNORECASE))
print(re.sub('python', 'snake', text, flags=re.IGNORECASE))
# ['python']
# ['PYTHON', 'python', 'Python']
# UPPER snake, lower snake, Mixed snake
# 弊端:这样不管原字符串中的是大写、小写或首字母大写的python,全替换为小写的snake
# 【实例1-2】替换字符串能自动跟被匹配字符串的大小写保持一致
import re
text = 'UPPER PYTHON, lower python, Mixed Python'
def matchcase(word):
def replace(m):
text = m.group()
if text.isupper():
return word.upper()
elif text.islower():
return word.lower()
elif text[0].isupper():
return word.capitalize()
else:
return word
return replace
print(re.sub('python', matchcase('snake'), text, flags=re.IGNORECASE))
# UPPER SNAKE, lower snake, Mixed Snake
# matchcase('snake')返回了一个回调函数
# 【拓展】Python支持的其它正则表达式的标志位flags:
# re.A: 或re.ASCII,使\w/\W/d/\D/\s/\S/\b/\B只匹配ASCII字符,不匹配Unicode字符。
# re.I: 或re.IGNORECASE,忽略大小写,[A-Z]会匹配小写字母。
# re.M: 或re.MULTILINE,多行模式,改变^或$的默认行为。
# re.S: 或re.DOTALL,Make the.special character match any character at all, including a newline; without this flag, .will match anything except a newline。
# re.U: 或re.UNICODE,默认使用此标志位,\w/\W/d/\D/\s/\S/\b/\B会匹配Unicode字符,如果指定了re.A标志,则re.U失效。
# re.X: 或re.VERBOSE,允许整个正则表达式写成多行,忽略空白字符,并可以添加#开头的注释,这样更美观。
# 【实例】
import re
a = re.compile(r"""\d + # the integral part
\. # the decimal point
\d * # some fractional digits""", re.X)
b = re.compile(r"\d+\.\d*")
print(a.match('10.2'))
print(b.match('10.2'))
# <re.Match object; span=(0, 4), match='10.2'>
# <re.Match object; span=(0, 4), match='10.2'>
# 【实例】一次指定多个标志位
import re
text = '''UPPER PYTHON
lower python
Mixed Python'''
print(re.sub('python$', 'snake', text))
# UPPER PYTHON
# lower python
# Mixed Python
print(re.sub('python$', 'snake', text, flags=re.IGNORECASE))
# UPPER PYTHON
# lower python
# Mixed snake
print(re.sub('python$', 'snake', text, flags=re.IGNORECASE|re.MULTILINE))
# UPPER snake
# lower snake
# Mixed snake
p = re.compile(r'python$', re.I|re.M)
print(p)
# re.compile('python$', re.IGNORECASE|re.MULTILINE)
print(p.sub('snake', text))
# UPPER snake
# lower snake
# Mixed snake
print(re.sub('(?im)python$', 'snake', text))
# UPPER snake
# lower snake
# Mixed snake
实例2 — — 最短匹配模式: 非贪婪
# 【实例2-1】试着用正则表达式匹配某个文本模式,但是它找到的是模式的最长可能匹配
import re
p = re.compile(r'"(.*)"')
text1 = 'Computer says "no."'
print(p.findall(text1))
text2 = 'Computer says "no." Phone says "yes."'
print(p.findall(text2)) # 默认是贪婪模式,(.*)会尽可能多的匹配,只要后面有一个"就能满足整个正则,所以匹配到最后一个冒号之前了
# ['no.']
# ['no." Phone says "yes.']
# 【实例2-2】修正2-1的问题在模式中的*操作符后面加上?修饰符
p2 = re.compile(r'"(.*?)"')
print(p2.findall(text2))
# ['no.', 'yes.']
实例3 — — 多行匹配
# 试着使用正则表达式去匹配一大块的文本,而你需要跨越多行去匹配,当你用点.去匹配任意字符的时候,忘记了点.不能匹配换行符\n的事实。
# 【实例3-1】试着去匹配C语言分割的注释
import re
text1 = '/* this is a comment */'
text2 = '''/* this is a
multiline comment */
'''
p = re.compile(r'/\*(.*?)\*/')
print(p.findall(text1)) # 能正确匹配单行
print(p.findall(text2)) # . 点号不能匹配多行中的换行符,所以整体匹配失败
# [' this is a comment ']
# []
# 【实例3-2】改进3-1,可以使用.|\n匹配所有字符,或者指定re.DOTALL标志位
p2 = re.compile(r'/\*((?:.|\n)*?)\*/')
print(p2.findall(text2))
p3 = re.compile(r'/\*(.*?)\*/', re.DOTALL)
print(p3)
print(p3.findall(text2))
# [' this is a\nmultiline comment ']
# re.compile('/\\*(.*?)\\*/', re.DOTALL)
# [' this is a\nmultiline comment ']
# 【例3-3】re.MULTILINE多行模式的用法
import re
text = 'This is some text -- with punctuation.\nA second line.'
pattern = r'(^\w+)|(\w+\S*$)'
single_line = re.compile(pattern)
multiline = re.compile(pattern, re.MULTILINE)
print('Single Line :')
for match in single_line.findall(text):
print(' {!r}'.format(match))
print('Multline :')
for match in multiline.findall(text):
print(' {!r}'.format(match))
# Single Line :
# ('This', '')
# ('', 'line.')
# Multline :
# ('This', '')
# ('', 'punctuation.')
# ('A', '')
# ('', 'line.')