python3.5 - re模块

python标准库--re(python3.5)

基础知识

正则表达式的基础知识,推荐学习正则表达式30分钟入门教程

正则表达式中使用"\"加某些字符表示特殊序列,或者将具有特殊意义的字符(元字符)按照其文本意义使用(转义)。在python字符串中"\"的具有相同的作用。这种冲突会导致某些正则表达式中某些字符的表示形式比较令人费解。

举例说明:如果需要在正则表达式中匹配"\",那么应该使用"\\"对其进行转义;在python字符串中也使用"\\"转义表示"\";那么为了在正则表达式中匹配"\"需要写成"\\\\"。我们要分别消除"\"在python字符串中和正则表达式中的特殊含义。

为了解决这个问题,建议使用python的raw字符串表示正则表达式。在raw字符串中,""不被当作特殊字符使用。如r'\n'表示两个字符'\'和'n',而不是换行。可以直接用r'\\'去匹配"\"

在交互模式下更直观的看下,由结果可知r'\\'等价于'\\\\'


>>> r'\\'
'\\\\'

re模块

flags

re模块的一些函数中将flags作为可选参数,下面列出了常用的几个flag, 它们实际对应的是二进制数,可以通过位或将他们组合使用。flags可能改变正则表达时的行为:

1. re.A re.ASCII: 将\w, \W, \b, \B, \d, \D的匹配范围限定在ASCII码中(默认是Unicode)
2. re.I re.IGNORECASE: 匹配中大小写不敏感
3. re.L re.LOCALE: 根据当前环境确定\w, \W, \b, \B的匹配(不稳定,不建议使用)
3. re.M  re.MULTILINE: "^"匹配字符串开始以及"\n"之后;"$"匹配"\n"之前以及字符串末尾。通常称为多行模式
4. re.S re.DOTALL: "."匹配任意字符,包括换行符。通常称为单行模式
5. re.U re.UNICODE: 默认

如果要同时使用单行模式和多行模式,只需要将函数的可选参数flags设置为re.M | re.S即可。

re.search

re.search(pattern, string, flags=0)
从头扫描字符串string,找到与正则表达式pattern的第一个匹配(可以是空字符串),并返回一个对应的match对象。如果没有匹配返回None.

re.match

re.match(pattern, string, flags=0)
在字符串开头匹配pattern,如果匹配成功(可以是空字符串)返回对应的match对象,否则返回None。

即使是在多行模式下,re.mathch也只在字符串开头匹配,而不是每一行的开头。

re.fullmatch

re.fullmatch(pattern, string, flags=0)
string是否整个和pattern匹配,如果是返回对应的match对象,否则返回None。(python3.4)


>>> re.match(r'.*?', 'hello')
<_sre.SRE_Match object; span=(0, 0), match=''>

>>> re.fullmatch(r'.*?', 'hello')
<_sre.SRE_Match object; span=(0, 5), match='hello'>

re.split

re.split(pattern, string, maxsplit=0, flags=0)

  1. 用pattern分割字符串,返回列表。
  2. 如果没有任何匹配,则返回含一个元素的列表。
  3. 如果pattern使用了"()",那么捕获到的组的内容将作为结果的一部分返回。
  4. 如果参数maxsplit的值不是0,那么最对分割maxsplit次;字符串剩余的部分作为列表的最后一个元素返回。

>>> re.split('b', '...words, words...')
['...words, words...']
>>> re.split('\W+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split('(\W+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split('\W+', 'Words, words, words.', 1)
['Words', 'words, words.']

上面示例第一个结果,列表的最后一个元素为空字符串。可以这样理解:一个pattern匹配必定将字符串分为两部分,pattern之后如果没有字符了,那么就是""。同理,如果pattern第一个匹配在字符串开头,那么结果列表的第一个元素为空字符串:


>>> re.split('(\W+)', '...words, words...')
['', '...', 'words', ', ', 'words', '...', '']
>>> re.split('\W+', '...words, words...')
['', 'words', 'words', '']

现在(python3.5)split()还不支持用空字符串匹配分割字符串。例如:


>>> re.split('x*', 'axbc')
['a', 'bc']

虽然'x*'可以匹配‘a’之前、‘b’和‘c’之间、‘c’之后的0个‘x’,目前的版本中这些匹配被忽略了。split函数的正确行为(上例返回['', 'a', 'b', 'c', ''])将会在python的未来版本中实现。

目前,只能匹配空字符串的模式不能实现字符串分割,在python3.5中将会抛出ValueError 异常:


>>>
>>> re.split("^$", "foo\n\nbar\n", flags=re.M)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  ...
ValueError: split() requires a non-empty pattern match.

3.1版本的改变: 添加可选参数flags

re.findall

re.findall(pattern, string, flags=0)

从左到右扫描字符串,按顺序将所有匹配到的字符串放到一个列表中并返回。
1. 如果在pattern中有一个或多个组,返回捕捉到的组的列表;
2. 如果有多个组,列表中的元素为元组;
3. 结果中包含空字符串匹配;如果空匹配和另一个匹配相连,那么该空匹配忽略。


>>> re.findall('\W+', '...words+words--')
['...', '+', '--']
>>> re.findall('(\W+)', '...words+words--')
['...', '+', '--']
>>> re.findall('(\W)\W+', '...words+words--')
['.', '-']
>>> re.findall('(\W)(\W+)', '...words+words--')
[('.', '..'), ('-', '-')]
>>> re.findall('(\W)?', '.w--')
['.', '', '-', '-', '']

re.finditer

re.finditer(pattern, string, flags=0)

返回一个迭代器。匹配规则和findall相同。

re.sub

re.sub(pattern, repl, string, count=0, flags=0)

用repl替代pattern在字符串string中的所有匹配项,返回替换后的字符串。如果pattern没有任何匹配,返回原字符串。

repl可以是一个字符串或者函数。如果repl是字符串(可以是raw-string或者普通的字符串),字符串中可以包含转义字符以及pattern捕获的组的后向引用;保留含义不明的转义字符不处理。
关于后向引用:
1. \g<name> 引用pattern中用(?P<name>...)语法定义的组匹配搭配的子串
2. \g<number> 对应组号的引用,\g<2> 等价于\2
3. \g<0> 引用正则表达式匹配到的整个子串

示例:


>>> re.sub(r'def\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(\s*\):',
...        r'static PyObject*\npy_\1(void)\n{',
...        'def myfunc():')
'static PyObject*\npy_myfunc(void)\n{'

如果repl是函数,函数必须就有有个参数,且参数类型为<class '_sre.SRE_Match'>,返回一个字符串。所有匹配都会调用函数,匹配得到的match对象作为函数的参数,函数返回值作为替换字符串。示例:


>>> def dashrepl(matchobj):
...     if matchobj.group(0) == '-': return ' '
...     else: return '-'
>>> re.sub('-{1,2}', dashrepl, 'pro----gram-files')
'pro--gram files'
>>> re.sub(r'\sAND\s', ' & ', 'Baked Beans And Spam', flags=re.IGNORECASE)
'Baked Beans & Spam'

可选参数count表示最多替换的匹配项,它必须是一个非负整数,0或者不给表示替换所有匹配项。当空匹配不和之前的匹配相接时,空匹配同样会被替换,例如:

>>> re.findall('x*', "axbc")
['', 'x', '', '', '']
# 分别对应的位置为:'a'之前,‘x’, 'x'和'b'之间,'b'和'c'之间,'c'之后
>>> re.sub('x*', '-', 'axbc')
'-a-b-c-'

3.1版本改变: 添加可选参数flags
3.5版本改变: 无匹配的组用空字符串替代

re.subn

re.subn(pattern, repl, string, count=0, flags=0)

实现和re.sub()相同,返回值不同,返回一个元组(new_string, number_of_subs_made)。

3.1版本改变: 添加可选参数flags
3.5版本改变: 无匹配的组用空字符串替代

re.escape

re.escape(string)
转义(escape)非ASCII字母、数字、‘_’之外的所有字符。如果想要匹配的字符串中包含正则表达式元字符,可以用此函数转换。


>>> re.escape('.w--')
'\\.w\\-\\-'

3.3版本改变: '_'不再被转义。

re.purge

re.purge()
清除正则表达式缓存

re.compile

re.compile(pattern, flags=0)

编译正则表达式pattern,返回一个正则表达式对象。


prog = re.compile(pattern)
result = prog.match(string)

# 等价于

result = re.match(pattern, string)

使用re.compile()可以保存正则表达时对象,如果在一个程序中多次使用同一表达式,这种方式比较高效。

最新传递给re.compile()及模块级别的匹配函数的正则表达式的编译版本都会被缓存,所以对于仅有处理几个正则表达是的程序来说不用纠结是否需要编译正则表达式的问题。

正则表达式对象

正则表达式对象指re.compile(pattern)返回的对象:


>>> r = re.compile('\W')
>>> r
re.compile('\\W')
>>> type(r)
<class '_sre.SRE_Pattern'>
>>> dir(r)
# 结果省略掉特殊属性
[...,
 'findall',
 'finditer',
 'flags',
 'fullmatch',
 'groupindex',
 'groups',
 'match',
 'pattern',
 'scanner',
 'search',
 'split',
 'sub',
 'subn']

可以看到,正则表达式对象的方法属性大都有模块级别的函数对应,并且功能相同。区别在于,正则表达式对象方法不需要在编译pattern,而且某些方法可以调整匹配开始的及结束的位置。

split, sub

以下几个方法功能及用法完全和模块对应方法相同:

regex.split(string, maxsplit=0)

regex.sub(repl, string, count=0)

regex.subn(repl, string, count=0)

匹配类方法(可变参数pos, endpos)

regex.search(string[, pos[, endpos]])

可选参数:
pos: 从索引pos处开始对string进行匹配搜索,默认为0.它不等同于字符串的分片操作,""只锚定字符串真正的开始(对行模式下还包括每行的开始),而不是通过pos确定的搜索开始的位置。


>>> pattern = re.compile('^a')
>>> pattern.search('aa', 1)
>>> pattern.search('aa')
<_sre.SRE_Match object; span=(0, 1), match='a'>

endpos:字符串停止搜索的位置,它假定字符串的末尾是endpos处,regex.search(string, 0, 50) 等价于 regex.search(string[:50], 0)。如果endpos<pos,返回None。


>>> pattern = re.compile('a$')
>>> pattern.search("ab",0, 1)
<_sre.SRE_Match object; span=(0, 1), match='a'>
>>> pattern.search("ab")
>>> 

下面方法中的可选参数pos和endpos和本方法中的含义都一样,不再说明:


1、regex.match(string[, pos[, endpos]])
# 示例
>>> pattern = re.compile("o")
>>> pattern.match("dog")      # No match as "o" is not at the start of "dog".
>>> pattern.match("dog", 1)   # Match as "o" is the 2nd character of "dog".
<_sre.SRE_Match object; span=(1, 2), match='o'>

2、regex.fullmatch(string[, pos[, endpos]])
>>> pattern = re.compile("o[gh]")
>>> pattern.fullmatch("dog")      # No match as "o" is not at the start of "dog".
>>> pattern.fullmatch("ogre")     # No match as not the full string matches.
>>> pattern.fullmatch("doggie", 1, 3)   # Matches within given limits.
<_sre.SRE_Match object; span=(1, 3), match='og'>
# python3.4新加

3、regex.findall(string[, pos[, endpos]])

4、regex.finditer(string[, pos[, endpos]])

数据属性


>>> regex = re.compile(r'(\d+)(?P<name>\w+)')
>>> 
>>> regex.flags # 规定模式匹配行为
32  #  re.U re.UNICODE
>>> regex.groups    # pattern中捕获的组的个数
2
>>> regex.groupindex  # 字典,记录命名分组的名称和组号的对应
mappingproxy({'name': 2})
>>> regex.pattern   # RE对象对应的pattern字符串
'(\\d+)(?P<name>\\w+)'
>>>

Match Objects

所有match对象的布尔值为Ture。python re模块需要返回match对象的函数(如match()、search()),如果匹配不成功返回None。可以通过if语句测试匹配是否成功。


match = re.search(pattern, string)
if match:
    process(match)

match对象支持以下属性:

match.expand

match.expand(template)

template是一个字符串,字符串中可以包含转义字符以及捕获的组的后向引用,expand()返回后向引用替换后的字符串。实现类似于sub()函数的repl的字符串形式。


>>> m = re.search("(b)(c)?", "cba")
>>> m.expand(r'ab\1')   # \1 -->b
'abb'
>>> m.expand(r'ab\2')   # \2 --> ''
'ab'
>>> m.expand(r'ab\n')   # raw string转义字符串直接按照普通转义解析,而不是看着分割的单个字符
'ab\n'
>>> m.expand('ab\n')
'ab\n'

python3.5更改: 未匹配的组用空字符串替代

match.group

match.group([group1, ...])

参数可以是组号或者命名分组的组名。

返回pattern中一个或多个分组的匹配:
1. 如果只有一个参数,返回一个字符串;
2. 如果有多个参数,返回一个元组;
3. 不传参,group1默认为0,返回整个匹配的子串。
4. 如果组号为负或者比pattern中实际存在的组号大,抛出IndexError 异常;
5. 如果groupN存在于pattern中,但是实际匹配没匹配到,其对应的结果为None;
6. 如果一个分组被匹配到多次,返回最后一次匹配。


>>> m = re.search("(b)(c)?", "cba")
>>> >>> m.group()
'b'
>>> m.group(1, 2)
('b', None)
>>> m = re.match(r"(..)+", "a1b2c3")  # Matches 3 times.
>>> m.group(1)                        # Returns only the last match.
'c3'

match.groups

match.groups(default=None)
返回pattern中所有分组组的匹配,数据类型为元组。

默认参数用于没有匹配的分组,默认为None.


>>> m = re.search("(b)(c)?", "cba")
>>> m.groups()  # 第二个分组默认是None
('b', None)
>>> m.groups('')    # 现在第二个分组默认是''
('b', '')

match.groupdict

match.groupdict(default=None)

返回有个包含命名分组名到其匹配的对应的字典。默认参数用法和groups()相同。


>>> m = re.search("(b)(?P<name>c)?", "cba")
>>> m.groupdict()
{'name': None}

match.start

match.end

match.start([group])
match.end([group])

分别返回分组匹配的子串开始和结束的位置。参数group默认为0(匹配到的整个子字符串)。如果一个分组存在但是对整个匹配没有贡献,返回-1.

对于match对象m,可以通过m.string[m.start(g):m.end(g)]获得分组g匹配到的子字符串, 作用等价于m.group(g)


>>> email = "tony@tiremove_thisger.net"
>>> m = re.search("remove_this", email)
>>> email[:m.start()] + email[m.end():]
'tony@tiger.net'

如果分组匹配到空字符串,m.start(group)和m.end(group)相等。


>>> m = re.search('b(c?)', 'cba')   # 分组肯定要被匹配到,匹配到'a'或者''
>>> m.groups()
('',)
>>> m.start(1)
2
>>> m.end(1)
2
>>> m = re.search('b(c)?', 'cba') # '(c)?'分组可以被匹配到,也可以不被匹配到
>>> m.start(1)
-1
>>> m.end(1)
-1

match.span

match.span([group])

对match对象m,返回值为一个二元组(m.start(group), m.end(group))。具体返回值参考start()和end()方法。

数据属性


>>> m = re.search("(b)(?P<name>c)?", "cba")
>>> m.pos   # RE对象的匹配类方法传入的pos参数,字符串匹配查找开始的位置(包含)
0
>>> m.endpos    # RE对象的匹配类方法传入的endpos参数,字符串匹配查找结束的位置(不包含)
3
>>> m.lastindex     # 最后一个匹配到的分组的索引;如果没有分组被匹配到,返回None
1
>>> m.lastgroup     # 最后一个匹配到的命名分组的名称;如果没有分组被匹配到或者分组没有被命名,返回None
>>> m.re            # 该match对象对应的正则表达式对象
>>> re.compile('(b)(?P<name>c)?')
>>> m.string    # The string passed to match() or search().
'cba'

以上内容大部分翻译自python帮助文档。文档最后有个分词器的示例,个人觉得非常具有参考价值放到这里供自己查看:


import collections
import re

Token = collections.namedtuple('Token', ['typ', 'value', 'line', 'column'])

def tokenize(code):
    keywords = {'IF', 'THEN', 'ENDIF', 'FOR', 'NEXT', 'GOSUB', 'RETURN'}
    token_specification = [
        ('NUMBER',  r'\d+(\.\d*)?'),  # Integer or decimal number
        ('ASSIGN',  r':='),           # Assignment operator
        ('END',     r';'),            # Statement terminator
        ('ID',      r'[A-Za-z]+'),    # Identifiers
        ('OP',      r'[+\-*/]'),      # Arithmetic operators
        ('NEWLINE', r'\n'),           # Line endings
        ('SKIP',    r'[ \t]+'),       # Skip over spaces and tabs
        ('MISMATCH',r'.'),            # Any other character
    ]
    tok_regex = '|'.join('(?P<%s>%s)' % pair for pair in token_specification)
    line_num = 1
    line_start = 0
    for mo in re.finditer(tok_regex, code):
        kind = mo.lastgroup
        value = mo.group(kind)
        if kind == 'NEWLINE':
            line_start = mo.end()
            line_num += 1
        elif kind == 'SKIP':
            pass
        elif kind == 'MISMATCH':
            raise RuntimeError('%r unexpected on line %d' % (value, line_num))
        else:
            if kind == 'ID' and value in keywords:
                kind = value
            column = mo.start() - line_start
            yield Token(kind, value, line_num, column)

statements = '''
    IF quantity THEN
        total := total + price * quantity;
        tax := price * 0.05;
    ENDIF;
'''

for token in tokenize(statements):
    print(token)

posted @ 2017-12-28 11:08  Wadirum  阅读(2485)  评论(1编辑  收藏  举报