python 中的正则表达式
python 是通过 re 模块(Regular Expression)提供的各种功能,是 python 内嵌的模块。完整的正则表达式涉及的内容很多,以下只是记录了一些比较常用的使用方法。
常用函数
re.Match
先介绍一下 re.Match 类,其内容包含了两个部分:‘span’ 匹配到的字符串在原串中的头尾索引,‘match’ 是匹配到的最终字符串。
Match.group()
返回匹配到的字符串的结果,没有匹配到则是 None。
Match.start()
返回匹配的字符串首位在原串中的索引位置。
Match.end()
返回匹配的字符串末位在原串中的索引位置。
>>> re.match('[a-z]*', 'hello world').group() 'hello' >>> re.match('[a-z]*', 'hello world').start() 0 >>> re.match('[a-z]*', 'hello world').end() 5
匹配
re.match(pattern, string, flags=0)
判断一个正则表达式是否从字符串的开头处匹配,如果不满足条件则返回 None, 返回 re.Match。
re.search(pattern, string, flags=0)
遍历字符串,找到第一个满足正则表达式返回 re.Match,若无满足的返回 None。
re.findall(pattern, string, flags=0)
遍历字符串,找出所有正则表达式的子串并以列表形式返回,若无则返回一个空列表。
re.finditer(pattern, string, flags=0)
遍历字符串,找出所有正则表达式的子串并以迭代器形式返回。
>>> print re.match('[a-z]*', 'hello world').group() hello >>> print re.search('[a-z]*', 'hello world').group() hello >>> print re.findall('[a-z]*', 'hello world') ['hello', '', 'world', ''] >>> print re.finditer('[a-z]*', 'hello world') <callable-iterator object at 0x0394F350>
替换
re.sub(pattern, repl, string, count=0, flags=0)
先找出所有匹配的字符串,用 repl 来代替匹配的字符串,count 为替换的次数,默认为0,如无匹配则无改变的返回。
>>> re.sub('[0-9]+', ' ', 'ni38shi3434342yi313ge32415ren!',3) 'ni shi yi ge32415ren!'
re.subn(pattern, repl, string, count=0, flags=0)
功能同上,但是会返回替换后的字符串以及替换次数的两元组,如下:
>>> re.subn('[0-9]+', ' ', 'ni38shi3434342yi313ge32415ren!',6) ('ni shi yi ge ren!', 4)
分割
re.split(pattern, string, maxsplit=0, flags=0)
先匹配出所有字符串,用匹配出的字符串当做分隔符,然后将分割结果以 list 的形式返回。
>>> re.split('a', 'dsfadasgaaeaf',10) ['dsf', 'd', 'sg', '', 'e', 'f']
特殊的函数
re.compile(pattern)
将正则表达式语句编译成新的对象,这样如果需要重复用到一个规则是就很方便了,同时也能够提高效率。
>>> e=re.compile('\w+') >>> s=e.findall('wo shi zhong guo ren!') >>> print s ['wo', 'shi', 'zhong', 'guo', 'ren']
re.escape(pattern)
将字符串中所有非字母数字更换为转义字符并返回。
>>> re.escape('12\dae#df\ns t') '12\\\\dae\\#df\\\ns\\\t\\ t'
正则表达式语法
正则表达式的规则其实用字符串去寻找相同的字符串,如 test
去匹配 math test
,数字也一样。如果仅仅只是这样的话,倒也没必要继续介绍下去,因为有一类特殊的字符的存在,称之为元字符 。
. ^ $ * + ? { } [ ] \ | ( )
[ ]
中括号,用来指定字符类,因为字母和数字都是连续的,所以可以用 “-” 来指定一系列的字母或数字。如:[abcde] 和 [a-e] 是等价的。但是 - 在首位或则末位是匹配其本身如:[-abc] 匹配的是 ‘-’,‘a’, 'b', 'c'。
括号中的元字符是没有特殊意义的,会匹配其本身。
当括号中首位字符为 ^ 时表示取反,表示除了括号中的这些字符外的所有字符。如果在其他位置,只表示其本身。
当需要匹配 [ ] 本身时,有两种方式,一种是加上 \ 转义,另一种就是把 ] 放在首位,破坏配对。
>>> re.findall('[^a]', 'dsfkhab') ['d', 's', 'f', 'k', 'h', 'b'] >>> re.findall('[-abc]', 'a-bcdag') ['a', '-', 'b', 'c', 'a'] >>> re.findall('[a^]', 'adf^dfadsf*') ['a', '^', 'a'] >>> re.findall('[][]', '[df]3\77\[\d\f]adsf*') ['[', ']', '[', ']'] >>> re.findall('[[\]]', '[df]3\77\[\d\f]adsf*') ['[', ']', '[', ']']
\
转义符,需要使用任何的特殊字符,在前面加上 \ 即可,此外 \ 加上字母表示一些特殊的集合。
\d 匹配任何十进制数字;这等价于类 [0-9]。 \D 匹配任何非数字字符;这等价于类 [^0-9]。 \s 匹配任何空白字符;这等价于类 [ \t\n\r\f\v]。 \S 匹配任何非空白字符;这相当于类 [^ \t\n\r\f\v]。 \w 匹配任何字母与数字字符;这相当于类 [a-zA-Z0-9_]。 \W 匹配任何非字母与数字字符;这相当于类 [^a-zA-Z0-9_]。 \b 匹配单词边界,返回的匹配结果里是不包含空白,而 \s 是会包含的。 \B 匹配非单词边界 >>> re.findall( r'\Bbc\w+', 'abc abcd bc bce') ['bcd']
.
匹配任何除了换行符以外的所有字符,当指定 re.DOTALL 标记时,可匹配任意字符(包括换行符)。
^
匹配字符串的开头,与中括号里表示的意义不一样。MULTILINE (设置 re.M) 的情况下,会默认匹配换行符后面的第一个字符。\A 也只匹配开头,即使在 M 的模式下也只匹配字符串首位。
>>> re.findall('^3\w', '3ds3fk3h3acb') ['3d'] >>> re.findall('^3\w', '3ds3fk\n3h3acb',re.M) ['3d', '3h']
$
匹配字符串的末位,MULTILINE (设置 re.M) 的情况下,会默认匹配换行符前面的字符。\Z 只匹配字符串的结尾,即使在 M 的模式下也只匹配字符串末尾。
>>> re.findall('foo.$', 'foo1\nfoo2\n') ['foo2'] >>> re.findall('foo.$', 'foo1\nfoo2\n', re.M) ['foo1', 'foo2']
*
表示前面字符重复 0 次到任意次,如:‘ab*’ 可以匹配到 ‘a’,‘ab’, 'abb'……等好多个。这里的话一般情况下,会按照贪心算法来匹配,除非特殊设定后,后面再讲。
>>> re.findall('fo*', 'f1\nfoo2\n') ['f', 'foo']
+
表示前面字符重复 1 次到任意次,和 * 类似,只是要求至少出现一次。
>>> re.findall('fo+', 'f1\nfoo2\n') ['foo']
?
表示前面的字符最多出现 1次。
>>> re.findall('fo?', 'f1\nfoo2\n') ['f', 'fo']
*? +? ??
前面说了,正常情况下 * ,+,? 都是贪心算法,但是在其后面加上 ?后就不一样了,总是以最小匹配长度来匹配。
>>> re.findall('<.*>', '<a>b<c>') ['<a>b<c>'] >>> re.findall('<.*?>', '<a>b<c>') ['<a>', '<c>']
{ }
用来表示前面的字符应该匹配多少次,分为两种情况:'a{3}' 这是匹配连续 3 个 a,‘{3, 6}’ 匹配连续 3 到 6 个 a,‘{3,}’ 匹配至少 3 个 a。依然遵循贪心匹配规则,如果不希望这样的话和上面一样,在后面加上 ? 即可,如:
>>> re.findall('a{3,6}', 'aaaaaaaa') # 8个a ['aaaaaa'] >>> re.findall('a{3,6}', 'aaaaaaaaa') # 9个a ['aaaaaa', 'aaa'] >>> re.findall('a{3,6}?', 'aaaaaaaa') # 8个a ['aaa', 'aaa']
|
或,A|B 匹配 A 或者 B 均可,会优先匹配 A,如满足就不再匹配 B。
>>> re.findall('ab|bc', 'abc') ['ab']
组
简单的理解()括起来的就是一个组。
捕获组
捕获组会根据最终匹配的结果将结果存储在内存中,可通过反引用的方式(\number 或 \name)来使用捕获组的匹配结果。
捕获组包含两种:
一种是正常的 (pattern),其结果会按数字编号来存储,0 代表整个表达式的匹配结果,1 代表第一个组的匹配结果,以此类推。
另一种叫命名捕获组 (?P<name>pattern),其结果会以 name 存储,通过反引用就可以来查看或使用其结果。
>>> s = "<html><h1>learning is good!</h1></html>" >>> p = r"<(.+)><(.+)>(?p<content>.+)</\2></\1>" >>> m = re.match(p, s) >>> m.groups() ('html', 'h1', 'learning is good!') >>> print m.group(1),m.group('content') html learning is good!
非捕获组
看这名字不太好理解,非捕获组匹配到的结果并不会保存在内存,也就不存在反引用了。形式为:(?:pattern)
这里有个注意的点,就是 findall 使用时,捕获组和非捕获组的结果是不一样的。如:
>>> s='123@qq.com345@163.com126@126.com' >>> re.findall(r'\w+@(qq|163|126).com', s) ['qq', '163', '126'] >>> re.findall(r'\w+@(?:qq|163|126).com', s) ['123@qq.com', '345@163.com', '126@126.com']
这个原因是 findall() ,如果存在一个或者多个组,那么结果将会返回一个组列表或则是多个组组成的元组列表。
零宽断言
用于查找特定内容之前或之后的内容,但是不包括特定内容本身。
-
(?=exp)
匹配exp后面的位置
-
(?<=exp)
匹配exp前面的位置
-
(?!exp)
匹配后面跟的不是exp的位置
-
(?<!exp)
匹配前面不是exp的位置
>>> s="regex represents regular expression" >>> re.search(r're(?=sents)', s).span() (9, 11) >>> re.search(r're(?!sents)', s).span() (0, 2) >>> re.search(r'(?<=p)re', s).span() (9, 11) >>> re.search(r'(?<!p)re', s).span() (0, 2)
注释
(?#) python 允许在表达式中写注释,匹配时会忽略其内容。
标志修饰符
简写 | 全称 | 描述 |
---|---|---|
re.I | re.IGNORECASE | 使匹配对大小写不敏感 |
re.L | re.LOCALE | 做本地化识别(locale-aware)匹配,不建议使用 |
re.M | re.MULTILINE | 多行匹配,影响 ^ 和 $ |
re.S | re.DOTALL | 使 . 匹配包括换行在内的所有字符 |
re.A | re.ASCII | 让 \w , \W , \b , \B , \d , \D , \s 和 \S 只匹配ASCII,而不是Unicodere |
re.U | re.UNICODE | 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B |
re.X | re.VERBOSE | 忽略空白,使能够更加自由的编写表达式,如添加注释 |
a = re.compile(r"""\d + # the integral part \. # the decimal point \d * # some fractional digits""", re.X) b = re.compile(r"\d+\.\d*")
这些修饰符怎么用呢,可以翻看上面的常用函数,里面有个 flags 的参数,就是用来传递这些修饰符的,当然可以用 | 来使多条同时生效,如:re.I | re.M 就是忽略大小写同时开启多行模式。
此外,还可以通过另外一种方式来规定使用什么模式,(?aiLmsux-imsx:pattern) ,这里的模式只对括号内的有效。其中前面的是设置一个或多个模式,而 - 后面的是去除哪些模式。
一些常用的正则表达式
校验数字的表达式
-
数字:
^[0-9]*$
-
n位的数字:
^\d{n}$
-
至少n位的数字:
^\d{n,}$
-
m-n位的数字:
^\d{m,n}$
-
零和非零开头的数字:
^(0|[1-9][0-9]*)$ *
-
非零开头的最多带两位小数的数字:`^(1-9)+(.[0-9]{1,2})?$`
-
带1-2位小数的正数或负数:
^(\-)?\d+(\.\d{1,2})?$
-
正数、负数、和小数:
^(\-|\+)?\d+(\.\d+)?$
-
有两位小数的正实数:
^[0-9]+(.[0-9]{2})?$
-
有1~3位小数的正实数:
^[0-9]+(.[0-9]{1,3})?$
-
非零的正整数:
^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
* -
非零的负整数:`^-1-90-9"$ 或 ^-[1-9]\d$`
-
非负整数:`^\d+$ 或 ^[1-9]\d|0$`
-
非正整数:
^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$*
-
非负浮点数:`^\d+(.\d+)?$ 或 ^[1-9]\d.\d|0.\d[1-9]\d|0?.0+|0$`
-
非正浮点数:`^((-\d+(.\d+)?)|(0+(.0+)?))$ 或 ^(-([1-9]\d.\d|0.\d[1-9]\d))|0?.0+|0$`
-
正浮点数:`^[1-9]\d.\d|0.\d[1-9]\d$ 或 ^(([0-9]+.[0-9]1-9)|([0-9]1-9.[0-9]+)|([0-9]1-9))$`
-
负浮点数:`^-([1-9]\d.\d|0.\d[1-9]\d)$ 或 ^(-(([0-9]+.[0-9]1-9)|([0-9]1-9.[0-9]+)|([0-9]1-9)))$`
-
浮点数:`^(-?\d+)(.\d+)?$ 或 ^-?([1-9]\d.\d|0.\d[1-9]\d*|0?.0+|0)$`
校验字符的表达式
-
汉字:
^[\u4e00-\u9fa5]{0,}$
-
英文和数字:
^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
-
长度为3-20的所有字符:
^.{3,20}$
-
由26个英文字母组成的字符串:
^[A-Za-z]+$
-
由26个大写英文字母组成的字符串:
^[A-Z]+$
-
由26个小写英文字母组成的字符串:
^[a-z]+$
-
由数字和26个英文字母组成的字符串:
^[A-Za-z0-9]+$
-
由数字、26个英文字母或者下划线组成的字符串:
^\w+$
或^\w{3,20}$
-
中文、英文、数字包括下划线:
^[\u4E00-\u9FA5A-Za-z0-9_]+$
-
中文、英文、数字但不包括下划线等符号:
^[\u4E00-\u9FA5A-Za-z0-9]+$
或^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
-
可以输入含有^%&',;=?$"等字符:
[^%&',;=?$\x22]+
-
禁止输入含有~的字符:
[^~\x22]+
特殊需求表达式
-
Email地址:
^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
-
域名:
[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
-
InternetURL:
[a-zA-z]+://[^\s]*或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
-
手机号码:
^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
-
电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):
^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
-
国内电话号码(0511-4405222、021-87888822):
\d{3}-\d{8}|\d{4}-\d{7}
-
身份证号(15位、18位数字):
^\d{15}|\d{18}$
-
短身份证号码(数字、字母x结尾):
^([0-9]){7,18}(x|X)?$
或^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
-
帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):
^[a-zA-Z][a-zA-Z0-9_]{4,15}$
-
密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):
^[a-zA-Z]\w{5,17}$
-
强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):
^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
-
日期格式:
^\d{4}-\d{1,2}-\d{1,2}
-
一年的12个月(01~09和1~12):
^(0?[1-9]|1[0-2])$
-
一个月的31天(01~09和1~31):
^((0?[1-9])|((1|2)[0-9])|30|31)$
-
钱的输入格式:
-
有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":
^[1-9][0-9]*$*
-
这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:`^(0|1-9)$`
-
一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:
^(0|-?[1-9][0-9]*)$
-
这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:
^[0-9]+(.[0-9]+)?$
-
必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:
^[0-9]+(.[0-9]{2})?$
-
这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:
^[0-9]+(.[0-9]{1,2})?$
-
这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:
^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$*
-
1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:`^([0-9]+|[0-9]{1,3}(,[0-9]{3}))(.[0-9]{1,2})?$`
-
-
备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
-
xml文件:
^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]
-
中文字符的正则表达式:
[\u4e00-\u9fa5]
-
双字节字符:
[^\x00-\xff]
(包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)) -
空白行的正则表达式:\n\s\r (可以用来删除空白行)
-
-
HTML标记的正则表达式:
<(\S*?)[^>]*>.*?</\1>|<.*? />
(网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力) -
首尾空白字符的正则表达式:
^\s*|\s*$
或(^\s*)|(\s*$)
(可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
-
-
腾讯QQ号:
[1-9][0-9]{4,}
(腾讯QQ号从10000开始) -
中国邮政编码:
[1-9]\d{5}(?!\d)
(中国邮政编码为6位数字) -
IP地址:
\d+\.\d+\.\d+\.\d+
(提取IP地址时有用) 34 IP地址:((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))