Python::re 模块 -- 在Python中使用正则表达式

前言

这篇文章,并非对正则表达式的介绍,而是对Python中如何结合re模块使用正则表达式的介绍。文章的侧重点是如何使用re模块在Python语言中使用正则表达式,对于Python表达式的语法和详细的介绍,能够参考别的文章,这篇文章只是给出一些经常使用的正则表达式语法,以方便对re模块的使用进行讲解。html

对正则表达式的介绍,能够参看这两篇文章:python

正则表达式30分钟入门教程正则表达式

正则表达式之道编程

注意:实验环境为 Python 3.4.3缓存

正则表达式简介

正则表达式,又称正规表示式、正规表示法、正规表达式、规则表达式、常规表示法(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。在不少文本编辑器里,正则表达式一般被用来检索、替换那些符合某个模式的文本。编辑器

许多程序设计语言都支持利用正则表达式进行字符串操做。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的。正则表达式一般缩写成“regex”,单数有regexp、regex,复数有regexps、regexes、regexen。函数

最初的正则表达式出现于理论计算机科学的自动控制理论和形式化语言理论中。在这些领域中有对计算(自动控制)的模型和对形式化语言描述与分类的研究。 1940年,Warren McCulloch与Walter Pitts将神经系统中的神经元描述成小而简单的自动控制元。 1950年代,数学家斯蒂芬·科尔·克莱尼利用称之为“正则集合”的数学符号来描述此模型。肯·汤普逊将此符号系统引入编辑器QED,而后是Unix上的编辑器ed,并最终引入grep。自此,正则表达式被普遍地使用于各类Unix或者相似Unix的工具,例如Perl。工具

Python提供了对正则表达式的支持,它内嵌在Python中,经过Python的re模块提供。学习

re模块提供了相似于Perl的正则表达式语法。this

经过使用正则表达式,咱们能够制定须要匹配的字符串的特定格式,而后从须要处理的字符串中提取咱们感兴趣的字符串。Python中的re模块也提供了像sub()subn()split()这些方法来实现经过正则表达式来灵活地进行文本的替换和分割。

在Python中,正则表达式会被编译成一系列的字节码,而后由经过C编写的正则表达式引擎进行执行。

初尝正则表达式

下面,咱们先看看正则表达是什么,了解下正则表达式是怎么处理字符串问题的。若是你之前没有接触过正则表达式,是正则表达式小白,那么能够初略的熟悉下这节内容,内心有个大概的了解。在接下来的几节中,咱们将学习到正则表达式的一些知识,等学习完了有关正则的知识,能够再回过头来看看这些例子,加深下理解。

下面,咱们简单的看看在Python中使用正则表达式的例子:

简答的字符串匹配操做

>>> import re
 >>> p = re.compile(r'ab*')
 >>> m = p.search('abbbba')
 >>> m.group()
 'abbbb'
>>> import re
 >>> p = re.compile(r'(ab)*')
 >>> m = p.search('abababa')
 >>> m.group(0)
 'ababab'

找出字符串中的数字

>>> import re
 >>> p = re.compile(r'\d+')
 >>> m = p.search('this year is 2015')
 >>> m.group()
 '2015'

找出字符串中的字符

>>> import re
 >>> p = re.compile(r'\w+')
 >>> m = p.search('this year is 2015')
 >>> m.group()
 'this'

匹配邮箱地址

>>> p = re.compile(r'[\w\d]+@\w+\.com')
 >>> m = p.search('mymail@mail.com')
 >>> m.group()
 'mymail@mail.com'

匹配IP地址

>>> p = re.compile(r'(\d{1,3}\.){3}\d{1,3}')
 >>> m = p.search('ip address is : 192.168.1.1 not 192.1.1')
 >>> m.group()
 '192.168.1.1'

正则表达式中的元字符

大多数的字符在进行正则表达式匹配的时候,会简单的进行一对一的匹配,好比,普通的字符串test将会精确地匹配到test。可是,则正则表达式中,有一些字符具备特殊的用于,它们在匹配的时候不会精确匹配到对应的字符。这些具备特殊用于的字符,在正则表达式中被称为元字符

元字符不会匹配自身,相反,单个的元字符能够匹配不一样内容和长度的字符串,或者影响正则表达式的执行。正是因为元字符的存在,才使得正则表达式如此灵活和强大。

在正则表达式中,元字符由一些这些字符组成,这些字符在使用的时候再也不表示它们原来的做用,若是要使用它们原来的意思,则须要使用反斜杠转义:

. ^ $ * + ? { } [ ] \ | ( )

下面,咱们将会正则表达式中的这些元字符进行解释,可是在这以前,咱们须要先介绍下在Python中使用反斜杠进行转义时碰到的问题。

转义

在Python的字符串中,一些特殊的转义字符具备特殊的意义。好比,在字符串中的'\n'表示的是换行,'\b'表示的是回退键(Backspace),等等。详细的转义字符表能够查看这里。咱们能够看到,对于转义字符,咱们须要在前面加上一个反斜杠来表示。那么,若是咱们须要在字符串中使用一个反斜杠,而不是把这个反斜杠用于转义的时候,咱们须要使用两个反斜杠来表示'\\'

因此,若是咱们但愿在字符串中包含'\n',可是咱们不但愿进行转义,那么能够在前面的反斜杠以前添加上一个反斜杠来进行跳脱(escape)。因此'\\n'就变成了单纯的'\n'字符了,而不是一个换行符。反斜杠的做用就是用来对转义字符进行跳脱。除了使用反斜杠来进行转义字符的跳脱,也能够用Python的raw字符串来达到一样的效果,python的raw字符串能够在字符串前面加上一个字符r来表示:r'raw string',这个字符串就是一个raw字符串。在raw字符串中,全部的转义都将失去做用,因此r'\n'将表示两个字符'\''n',而不是一个换行符。

前面咱们讲到了Python中的转义字符,那转义字符对正则表达式有什么影响呢?

咱们知道,正则表达式实际上就是一个字符串,和别的字符串的区别就是它们能够被正则表达式引擎解析和执行。因此,为了使正则表达式能够正常的工做,咱们须要保证传递给正则表达式引擎的正则表达式字符串的语法是正确的,而且是咱们指望的样子。可是,因为Python中的转义字符的存在,而且,在正则表达式的元字符中也包含了反斜杠,因此咱们须要正确地处理正则表达式中的反斜杠。

若是咱们须要在正则表达式中使用反斜杠原本的意思,而不是做为元字符使用,那么咱们须要传递给正则表达式引擎的就是一个'\\'表示的字符串。其中第一个反斜杠用于对后面的反斜杠进行跳脱,使得后面的反斜杠失去元字符的做用。如今,咱们须要两个反斜杠'\\'传递给正则表达式引擎,那么,咱们须要用'\\\\'这样的Python字符串来表示。为何呢?由于,咱们是在Python中使用正则表达式,而且正则表达式是使用Python中的字符串表示的,那么考虑到Python中转义字符的问题,咱们若是须要一个反斜杠,那么须要在字符串中这样表示'\\',若是须要两个,那么就要这样表示'\\\\'。这样,咱们的Python字符串就会产生两个反斜杠组成的字符串了。

而对于正则表达式的一些特殊的元字符,好比'\d',若是用Python的字符串表示,则须要表示成'\\d'来保证传递给正则表达式引擎的是'\d'

>>> print('\\')
 \
 >>> print('\\\\')
 \\
 >>> print('\\d')
 \d

若是正则表达式中包含了不少的反斜杠,这样会致使正则表达式变的复杂和难以理解,因此,咱们通常都用Python的raw字符串来表示一个正则表达式。由于在raw字符串中,反斜杠将不具备特殊用途,因此r'\\'表示'\\'r'\d'表示'\d'

>>> print(r'\\')
 \\
 >>> print(r'\d')
 \d

咱们在编写正则表达式的时候,都应该使用Python的raw字符串来表示。

元字符

介绍完了反斜杠的问题,下面,咱们介绍正则表达式中的元字符,是它们使得正则表达式如此灵活和强大的。

注意:这里只是介绍正则表达式中一些经常使用的元字符,详细的介绍能够参考Python标准库中的re模块

元字符[]用于表示一个字符类,一个字符类表示了一个但愿被匹配的字符组成的集合。字符类中的字符能够是以单个的字符出现,也能够是以字符区间的形式出现,字符区间是由两个字符组成,中间由一个连字符'-'分隔。如:[abc]能够匹配a、b、c中的任何一个字符,一样,也能够用[a-c]来达到一样的效果,[a-c]表示匹配a到c之间的任何一个字母。若是须要匹配任何一个小写字母,则能够这样写[a-z]

在元字符[]中的字符,将会失去特殊的做用,也就是说,若是在[]中包含了元字符,则这些元字符将再也不具备特使做用,而只是表示字符自身。这能够用来代替反斜杠来跳脱元字符:
[$]能够和\$达到相同的效果。

若是须要匹配再也不字符类中的字符,则能够对字符类进行取反,使用'^'符号来进行取反。将'^'符号放在字符类中的字符集合中的第一个,使得能够匹配不在这个字符集合中的字符。好比:[^a-z]表示匹配任何不是小写字母的字符。

>>> p = re.compile(r'[ab]')
 >>> m = p.search('ababaabaacdefg')
 >>> m.group()
 'a'

元字符\w能够匹配字母和数字。若是正则表达式是由字节表示的,则\w至关因而字符类[a-zA-Z0-9_]。若是正则表达式是一个字符串,则\w将会匹配全部在Unicode database中标记为文字(letters)的字符,Unicode database由模块unicodedata模块提供。在编译正则表达式的时候,能够经过指定 re.ASCII 选项来限制\w匹配的范围。

>>> p = re.compile(r'\w')
 >>> m = p.search('ababaabaacdefg')
 >>> m.group()
 'a'

元字符\d将会匹配数字,对于Unicode表示的正则表达式(str类型),将会匹配Unicode表示的十进制数字,包括[0-9],以及其余的数字字符。若是 re.ASCII 选项在编译的时候被指定,则只会匹配[0-9](因为 re.ASCII 选项会影响整个正则表达式,因此在须要匹配0到9组成的数字的时候,可使用[0-9])。对于8位字节(bytes类型)表示的正则表达式,则至关因而[0-9]

>>> p = re.compile(r'\d')
 >>> m = p.search('123')
 >>> m.group()
 '1'

元字符\D匹配任何不是Unicode数字的字符,这个元字符和\d的相反。若是指定了 re.ASCII 选项,则至关因而 [^0-9]

>>> p = re.compile(r'\D')
 >>> m = p.search('123abc')
 >>> m.group()
 'a'

元字符\s,在Unicode表示(str类型)的正则表达式中,用于匹配Unicode空白字符,包括[ \t\n\r\f\v],若是 re.ASCII 选项被指定,则只匹配[ \t\b\r\f\v]。对于8位字节(bytes)表示的正则表达式,则和[ \t\n\r\f\v]是等价的。

>>> p = re.compile(r'\s')
 >>> m = p.search('hello world')
 >>> m.group()
 ' '

元字符\S,匹配不是Unicode空白字符的任何字符。和\s相反。若是 re.ASCII 选项被指定,则和 [^ \t\n\r\f\v]等价。

>>> p = re.compile(r'\S')
 >>> m = p.search('hello world')
 >>> m.group()
 'h'

元字符\W匹配不是Unicode中的word字符的任何字符。和\w相反。若是指定了 re.ASCII 选项,则和 [^a-zA-Z0-9_]等价。

>>> p = re.compile(r'\W')
 >>> m = p.search('hello-world')
 >>> m.group()
 '-'

元字符 .能够匹配除了换行符外的任何一个字符。可是,若是在编译的时候指定了 re.DOTALL 选项,则能够匹配任何一个字符,包括换行符。

>>> p = re.compile(r'.')
 >>> m = p.search('hello')
 >>> m.group()
 'h'

元字符|表示操做。若是A和B都是正则表达式,则A|B表示会匹配A和B中的任何一个。因为|的优先级具备很低的优先级,因此当进行以下hello|world模式串进行匹配的时候,helloworld之间的任何一个匹配了,则这个正则表达式就匹配成功了,而不是做用于ow。若是须要在正则表达式中使用|做为普通的字符,则可使用反斜杠进行转义\|,或者使用字符类[|]

>>> p = re.compile(r'\d+|\w+')
 >>> m = p.search('a1b2c3d4')
 >>> m.group()
 'a1b2c3d4'

元字符^匹配一行的开头。除非指定了re.MULTILINE选项,不然会匹配字符串的开头,若是re.MULTILINE选项被指定,则会匹配字符串中换行符后面的位置。
若是须要在模式串中去掉元字符的特殊意义,则可使用反斜杠来转义\^,或者使用字符类[^]

>>> p = re.compile(r'^hello')
 >>> p.search('hello world')
 <_sre.SRE_Match object; span=(0, 5), match='hello'>
 >>> print(p.search('a word hello'))
 None

元字符$匹配一行的结尾,一行的结尾的定义是:一个字符串的结尾,当指定了re.MULTILNE选项后,会匹配换行符以前的位置。
若是须要去掉元字符的特殊意义,则可使用反斜杠进行转义\^,或者使用字符类[$]

>>> p = re.compile(r'd$')
 >>> p.search('hello world')
 <_sre.SRE_Match object; span=(10, 11), match='d'>
 >>> print(p.search('hello'))
 None

 >>> p = re.compile(r'\w+d$', re.M)
 >>> p.search('word\n Word').group()
 'word'

元字符\A\Z分别匹配字符串的开头和结尾,不会受到re.MULTILINE选项的影响。当没有指定re.MULTILINE选项的状况下,^相对因而\A,而$至关因而\Z。(为何要使用AZ做为匹配字符串开头和结尾的元字符,多是考虑到A是英文字符的第一个字母,而Z是英文字符的最后一个字母的缘故,这样相对容易记忆)。

元字符\b匹配单词的边界,这是一个零宽断言,匹配单词的边界(开头或结尾)。单词的定义是:一系列字母数字组成的序列,因此单词能够被空白符或者非字母数字分隔。

>>> p = re.compile(r'\bhello\b')
 >>> p.search('hello world').group()
 'hello'
 >>> print(p.search('helloworld'))
 None

元字符\B匹配不是单词边界的位置,这个元字符也是一个零宽断言,和元字符\b的意思相反。

重复匹配

正则表达式除了能够经过元字符匹配任意的字符之外,还具备重复的能力,能够指定匹配的次数。

元字符 *匹配重复前一个字符或分组0次或屡次,若是须要匹配'*'字符,则须要进行转义'\*'。因为Python的re模块中的正则表达式引擎是采用C编写的,因此重复次数的最大值被int类型的最大值限制,重复次数最大为20亿次。

Python中正则表达式引擎的重复匹配是贪婪的,匹配引擎会尽量多的进行匹配,直到不能匹配为止,而后匹配引擎会进行回溯,尝试更小的重复次数进行匹配,直到匹配上。因此对于字符串aaaaaaba*将会匹配aaaaaa,而不是匹配a

>>> p = re.compile(r'ca*t')
 >>> m = p.search('caaat')
 >>> m.group()
 'caaat'

元字符+,用于重复前一个字符或分组一次或屡次。元字符*+是有区别的,+要求前一个字符出现至少一次,而*并不要求前一个字符必定要出现。因此对于正则表达式ca*t,能够匹配ct,可是正则表达式ca+t将不能匹配ct

元字符?,用于重复前一个字符或分组一次或0次。正则表达式home-?brew能够匹配home-brewhomebrew

元字符{m, n},能够匹配前一个字符或分组m ~ n次。正则表达式a/{1,3}b将会匹配a/ba//ba///b。若是省略了m,则表示匹配0 ~ n次,若是省略了n,则表示至少匹配m次。所以,{0,}*是等价的,{0,1}?是等价的,{1,}+是等价的。

使用正则表达式

如今,咱们已经对正则表达式有了一些了解,也知道了正则表达式中元字符的做用。下面,咱们开始结合Python的re模块来学习使用这些正则表达式。

Python的re模块提供了一套接口来使用正则表达式引擎,经过re模块提供的接口,能够将正则表达式编程成正则表达式对象,而后进行须要的匹配操做。

编译正则表达式

正则表达式能够被编译成模式对象,而后调用模式对象的方法来进行字符串匹配操做,好比搜索、替换或者分割。

经过re模块的compile()函数能够将一个正则表达式编译成一个模式对象

re.compile(pattern, flags=0)
>>> import re
 >>> p = re.compile(r'a*')
 >>> p
 re.compile('a*')

传递给re.compile()函数一个可选的flags参数,能够控制编译后的对象在匹配的时候的行为。

flags 参数的值能够是如下的值:

re.A | re.ASCII
: 对\w\W\b\B\d\D\s\S产生影响,编译后的模式对象在进行匹配的时候,只会匹配ASCII字符,而不是Unicode字符。

这个选项只对Unicode模式串有效,对字节模式串无效。

re.I | re.IGNORECASE
: 在匹配的时候忽略大小写,在字符类和字符字面值进行匹配的时候会忽略大小写。进行大小写转换的时候,不会考虑当前的locale信息,除非指定了re.LOCALE选项。

>>> p = re.compile(r'[a-z]+', re.I)
    >>> m = p.search('abcdABCD')
    >>> m.group()
    abcdABCD

re.M | re.MULTILINE
: 默认,元字符^会匹配字符串的开始处,元字符$会匹配字符串的结束位置和字符串后面紧跟的换行符以前(若是存在这个换行符)。若是指定了这个选项,则^将会匹配字符串的开头和每一行的开始处,紧跟在每个换行符后面的位置。相似的,$会匹配字符串的最后和每一行的最后,在接下来的换行符的前面的位置。

>>> p = re.compile(r'(^hello$)\s(^hello$)\s(^hello$)\s')
    >>> m = p.search('hello\nhello\nhello\n')
    >>> print(m)
    None
    
    >>> p = re.compile(r'(^hello$)\s(^hello$)\s(^hello$)\s', re.M)
    >>> m = p.search('\nhello\nhello\nhello\n')
    >>> m.groups()
    ('hello', 'hello', 'hello')

re.S | re.DOTALL
: 使得.元字符能够匹配任何字符,包括换行符。

re.X | re.VERBOSE
: 这个选项容许编写可读性更强的正则表达式代码,并进行自由的格式化操做。当这个选项被指定之后,在正则表达式之间的空格符会被忽略,除非这个空格符是在一个字符类中[ ],或者在空格前使用一个反斜杠\。这个选项容许对正则表达式进行缩进,使得正则表达式的代码更加格式化,更加清晰。而且能够在正则表达式的代码中使用注释,这些注释会被正则表达式引擎在处理的时候忽略。注释以'#'字符开头。因此若是须要在正则表达式中使用'#'符号,须要在前面添加反斜杠'\#'或者将它放在[]中,[#]

charref = re.compile(r"""
    &[#]                # Start of a numeric entity reference
    (
         0[0-7]+         # Octal form
       | [0-9]+          # Decimal form
       | x[0-9a-fA-F]+   # Hexadecimal form
    )
    ;                   # Trailing semicolon
    """, re.VERBOSE)

若是没有指定re.**VERBOSE**选项,则至关于:

    charref = re.compile("&#(0[0-7]+"
                 "|[0-9]+"
                 "|x[0-9a-fA-F]+);")

进行匹配

一旦咱们经过re.compile()编译后产生了一个正则表达式对象,咱们就能够经过这个正则表达式对象进行匹配操做了。re模块的正则表达式对象包含了一些方法,能够进行须要的匹配操做。

方法 描述
match() 从字符串开头位置开始匹配
search() 对字符串的任意位置进行匹配
findall() 返回字符串中全部匹配的子串组成的列表
finditer() 返回一个包含了全部的匹配对象的迭代器

若是没有匹配到,则match()search()方法返回None,若是匹配成功,则返回一个匹配对象。返回的匹配对象包含了匹配信息:包括匹配的子串的开始位置和结束位置等信息。

>>> p = re.compile(r'[a-z]+')
 >>> print(p.match('hello123'))
 <_sre.SRE_Match object; span=(0, 5), match='hello'>
 >>> print(p.match('123hello'))
 None
 >>> print(p.search('123hello'))
 <_sre.SRE_Match object; span=(3, 8), match='hello'>

 >>> p = re.compile(r'\d+')
 >>> p.findall('a number A is 12, and a number B is 8, A + B = 20')
 ['12', '8', '20']

 >>> i = p.finditer('a number A is 12, and a number B is 8, A + B = 20')
 >>> for item in i:
    print(item.group())
    
 12
 8
 20

除了使用正则表达式对象中的方法,re模块也提供了一些模块级别的同名函数来进行匹配操做。包括:match(),search(),findall()和sub()等函数。这些函数接受和正则表达式对象中的方法相似的参数,除了第一个参数是一个正则表达式字符串之外,后面的参数和正则表达式对象中的方法接受的参数是一致的。而且这些函数的返回值也是相同的。

>>> re.search(r'\d+', 'a number A is 12')
 <_sre.SRE_Match object; span=(14, 16), match='12'>

 >>> re.findall(r'\d+', 'a number A is 12, and a number B is 8, A + B = 20')
 ['12', '8', '20']

实际上,这些函数在底层会对正则表达式字符串进行编译,产生一个正则表达式对象,而后调用正则表达式中同名的方法来实现。而且,这些函数会将正则表达式对象缓存起来,因此下次再次使用相同的正则表达式的时候,就能够不用再次对这个正则表达式进行编译了。

对于使用模块级别的函数仍是使用正则表达式对象中的方法来进行匹配,须要依据不一样的使用场景来权衡。若是是在循环中,则使用编译好的正则表达式对象会相对高效,而在循环外,因为缓存的存在,这两种方式区别不大。

咱们获得匹配对象之后,就能够经过匹配对象中的方法对匹配的信息进行进一步的处理了。

匹配对象中重要的方法以下:

方法 描述
group() 返回正则表达式匹配到的字符串
start() 返回匹配的起始位置
end() 返回匹配的结束位置
span() 返回一个包含匹配的起始位置和结束位置的元组(start, end)
>>> p = re.compile(r'[a-z]+')
 >>> m = p.search('123hello')
 >>> m
 <_sre.SRE_Match object; span=(3, 8), match='hello'>
 >>> m.group()
 'hello'
 >>> m.start()
 3
 >>> m.end()
 8
 >>> m.span()
 (3, 8)

正则表达式不是万能的

虽然正则表达式很强大,能够灵活地处理不少字符串处理得工做。可是,因为正则表达式语言相对严格和小巧,因此一些字符串处理工做,正则表达式并不能处理的很好。

在一些状况下,并不适合使用正则表达式,相反,使用Python的字符串中的方法,可能更加合适。好比,若是你须要对一个字符串进行匹配,而匹配的模式串是一个固定的字符串的话,那么,使用Python中的字符串中的一些方法,如replace()方法,会比使用正则表达式来的更加高效和简单。

所以,在咱们使用Python的re模块中的正则表达式来进行字符串的处理工做以前,咱们不妨考虑下,是否可使用字符串中简单高效的方法来解决。

后续

这篇文章没有提到正则表达式中的分组(group),在re模块中也支持分组,而且添加了Python的一些特性,后续再来介绍下re模块中的分组的使用,以及一些别的特性。


正则表达式 - 维基百科

https://docs.python.org/3/howto/regex.html

相关文章
相关标签/搜索