【python语法】正则表达式

为什么要用正则表达式

对字符串进行操作几乎是每种编程语言中最重要的功能之一。很简单就可以理解,因为人类进行信息传播主要靠的是文字,也就是字符串,但是这么多信息并不完全是我们所要的,所以我们会通过编程来提取或者验证字符串的部分。

正则表达式就是用来匹配字符串的工具,其实它定义了一套语法,用若干描述字符就可以匹配出某段字符串的特征来。凡是符合种描述规则的,我们就认为它匹配

所以比如我们要判断一串字符是否为合法的Email地址的方法就是:

  • 创建一个符合Email特征的正则表达式
  • 然后使用该正则表达式去匹配输入的字符串,以判断是否合法。

正则表达式

元字符

用\d可以匹配一个数字,\w可以匹配一个字母或数字

元字符 匹配
. 任意字符(但是不包括换行符\n\r等
\w 字母 or 数字 or 下划线
\s 空白符(包括Tab等)
\d 数字

举个例子 'py.'可以匹配'pyc''pyo''py!'等等。因为.表示的是任意字符,所以可以匹配正常的字母,也可以匹配!

注意一个元字符只代表一个字符,比如\w只代表一个字母或者数字。

可以用[]表示范围,比如[0-9]表示匹配0~9之间的任意一个数字

  • [0-9a-zA-Z\_]可以匹配一个数字、字母或者下划线,可以等价于\w

有时需要查找不属于某个能简单定义的字符类的字符,这就是反义

代码/语法 匹配
[^x] 除了x以外的任意字符
[^aeiou] 除了aeiou这几个字母以外的任意字符

匹配变长的

如果好匹配变长的字符,用*表示0个或者以上的字符,用+表示1个或者以上的字符,用?表示0个或者1个字符。

还可以用大括号来表示,用{n}表示n个字符,用{n,m}表示n-m个字符。

代码/语法 说明
* 重复0次以上,等价于{0,}
+ 重复1次以上,等价于{1,}
? 重复0次或者1次,等价于{0,1}
{n} 重复n次
{n,} 重复n次以上
{n,m} 重复n到m次

所以比如\d{3}\s+\d{3,8}可以匹配哪些类型的字符串呢?
从左到右读一下:

  • \d{3}表示匹配3个数字,例如'010';
  • \s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配' ',' '等;
  • \d{3,8}表示3-8个数字,例如'1234567'。

如果要匹配'010-12345'这样的号码呢?由于'-'是特殊字符,在正则表达式中,要用''转义,所以,上面的正则是\d{3}-\d{3,8}。

  • [0-9a-zA-Z\_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如'a100''0_Z''Py3000'等等;

  • [a-zA-Z\_][0-9a-zA-Z\_]*可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;

  • [a-zA-Z\_][0-9a-zA-Z\_]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。

注意与通配符区分,linux的bash命令行中可以使用通配符,用*来代理任意个的字符。对于正则表达式而言,必须使用.*来表示任意个字符

那么对之前电话号码的那个例子,我们可以用更复杂的表达式来匹配\(?0\d{2}[) -]?\d{8}。\(?0\d{2}[) -]?\d{8}。,可以匹配(010)88886666,或022-22334455,或02912345678等。

  • 首先是一个转义字符(,它能出现0次或1次(?),
  • 然后是一个0,后面跟着2个数字(\d{2}),
  • 然后是)或-或空格中的一个,它出现1次或不出现(?),
  • 最后是8个数字(\d{8}

但是这个表达式也能匹配010)12345678或(022-87654321这样的“不正确”的格式。后面会说怎么样修改就可以解决这个问题。

边界限定符

边界限定 匹配
^ 字符串的开始
$ 字符串的结束

比如^\d{5,12}$表示以数字开头,以数字结尾,整行匹配,同时长度在5~12位一串数字。

分支条件

所谓分支条件就类似逻辑中的“或”,满足任意一个条件即匹配。具体方法是用|把不同的规则分隔开

比如之前讲过的匹配电话号码的例子。

  • 0\d{2}-\d{8}|0\d{3}-\d{7}这个表达式能匹配
    • 三位区号,8位本地号(如010-12345678),
    • 4位区号,7位本地号(0376-2233445)。
  • \(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}:这个表达式被|分为两个条件
    • 左边的表达式:\(0\d{2}\)可以匹配(010),[- ]?表示之间的连接符可以为-,也可以用空格间隔,也可以没有。
    • 右边的表达式0\d{2}[- ]?\d{8}:表示区号不用小括号括起来。

注意:匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。

分组

之前提到的是怎么重复单个字符(直接在字符后面加上限定符就行了);
但如果想要重复多个字符又该怎么办?可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了

比如(\d{1,3}\.){3}\d{1,3}可以按顺序进行分析,

  • \d{1,3}匹配1到3位的数字,
  • (\d{1,3}.){3}匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,
  • 最后再加上一个一到三位的数字(\d{1,3})。

总结

相信突然一下出现这么的符号大家一定是懵逼的。下面我们来总结一下{}[]()这几种符号的用途。

  • {2,3}:需要与它前面的字符结合,比如a{2,3}表示a出现2~3次
  • []:有3层含义
    • [a-z]:表示一个范围,也就是a~z之间的一个字符
    • [.*]:只要放入了[]里面的.*都不表示之前的含义,只是单纯作为一个普通的符号而已。比如这里面就表示要么为点号要么为星号的符号。
    • [^a]:表示非a的所有字符。主要不要和^a混淆,^a表示以a开头的一行。

贪婪匹配与懒惰匹配

a.*b来说 ,它将匹配最长的以a开始,以b结束的字符串,比如用它来搜索aabab的时候,会匹配整个字符串aabab,这就是贪婪匹配,也就是尽可能多的匹配

那么懒惰匹配指的就是尽可能少的匹配字符。在.*后面加上一个?以后,可以转换为懒惰匹配模式,那么.*?意味着使匹配成功的前提下使用最少的重复。比如把它应用于aabab,会匹配aab和ab

为什么第一个匹配是aab而不是ab?因为正则表达式有一条规则:最先开始的匹配拥有最高的优先权

| 代码/语法 | 说明 |
|-|
| *? | 重复任意次,但尽可能少重复 |
| +? | 重复1次或更多次,但尽可能少重复 |
| ?? | 重复0次或1次,但尽可能少重复 |
| {n,m}? | 重复n到m次,但尽可能少重复 |
| {n,}? | 重复n次以上,但尽可能少重复 |

匹配汉字

匹配汉字的表达式为[\u4E00-\u9FA5],这是汉字的UTF-8编码的范围。

python调用正则表达式

Python提供re模块,包含所有正则表达式的功能。由于Python的字符串本身也用\转义,所以要特别注意:
比如python字符串s = 'ABC\\-001'对应的正则表达式变成'ABC\-001'
所以最好把python字符串上加上r前缀,就不用考虑转义的问题,比如s = r'ABC\-001' # Python的字符串

如何判断正则表达式是否匹配:

  • 引入re模块:import re
  • 使用match方法,如果匹配成功,返回一个Match对象,否则返回None
    test = '用户输入的字符串'
if re.match(r'正则表达式', test):
    print('ok')
else:
    print('failed')

切分字符串

使用正则表达式后,切分字符变得更灵活。

如下使用split 的正常切分代码,可以看出无法识别连续的空格

>>> 'a b   c'.split(' ')
['a', 'b', '', '', 'c']

使用正则表达式可以实现更复杂的切分:

>>> re.split(r'[\s\,\;]+', 'a,b;; c  d')
['a', 'b', 'c', 'd']

分组

除了判断是否匹配之外,正则表达式可以提取子串的强大功能。用()表示的就是要提取的分组(Group)。
比如

m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')

这个正则表达式定义了两个分组,可以匹配-前后的两个表达式。

  • m.group(0):获得的是'010-12345'
  • m.group(1):获得是“010”
  • m.group(2):获得是'12345'

group(0)永远是原始字符串,group(1)、group(2)……表示第1、2、……个子串。

贪婪匹配

正则表达式默认就是贪婪匹配的。比如

>>> re.match(r'^(\d+)(0*)$', '102300').groups()
#结果是('102300', ''),\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串了

必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:

>>> re.match(r'^(\d+?)(0*)$', '102300').groups()
('1023', '00')

再比如

import re 
line = "boooooobby123";
reg_str = ".*(b.*b).*";
match_obj = re.match (reg_str , line);
if match_obj:
    print (match_obj.group(1));

因为.*是贪婪匹配的,所以它会一直匹配到booooooboooooo,那么小括号里面实际只匹配了bb

正则

如果使用非贪婪模式,也就是在.*后面加一个?

import re 
line = "boooooobby123";
reg_str = ".*?(b.*?b).*";
match_obj = re.match (reg_str , line);
if match_obj:
    print (match_obj.group(1)); 
image.png

例子:提取日期

下面我们希望能自动化的把一段文字中的生日给提取出来,但是如果之前没有规定格式的话,大家会随心所欲的写日期,比如

  • 出生于2018年1月23日
  • 出生于2018/1/23
  • 出生于2018-1-23
  • 出生于2018-01-23
  • 出生于2018-01
  • 出生于2018年01月

下面我们需要给一个正则表达式,要求他能匹配上面所有的日期格式。

  • 首先匹配日期中的年的部分,从上面的文本可以看出,只有2018年2018-
    2018/这几种形式。也就是可以先用\d{4}表示数字,再用[年-\]来表示符号。凑起来就是
regex = r"出生于(\d{4}[年/-])"
  • 再来看月份的数字部分只可能有011两种形式:\d{1,2}
  • 月份后面的部分就相对比较复杂了。同样的,我们可以进行分类列举,然后使用分支条件即可统一表达。
    • 匹配2018年1月23日2018-01-23以及2018/1/23后面的部分:[月/-]\d{1,2}日?
    • 匹配2018年01月这种的后面的部分:[月/-]$
    • 匹配2018-01后面的部分,当然是直接用结尾符:$
    • 最后用()括起来,使用|进行分类讨论。
([月/-]\d{1,2}日?|[月/-]$|$)

最后把所有的部分合并起来。

import re 
lines = [
 "出生于2018年1月23日",
 "出生于2018/1/23",
 "出生于2018-1-23",
 "出生于2018-01-23",
 "出生于2018-01",
 "出生于2018年01月"]
regex = r"出生于(\d{4}[年/-]\d{1,2}([月/-]\d{1,2}日?|[月/-]$|$))"
for line in lines :
    m = re.match(regex  , line )
    if m :
        print(m.group(1));
image.png

编译

使用正则表达式时,re模块内部会干两件事情:

  • 编译正则表达式,此时会进行语法分析,如果表达式本身不合法,会报错;
  • 用编译后的正则表达式去匹配字符串。
    那么如果一个正则表达式要使用非常多次,可以预编译该正则表达式
# 编译:
>>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
# 使用:
>>> re_telephone.match('010-12345').groups()
('010', '12345')

参考

廖雪峰-正则表达式
正则表达式30分钟入门教程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容