re —正则表达式运算

源代码: Lib/re.py


该模块提供与 Perl 中类似的正则表达式匹配操作。

模式和要搜索的字符串都可以是 Unicode 字符串(str)以及 8 位字符串(bytes)。但是,不能将 Unicode 字符串和 8 位字符串混合使用:也就是说,您不能将 Unicode 字符串与字节模式匹配,反之亦然;类似地,当要求替换时,替换字符串必须与模式和搜索字符串具有相同的类型。

正则表达式使用反斜杠字符('\')表示特殊形式或允许使用特殊字符而无需调用特殊含义。这与 Python 在字符串 Literals 中出于相同目的使用相同字符的目的相冲突。例如,要匹配 Literals 反斜杠,可能必须写'\\\\'作为模式字符串,因为正则表达式必须为\\,并且每个反斜杠必须在常规 Python 字符串 Literals 中表示为\\。另外,请注意,Python 在字符串 Literals 中使用反斜杠的任何无效转义序列现在都会生成DeprecationWarning,将来将变为SyntaxError。即使它是正则表达式的有效转义序列,也会发生此行为。

解决方案是将 Python 的原始字符串表示法用于正则表达式模式。反斜杠不会以任何特殊方式处理以'r'开头的字符串 Literals。因此r"\n"是一个包含'\''n'的两个字符的字符串,而"\n"是一个包含换行符的一个单字符的字符串。通常,模式将使用此原始字符串表示法在 Python 代码中表示。

重要的是要注意,大多数正则表达式操作都可以在编译正则表达式上用作模块级函数和方法。这些函数是快捷方式,不需要您首先编译一个 regex 对象,但是会错过一些微调参数。

See also

第三方regex模块具有与标准库re模块兼容的 API,但是提供了附加Function和更全面的 Unicode 支持。

正则表达式语法

正则表达式(或 RE)指定一组与之匹配的字符串;该模块中的函数可让您检查特定字符串是否与给定的正则表达式匹配(或给定的正则表达式是否与特定的字符串匹配,这取决于同一件事)。

可以将正则表达式连接起来以形成新的正则表达式。如果* A B 都是正则表达式,则 AB 也是正则表达式。通常,如果字符串 p 匹配 A ,而另一个字符串 q 匹配 B ,则字符串 pq 将匹配 AB。除非 A B *包含低优先级运算,否则这将成立。 * A B *之间的边界条件;或具有编号的组引用。因此,可以从简单的原始表达式(如此处所述的表达式)轻松构造复杂的表达式。有关正则表达式的理论和实现的详细信息,请参阅 Friedl 书[Frie09]或几乎所有有关编译器构造的教科书。

下面是对正则表达式格式的简要说明。有关更多信息和更柔和的介绍,请查阅正则表达式操作方法

正则表达式可以包含特殊字符和普通字符。大多数普通字符(例如'A''a''0')是最简单的正则表达式;他们只是匹配自己。您可以连接普通字符,因此last与字符串'last'匹配。 (在本节的其余部分中,我们将在this special style中编写 RE,通常不带引号,并在'in single quotes'处匹配字符串.)

某些字符(例如'|''(')很特殊。特殊字符要么代表普通字符类,要么影响它们周围正则表达式的解释方式。

重复限定符(*+?{m,n}等)不能直接嵌套。这样可以避免与非贪婪修饰符后缀?以及其他实现中的其他修饰符产生歧义。为了将第二次重复应用于内部重复,可以使用括号。例如,表达式(?:a{6})*匹配六个'a'字符的任何倍数。

特殊字符为:

如果您不使用原始字符串来表达模式,请记住 Python 还使用反斜杠作为字符串 Literals 中的转义序列。如果 Python 的解析器无法识别转义序列,则结果字符串中将包含反斜杠和后续字符。但是,如果 Python 将识别出结果序列,则应将反斜杠重复两次。这是复杂且难以理解的,因此强烈建议您对除最简单的表达式以外的所有表达式使用原始字符串。

在版本 3.7 中更改:如果字符集包含将来会在语义上更改的构造,则会引发FutureWarning

字母'a''L''u'用作内联标志时是互斥的,因此它们不能组合或跟随'-'。而是,当其中一个出现在内联组中时,它将覆盖封闭组中的匹配模式。在 Unicode 模式中,(?a:...)切换到仅 ASCII 匹配,(?u:...)切换到 Unicode 匹配(默认)。在字节模式中,(?L:...)切换到与语言环境有关的匹配,而(?a:...)切换到仅 ASCII 匹配(默认)。此替代仅对狭窄的嵌入式组有效,并且原始匹配模式在该组之外恢复。

3.6 版的新Function。

在版本 3.7 中更改:字母'a''L''u'也可以在组中使用。

可以在三个上下文中引用命名组。如果格式为(?P<quote>['"]).*?(?P=quote)(即匹配用单引号或双引号引起来的字符串):

引用组“ quote”的上下文 引用方式
以相同的模式 (?P=quote)(如图所示)

\1
当处理匹配对象* m * m.group('quote')
m.end('quote')(等)
在传递给re.sub()的* repl *参数的字符串中 \g<quote>
\g<1>
\1
>>> import re
>>> m = re.search('(?<=abc)def', 'abcdef')
>>> m.group(0)
'def'

本示例在连字符后查找单词:

>>> m = re.search(r'(?<=-)\w+', 'spam-egg')
>>> m.group(0)
'egg'

在版本 3.5 中进行了更改:添加了对固定长度的组引用的支持。

特殊序列由'\'和下面列表中的字符组成。如果普通字符不是 ASCII 数字或 ASCII 字母,则结果 RE 将与第二个字符匹配。例如,\$匹配字符'$'

默认情况下,Unicode 字母数字是 Unicode 模式中使用的字母数字,但是可以使用ASCII标志进行更改。如果使用LOCALE标志,则单词边界由当前语言环境确定。在字符范围内,\b表示退格字符,以与 Python 的字符串 Literals 兼容。

正则表达式解析器也接受 Python 字符串 Literals 支持的大多数标准转义:

\a      \b      \f      \n
\N      \r      \t      \u
\U      \v      \x      \\

(请注意,\b用于表示单词边界,仅在字符类内部表示“退格”.)

'\u''\U''\N'转义序列仅以 Unicode 模式识别。在字节模式中,它们是错误。 ASCII 字母的未知转义符保留供将来使用,并被视为错误。

八进制转义符以有限形式提供。如果第一个数字为 0,或者有三个八进制数字,则被视为八进制转义。否则,它是一个组引用。对于字符串 Literals,八进制转义符的长度始终最多为三位数。

在版本 3.3 中进行了更改:已添加'\u''\U'转义序列。

在版本 3.6 中更改:由'\'和 ASCII 字母组成的未知转义现在是错误。

在版本 3.8 中更改:已添加'\N{name}'转义序列。与字符串 Literals 一样,它扩展为命名的 Unicode 字符(例如'\N{EM DASH}')。

Module Contents

该模块定义了几个函数,常量和一个异常。其中一些Function是用于编译正则表达式的全Function方法的简化版本。大多数非平凡的应用程序始终使用已编译的表单。

在版本 3.6 中更改:标志常量现在是RegexFlag的实例,而RegexFlagenum.IntFlag的子类。

可以pass指定* flags *值来修改表达式的行为。值可以是以下任何变量,可以使用按位或(|运算符)进行组合。

The sequence

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

相当于

result = re.match(pattern, string)

但是,当在单个程序中多次使用表达式时,使用re.compile()并保存生成的正则表达式对象以供重用会更有效。

Note

传递给re.compile()的最新模式的已编译版本和模块级匹配函数将被缓存,因此一次仅使用几个正则表达式的程序不必担心会编译正则表达式。

请注意,为了向后兼容,re.U标志(以及其同义词re.UNICODE和其嵌入式对应项(?u))仍然存在,但是在 Python 3 中它们是多余的,因为默认情况下字符串的匹配项是 Unicode(并且 Unicode 匹配项不允许用于字节) )。

请注意,当 Unicode 模式[a-z][A-Z]IGNORECASE标志结合使用时,它们将匹配 52 个 ASCII 字母和 4 个其他非 ASCII 字母:'İ'(U 0130,拉丁大写字母 I,带点号),' ı”(U 0131,拉丁小写字母无点 i),“ ſ”(U 017F,拉丁小写字母 long s)和“ K”(U 212A,开尔文符号)。如果使用ASCII标志,则仅匹配字母“ a”至“ z”和“ A”至“ Z”。

在版本 3.6 中更改:re.LOCALE只能与字节模式一起使用,并且与re.ASCII不兼容。

在版本 3.7 中进行了更改:具有re.LOCALE标志的已编译正则表达式对象不再依赖于编译时的语言环境。只有匹配时的语言环境会影响匹配结果。

这意味着以下两个与十进制数匹配的正则表达式对象在Function上相等:

a = re.compile(r"""\d +  # the integral part
                   \.    # the decimal point
                   \d *  # some fractional digits""", re.X)
b = re.compile(r"\d+\.\d*")

对应于内联标志(?x)

请注意,即使在MULTILINE模式下,re.match()也只会在字符串的开头而不是每行的开头匹配。

如果要在* string *中的任何位置找到匹配项,请改用search()(另请参见search()与 match())。

3.4 版的新Function。

>>> re.split(r'\W+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split(r'(\W+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split(r'\W+', 'Words, words, words.', 1)
['Words', 'words, words.']
>>> re.split('[a-f]+', '0a3B9', flags=re.IGNORECASE)
['0', '3', '9']

如果分隔符中有捕获组,并且该匹配组在字符串的开头匹配,则结果将从空字符串开始。字符串的末尾也是如此:

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

这样,分隔符组件始终在结果列表中的相同相对索引中找到。

模式的空匹配仅在不与先前的空匹配相邻时才拆分字符串。

>>> re.split(r'\b', 'Words, words, words.')
['', 'Words', ', ', 'words', ', ', 'words', '.']
>>> re.split(r'\W*', '...words...')
['', '', 'w', 'o', 'r', 'd', 's', '', '']
>>> re.split(r'(\W*)', '...words...')
['', '...', '', '', 'w', '', 'o', '', 'r', '', 'd', '', 's', '...', '', '', '']

在版本 3.1 中更改:添加了可选的 flags 参数。

在版本 3.7 中进行了更改:添加了对可能与空字符串匹配的模式进行拆分的支持。

在版本 3.7 中进行了更改:现在可以在上一个空匹配之后立即开始非空匹配。

在版本 3.7 中进行了更改:现在可以在上一个空匹配之后立即开始非空匹配。

>>> 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 是一个函数,则在 pattern *的每次不重叠发生时都调用它。该函数接受单个match object参数,并返回替换字符串。例如:

>>> 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'

该模式可以是字符串或pattern object

可选参数* count *是要替换的最大模式出现次数; * count *必须为非负整数。如果Ellipsis或为零,将替换所有出现的事件。模式的空匹配仅在不与先前的空匹配相邻时才被替换,因此sub('x*', '-', 'abxd')返回'-a-b--d-'

在字符串类型* repl *参数中,除了上述的字符转义和反向引用外,\g<name>将使用由(?P<name>...)语法定义的,与名为name的组匹配的子字符串。 \g<number>使用相应的组号;因此,\g<2>等效于\2,但在诸如\g<2>0之类的替换中并没有歧义。 \20将被解释为对组 20 的引用,而不是对后跟 Literals 字符'0'的组 2 的引用。反向引用\g<0>替换 RE 匹配的整个子字符串。

在版本 3.1 中更改:添加了可选的 flags 参数。

在版本 3.5 中进行了更改:不匹配的组将替换为空字符串。

在版本 3.6 中进行了更改:现在,由'\'和 ASCII 字母组成的* pattern *中的未知转义符是错误。

在版本 3.7 中进行了更改:* repl *中由'\'和 ASCII 字母组成的未知转义符现在是错误。

在版本 3.7 中进行了更改:与先前的非空匹配项相邻时,将替换模式的空匹配项。

在版本 3.1 中更改:添加了可选的 flags 参数。

在版本 3.5 中进行了更改:不匹配的组将替换为空字符串。

>>> print(re.escape('http://www.python.org'))
http://www\.python\.org

>>> legal_chars = string.ascii_lowercase + string.digits + "!#$%&'*+-.^_`|~:"
>>> print('[%s]+' % re.escape(legal_chars))
[abcdefghijklmnopqrstuvwxyz0123456789!\#\$%\&'\*\+\-\.\^_`\|\~:]+

>>> operators = ['+', '-', '*', '/', '**']
>>> print('|'.join(map(re.escape, sorted(operators, reverse=True))))
/|\-|\+|\*\*|\*

此函数不得用于sub()subn()中的替换字符串,只能转义反斜杠。例如:

>>> digits_re = r'\d+'
>>> sample = '/usr/sbin/sendmail - 0 errors, 12 warnings'
>>> print(re.sub(digits_re, digits_re.replace('\\', r'\\'), sample))
/usr/sbin/sendmail - \d+ errors, \d+ warnings

在版本 3.3 中更改:'_'字符不再转义。

在版本 3.7 中进行了更改:仅对在正则表达式中具有特殊含义的字符进行转义。结果,不再逃避'!''"''%'"'"',''/'':'';''<''=''>''@'"`"

在版本 3.5 中进行了更改:添加了其他属性。

正则表达式对象

编译的正则表达式对象支持以下方法和属性:

可选的第二个参数* pos *在搜索开始的字符串中给出一个索引;默认为0。这并不完全等同于切片字符串。 '^'模式字符在字符串的实际开头和换行符之后的位置匹配,但不一定在搜索要开始的索引处匹配。

可选参数* endpos 限制了将搜索字符串的距离;就像字符串的长度是 endpos 个字符一样,因此仅搜索从 pos endpos - 1的字符进行匹配。如果 endpos 小于 pos ,则找不到匹配项;否则,如果 rx *是已编译的正则表达式对象,则rx.search(string, 0, 50)等效于rx.search(string[:50], 0)

>>> pattern = re.compile("d")
>>> pattern.search("dog")     # Match at index 0
<re.Match object; span=(0, 1), match='d'>
>>> pattern.search("dog", 1)  # No match; search doesn't include the "d"

可选的* pos endpos *参数的含义与search()方法的含义相同。

>>> 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".
<re.Match object; span=(1, 2), match='o'>

如果要在* string *中的任何位置找到匹配项,请改用search()(另请参见search()与 match())。

可选的* pos endpos *参数的含义与search()方法的含义相同。

>>> 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.
<re.Match object; span=(1, 3), match='og'>

3.4 版的新Function。

在 3.7 版中进行了更改:添加了对copy.copy()copy.deepcopy()的支持。编译后的正则表达式对象被认为是原子的。

Match Objects

匹配对象的布尔值始终为True。由于match()search()在没有匹配项时返回None,因此您可以使用简单的if语句来测试是否存在匹配项:

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

匹配对象支持以下方法和属性:

在版本 3.5 中进行了更改:不匹配的组将替换为空字符串。

>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m.group(0)       # The entire match
'Isaac Newton'
>>> m.group(1)       # The first parenthesized subgroup.
'Isaac'
>>> m.group(2)       # The second parenthesized subgroup.
'Newton'
>>> m.group(1, 2)    # Multiple arguments give us a tuple.
('Isaac', 'Newton')

如果正则表达式使用(?P<name>...)语法,则* groupN *参数也可以是pass组名标识组的字符串。如果在模式中未将字符串参数用作组名,则会引发IndexError异常。

一个中等复杂的示例:

>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.group('first_name')
'Malcolm'
>>> m.group('last_name')
'Reynolds'

命名组也可以pass其索引来引用:

>>> m.group(1)
'Malcolm'
>>> m.group(2)
'Reynolds'

如果一个组多次匹配,则只能访问最后一个匹配项:

>>> m = re.match(r"(..)+", "a1b2c3")  # Matches 3 times.
>>> m.group(1)                        # Returns only the last match.
'c3'
>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m[0]       # The entire match
'Isaac Newton'
>>> m[1]       # The first parenthesized subgroup.
'Isaac'
>>> m[2]       # The second parenthesized subgroup.
'Newton'

3.6 版的新Function。

For example:

>>> m = re.match(r"(\d+)\.(\d+)", "24.1632")
>>> m.groups()
('24', '1632')

如果我们将小数位及其后的所有内容设为可选,则并非所有组都可以参加 match。除非给出* default *参数,否则这些组将默认为None

>>> m = re.match(r"(\d+)\.?(\d+)?", "24")
>>> m.groups()      # Second group defaults to None.
('24', None)
>>> m.groups('0')   # Now, the second group defaults to '0'.
('24', '0')
>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.groupdict()
{'first_name': 'Malcolm', 'last_name': 'Reynolds'}
m.string[m.start(g):m.end(g)]

请注意,如果* group *与空字符串匹配,则m.start(group)将等于m.end(group)。例如,在m = re.search('b(c?)', 'cba')之后,m.start(0)为 1,m.end(0)为 2,m.start(1)m.end(1)均为 2,并且m.start(2)引发IndexError异常。

将从电子邮件地址中删除* remove_this *的示例:

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

在 3.7 版中进行了更改:添加了对copy.copy()copy.deepcopy()的支持。匹配对象被视为原子对象。

正则表达式示例

检查配对

在此示例中,我们将使用以下帮助函数来更优雅地显示匹配对象:

def displaymatch(match):
    if match is None:
        return None
    return '<Match: %r, groups=%r>' % (match.group(), match.groups())

假设您正在编写一个扑克程序,其中玩家的手用 5 个字符的字符串表示,每个字符代表一张纸牌,“ a”代表 ace,“ k”代表国王,“ q”代表皇后,“ j”代表杰克, “ t”代表 10,“ 2”到“ 9”代表具有该值的卡。

要查看给定的字符串是否有效,可以执行以下操作:

>>> valid = re.compile(r"^[a2-9tjqk]{5}$")
>>> displaymatch(valid.match("akt5q"))  # Valid.
"<Match: 'akt5q', groups=()>"
>>> displaymatch(valid.match("akt5e"))  # Invalid.
>>> displaymatch(valid.match("akt"))    # Invalid.
>>> displaymatch(valid.match("727ak"))  # Valid.
"<Match: '727ak', groups=()>"

最后一手"727ak"包含Pair或两个相同值的卡。为了将其与正则表达式匹配,可以使用如下反向引用:

>>> pair = re.compile(r".*(.).*\1")
>>> displaymatch(pair.match("717ak"))     # Pair of 7s.
"<Match: '717', groups=('7',)>"
>>> displaymatch(pair.match("718ak"))     # No pairs.
>>> displaymatch(pair.match("354aa"))     # Pair of aces.
"<Match: '354aa', groups=('a',)>"

要找出两人组成的卡,可以pass以下方式使用 match 对象的group()方法:

>>> pair = re.compile(r".*(.).*\1")
>>> pair.match("717ak").group(1)
'7'

# Error because re.match() returns None, which doesn't have a group() method:
>>> pair.match("718ak").group(1)
Traceback (most recent call last):
  File "<pyshell#23>", line 1, in <module>
    re.match(r".*(.).*\1", "718ak").group(1)
AttributeError: 'NoneType' object has no attribute 'group'

>>> pair.match("354aa").group(1)
'a'

Simulating scanf()

Python 当前没有等效于scanf()。正则表达式通常比scanf()格式字符串更强大,但也更冗长。下表提供了scanf()格式标记和正则表达式之间的等效 Map。

scanf()令牌 Regular Expression
%c .
%5c .{5}
%d [-+]?\d+
%e , %E , %f , %g [-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?
%i [-+]?(0[xX][\dA-Fa-f]+|0[0-7]*|\d+)
%o [-+]?[0-7]+
%s \S+
%u \d+
%x , %X [-+]?(0[xX])?[\dA-Fa-f]+

从类似的字符串中提取文件名和数字

/usr/sbin/sendmail - 0 errors, 4 warnings

您将使用scanf()格式,例如

%s - %d errors, %d warnings

等效的正则表达式为

(\S+) - (\d+) errors, (\d+) warnings

search()vs. match()

Python 根据正则表达式提供了两种不同的基本操作:re.match()仅在字符串的开头检查匹配项,而re.search()在字符串的任何位置检查匹配项(这是 Perl 的默认设置)。

For example:

>>> re.match("c", "abcdef")    # No match
>>> re.search("c", "abcdef")   # Match
<re.Match object; span=(2, 3), match='c'>

'^'开头的正则表达式可以与search()一起使用以限制字符串开头的匹配:

>>> re.match("c", "abcdef")    # No match
>>> re.search("^c", "abcdef")  # No match
>>> re.search("^a", "abcdef")  # Match
<re.Match object; span=(0, 1), match='a'>

但是请注意,在MULTILINE模式下,match()仅在字符串的开头匹配,而将search()与以'^'开头的正则表达式一起使用将在每一行的开头匹配。

>>> re.match('X', 'A\nB\nX', re.MULTILINE)  # No match
>>> re.search('^X', 'A\nB\nX', re.MULTILINE)  # Match
<re.Match object; span=(4, 5), match='X'>

制作电话簿

split()将字符串拆分为由传递的模式分隔的列表。该方法对于将文本数据转换为可由 Python 轻松读取和修改的数据结构非常有用,如以下创建电话簿的示例所示。

首先,这是 Importing。通常它可能来自文件,这里我们使用三引号字符串语法

>>> text = """Ross McFluff: 834.345.1254 155 Elm Street
...
... Ronald Heathmore: 892.345.3428 436 Finley Avenue
... Frank Burger: 925.541.7625 662 South Dogwood Way
...
...
... Heather Albrecht: 548.326.4584 919 Park Place"""

这些条目由一个或多个换行符分隔。现在,我们将字符串转换为列表,每个非空行都有其自己的条目:

>>> entries = re.split("\n+", text)
>>> entries
['Ross McFluff: 834.345.1254 155 Elm Street',
'Ronald Heathmore: 892.345.3428 436 Finley Avenue',
'Frank Burger: 925.541.7625 662 South Dogwood Way',
'Heather Albrecht: 548.326.4584 919 Park Place']

最后,将每个条目分成一个包含名字,姓氏,电话 Numbers 和地址的列表。我们使用split()maxsplit参数,因为地址中包含空格(拆分模式):

>>> [re.split(":? ", entry, 3) for entry in entries]
[['Ross', 'McFluff', '834.345.1254', '155 Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436 Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662 South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919 Park Place']]

:?模式与姓氏后的冒号匹配,因此它不会出现在结果列表中。使用_3 的maxsplit,我们可以将门牌 Numbers 与街道名称分开:

>>> [re.split(":? ", entry, 4) for entry in entries]
[['Ross', 'McFluff', '834.345.1254', '155', 'Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436', 'Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662', 'South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919', 'Park Place']]

Text Munging

sub()用字符串或函数结果替换每次出现的模式。本示例演示使用带有函数sub()的“ munge”文本或随机化句子中每个单词中除第一个和最后一个字符外的所有字符的 Sequences:

>>> def repl(m):
...     inner_word = list(m.group(2))
...     random.shuffle(inner_word)
...     return m.group(1) + "".join(inner_word) + m.group(3)
>>> text = "Professor Abdolmalek, please report your absences promptly."
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Poefsrosr Aealmlobdk, pslaee reorpt your abnseces plmrptoy.'
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Pofsroser Aodlambelk, plasee reoprt yuor asnebces potlmrpy.'

查找所有副词

findall()匹配所有模式的出现,而不仅仅是search()匹配的第一个模式。例如,如果作者想在某个文本中找到所有副词,则他们可以按以下方式使用findall()

>>> text = "He was carefully disguised but captured quickly by police."
>>> re.findall(r"\w+ly", text)
['carefully', 'quickly']

查找所有副词及其位置

如果要获取有关模式所有匹配项的信息多于匹配文本,则finditer()很有用,因为它提供match objects而不是字符串。continue 前面的示例,如果作者希望在某些文本中找到所有副词及其位置,则可以按以下方式使用finditer()

>>> text = "He was carefully disguised but captured quickly by police."
>>> for m in re.finditer(r"\w+ly", text):
...     print('%02d-%02d: %s' % (m.start(), m.end(), m.group(0)))
07-16: carefully
40-47: quickly

原始字符串表示法

原始字符串符号(r"text")使正则表达式保持理智。如果没有它,则正则表达式中的每个反斜杠('\')都必须加上另一个前缀,以使其转义。例如,以下两行代码在Function上相同:

>>> re.match(r"\W(.)\1\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>
>>> re.match("\\W(.)\\1\\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>

当要匹配 Literals 反斜杠时,必须在正则表达式中将其转义。如果使用原始字符串符号,则表示r"\\"。如果没有原始字符串符号,则必须使用"\\\\",从而使以下代码行在Function上相同:

>>> re.match(r"\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>
>>> re.match("\\\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>

编写令牌生成器

令牌生成器或扫描器分析字符串以对字符组进行分类。这是编写编译器或解释器的有用的第一步。

文本类别使用正则表达式指定。该技术是将那些组合成单个主正则表达式,并循环遍历连续的匹配项:

from typing import NamedTuple
import re

class Token(NamedTuple):
    type: str
    value: str
    line: int
    column: int

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()
        column = mo.start() - line_start
        if kind == 'NUMBER':
            value = float(value) if '.' in value else int(value)
        elif kind == 'ID' and value in keywords:
            kind = value
        elif kind == 'NEWLINE':
            line_start = mo.end()
            line_num += 1
            continue
        elif kind == 'SKIP':
            continue
        elif kind == 'MISMATCH':
            raise RuntimeError(f'{value!r} unexpected on line {line_num}')
        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)

标记器产生以下输出:

Token(type='IF', value='IF', line=2, column=4)
Token(type='ID', value='quantity', line=2, column=7)
Token(type='THEN', value='THEN', line=2, column=16)
Token(type='ID', value='total', line=3, column=8)
Token(type='ASSIGN', value=':=', line=3, column=14)
Token(type='ID', value='total', line=3, column=17)
Token(type='OP', value='+', line=3, column=23)
Token(type='ID', value='price', line=3, column=25)
Token(type='OP', value='*', line=3, column=31)
Token(type='ID', value='quantity', line=3, column=33)
Token(type='END', value=';', line=3, column=41)
Token(type='ID', value='tax', line=4, column=8)
Token(type='ASSIGN', value=':=', line=4, column=12)
Token(type='ID', value='price', line=4, column=15)
Token(type='OP', value='*', line=4, column=21)
Token(type='NUMBER', value=0.05, line=4, column=23)
Token(type='END', value=';', line=4, column=27)
Token(type='ENDIF', value='ENDIF', line=5, column=4)
Token(type='END', value=';', line=5, column=9)
首页