正则表达式是一种匹配的方式,我们在进行多位字符查找是可能会通过不断的遍历比较进行对比,如果样本比较大可能需要非常多的次数进行判断,正则表达式的存在可以大大简化这个过程。
正则表达式,简称为 regex,是文本模式的描述方法。例如,\d 是一个正则表达式,表示一位数字字符,即任何一位 0 到 9 的数字。Python 使用正则表达式 \d\d\d-\d\d\d-\d\d\d\d,来匹配前面 isPhoneNumber() 函数匹配的同样文本:3 个数字、一个短横线、3 个数字、一个短横线、4 个数字。所有其他字符串都不能匹配 \d\d\d-\d\d\d-\d\d\d\d 正则表达式。当然了正则表达式可以复杂得多。例如,在一个模式后加上花括号包围的 3({3}),就是说,“匹配这个模式 3 次”。所以较短的正则表达式 \d{3}-\d{3}-\d{4},也匹配正确的电话号码格式。
Python 中所有正则表达式的函数都在 re 模块中,对于3.x版本的存储在 regex模块中:
向 regex.compile() 传入一个字符串值,表示正则表达式,它将返回一个 Regex 模式对象(或者就简称为 Regex 对象);
search 方法只匹配第一次,返回一个 Match 对象;
findall() 方法匹配所有,返回一个字符串列表;
本次加载import regex提示Unused import statement 'import regex',而且import regex为灰色,解决办法是通过Pycharm file 菜单下有Invalidate caches/Restart菜单栏,点击清除缓存重新启动Pycharm即可。
本书用一个查找电话号码的例子让我们对其进行进一步体验比较:
- import regex
- def is_phone_number(text):
- if len(text) != 12:
- return False
- for i in range(0, 3):
- if not text[i].isdecimal():
- return False
- if text[3] != '-':
- return False
- for i in range(4, 7):
- if not text[i].isdecimal():
- return False
- if text[7] != '-':
- return False
- for i in range(8, 12):
- if not text[i].isdecimal():
- return False
- return True
- message = 'Call me at 415-555-1011 tomorrow. 415-566-9999 is my office.'
- a = 0
- for a in range(len(message)):
- chunk = message[a:a+12]
- if is_phone_number(chunk):
- print('Phone number found: ' + chunk)
- print(a)
- print('-------------')
- phoneNumRegex = regex.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
- ob = phoneNumRegex.search(message)
- print('First Phone number found: ' + ob.group())
- print('-------------')
- a = 0
- obs = phoneNumRegex.findall(message)
- for a in range(len(obs)):
- chunk = obs[a]
- print('Phone number found: ' + chunk)
- print(a+1)
- print('-------------')
结果如下:
有一些小技巧:
使用括号可以分组,注意不要超过括号的数量,group(1) 对应第一个括号,group(0) 匹配全部;
(使用括号分组后)使用groups()返回的是一个元组第一次匹配的,元组可以使用多重复制的技巧;findall()返回的列表元素也是元组;
字符 | 称为“管道”,希望匹配许多表达式中的一个,返回第一个匹配成功的,例如A|B,首先和A匹配符合就不与B匹配了
- import regex
- message = 'Call me at 415-555-1011 tomorrow. 415-566-9999 is my office.'
- phoneNumRegex = regex.compile(r'(\d\d\d)-\d\d\d-\d\d\d\d')
- ob = phoneNumRegex.search(message)
- print('First Phone number found: ' + ob.group())
- print('-------------')
- phoneNumRegex = regex.compile(r'(\d\d\d)-(\d\d\d)-(\d\d\d\d)')
- ob = phoneNumRegex.search(message)
- print('First Phone number found: ' + ob.group(2))
- print(ob.groups())
- obs = phoneNumRegex.findall(message)
- print(obs)
- print('-------------')
- phoneNumRegex = regex.compile(r'\d\d\d\d|\d\d\d')
- ob = phoneNumRegex.search(message)
- print('First Phone number found: ' + ob.group())
- obs = phoneNumRegex.findall(message)
- print(obs)
结果如下:
字符?表明它前面的分组在这个模式中是可选的:
- batRegex = regex.compile(r'Bat(wo)?man')
- mo1 = batRegex.search('The Adventures of Batman')
- mo1.group()
- 'Batman'
- mo1.groups()
- (None,)
- mo1 = batRegex.findall('The Adventures of Batman')
- findall
- Traceback (most recent call last):
- File "<input>", line 1, in <module>
- NameError: name 'findall' is not defined
- mo1
- ['']
- mo2 = batRegex.search('The Adventures of Batwoman')
- mo2.group()
- 'Batwoman'
- mo3 = batRegex.search('The Adventures of Batman Batwoman')
- mo3.group()
- 'Batwoman'
由上可见groups()和findall()方法不能用,返回的是空,加问号后如果有提前遍历返回;
*(称为星号)意味着“匹配零次或多次”,即星号之前的分组,可以在文本中出现任意次。它可以完全不存在,或一次又一次地重复。+ 则意味着“匹配一次或多次”。星号不要求分组出现在匹配的字符串中,但加号不同,加号前面的分组必须“至少出现一次”。这不是可选的。
如果想要一个分组重复特定次数,就在正则表达式中该分组的后面,跟上花括号包围的数字。例如,正则表达式 (Ha){3} 将匹配字符串 'HaHaHa',但不会匹配 'HaHa',因为后者只重复了 (Ha) 分组两次。可以不写花括号中的第一个或第二个数字,不限定最小值或最大值。例如,(Ha){3,} 将匹配 3 次或更多次实例,(Ha){,5} 将匹配 0 到 5 次实例。Python 的正则表达式默认是“贪心”的,这表示在有二义的情况下,它们会尽可能匹配最长的字符串。花括号的“非贪心”版本匹配尽可能最短的字符串,即在结束的花括号后跟着一个问号。
缩写字符分类如下:
方括号定义自己的字符分类,例如[aeiouAEIOU] 将匹配所有元音字符;
插入符号 ^ ,表明匹配必须发生在被查找文本开始处。类似地,可以再正则表达式的末尾加上美元符号 $ ,表示该字符串必须以这个正则表达式的模式结束;
. 字符称为“通配符”。它匹配除了换行之外的所有字符;
可以用 .* 表示“任意文本”。回忆一下,句点字符表示“除换行外所有单个字符”,星号字符表示“前面字符出现零次或多次”。
正则表达式不仅能找到文本模式,而且能够用新的文本替换掉这些模式。Regex 对象的 sub() 方法需要传入两个参数。第一个参数是一个字符串,用于取代发现的匹配。第二个参数是一个字符串,即正则表达式;
向 re.compile() 传入变量 re.VERBOSE,作为第二个参数,可以忽略正则表达式字符串中的空白符和注释;
向 re.compile() 传入变量re.IGNORECASE作为第二个参数,来忽略大小写;第二参数可以通过“按位或”操作符同时起作用;
|