Python 3.7.1 模块 正则表达式 re

正则表达式操作


源代码: Lib/re.py
翻译+自己的理解

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

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

正则表达式使用反斜杠字符(\)来表示特殊形式或允许使用特殊字符而不调用它们的特殊含义。这与Python对 字符串文字 中相同用途的相同字符的使用不同; 例如,要匹配文字反斜杠,必须将其模式字符串写为\\\\,因为正则表达式必须是\\,并且在常规Python字符串文字中每个反斜杠必须表示为\\

解决方案是使用Python的原始字符串表示法来表示正则表达式模式; 在r前缀的字符串文字中,不以任何特殊方式处理反斜杠。r"\n"是一个包含\n的双字符字符串 ,同时\n是包含换行符的单字符字符串。通常在Python代码中,模式将使用此原始字符串表示法表示。

值得注意的是,大多数正则表达式操作都可用作 编译正则表达式 的模块级函数和方法 。这些函数是很便捷,不需要先编译正则表达式对象,但会遗漏一些微调参数。

扩展
第三方 正则 表达式模块,它具有与标准库re 模块 兼容的API ,但提供了额外的功能和更全面的Unicode支持。

1. 正则表达式语法

正则表达式(或RE)指定一组与之匹配的字符串; 通过此模块中的函数,您可以检查特定字符串是否与给定的正则表达式匹配(或者如果给定的正则表达式与特定字符串匹配,则归结为同一个字符串)。

正则表达式可以连接起来形成新的正则表达式; 如果A 和B都是正则表达式,那么AB也是正则表达式。通常,如果字符串p匹配A而另一个字符串q匹配B,则字符串pq将匹配AB。除非A或B包含低优先级操作 ,A和B之间有边界条件; 或编号组参考,否则这是成立的。因此,复杂的表达式可以很容易地从更简单的原始表达式构建,就像这里描述的那样。有关正则表达式的理论和实现的详细信息,请参阅Friedl一书,或几乎任何有关编译器构造的教科书。

下面是正则表达式格式的简要说明。有关更多信息和简单的介绍,请参阅 正则表达式HOWTO

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

某些字符,如'|''(',是特殊的。特殊字符或者代表普通字符的类,或者影响它们周围的正则表达式的解释方式。

重复限定符(*+?{m,n}等等)不能直接嵌套。这避免了非贪婪修饰符后缀?的模糊性 ,以及其他实现中的其他修饰符。为了对内部重复应用,可以使用括号。例如,表达式(?:a{6})*匹配六个’a’字符的任意倍数。

1.1 特殊字符

.

点。在默认模式下,它匹配除换行符之外的任何字符。如果已指定DOTALL标志,则匹配包括换行符在内的任何字符。

def re_bs():
    oris = '\nhi,@leng!\n'
    valid = re.compile(r".")
    valid1 = re.compile(r".", re.DOTALL)
    print(re.findall(valid,oris))
    print(re.findall(valid1,oris))
# 输出结果
['h', 'i', ',', '@', 'l', 'e', 'n', 'g', '!']
['\n', 'h', 'i', ',', '@', 'l', 'e', 'n', 'g', '!', '\n']

我的环境是windows系统,换行符是\n,你的系统换行符可能不一样。

^

Caret。匹配字符串的开头,并且在多行模式re.MULTILINE下也会在每个换行符后立即匹配。

def re_caret():
    oris = 'temp\nhi,@leng!\n'
    valid = re.compile(r"^.*")
    valid1 = re.compile(r"^.*", re.MULTILINE)
    print(re.findall(valid,oris))
    print(re.findall(valid1,oris))
    valid = re.compile(r"^")
    valid1 = re.compile(r"^", re.MULTILINE)
    print(re.findall(valid, oris))
    print(re.findall(valid1, oris))
# 输出结果
['temp']
['temp', 'hi,@leng!\r', '']
['']
['', '', '']

$

匹配字符串的结尾或在字符串末尾的换行符之前,并且在re.MULTILINE模式中也匹配换行符之前。 foo 匹配'foo''foobar',而正则表达式foo$只匹配'foo'。更有趣的是, 通常foo.$'foo1\nfoo2\n'中搜索并匹配到'foo2',但在re.MULTILINE模式中会匹配到'foo1';
搜索单个$'foo\n'中将找到两个(空)匹配:一个在换行符之前,一个在字符串末尾。

def re_end():
    oris = 'foo1\nfoo2\n'
    valid = re.compile(r"foo.$")
    valid1 = re.compile(r"foo.$", re.MULTILINE)
    print(re.findall(valid,oris))
    print(re.findall(valid1,oris))
    oris2 = 'foo\n\n'
    valid2 = re.compile(r"$")
    valid3 = re.compile(r"$",re.MULTILINE)
    print(re.findall(valid2,oris2))
    print(re.findall(valid3,oris2))
# 输出结果
['foo2']
['foo1', 'foo2']
['', '']
['', '', '']

译者注:在不指定re.MULTILINE的情况下,搜索开始^和结束$的返回空匹配个数不一样。

*

使得到的RE匹配 前面RE的0或更多次重复,尽可能多的重复。 ab*将匹配'a','ab'或'a',后跟任意数量的'b'

def re_star():
    oris = 'c,a,ab,abb,abbc,1abD'
    valid = re.compile(r"ab*")
    print(re.findall(valid, oris))
# 输出结果
['a', 'ab', 'abb', 'abb', 'ab']

+

使得到的RE匹配前一个RE的1次或更多次重复。 ab+将匹配’a’后跟任何非零数字的’b’; 它不会只匹配'a'

def re_star():
    oris = 'c,a,ab,abb,abbc,1abD'
    valid = re.compile(r"ab+")
    print(re.findall(valid, oris))
# 输出结果
['ab', 'abb', 'abb', 'ab']

?

使得到的RE匹配前面RE的0或1次重​​复。 ab?将匹配'a'或'ab'

def re_wen():
    oris = 'c,a,ab,abb,abbc,1abD'
    al = ['a','ab','abb']
    valid = re.compile(r"ab?")
    print(re.findall(valid,oris))
# 输出结果
['a', 'ab', 'ab', 'ab', 'ab']

*?,+?,??

'*','+'和'?'限定符都是贪婪的 ; 它们匹配尽可能多的文本。有时这种行为是不可取的; 如果RE <.*>匹配'<a> b <c>',它将匹配整个字符串,而不仅仅是'<a>'。在限定符之后添加?使其以非贪婪或最小的方式执行匹配; 尽可能少的 字符将匹配。使用RE <.*?>仅匹配'<a>'

def re_greedy():
    oris = "<html>CSS<title>HELLO</title><body>" + \
           "<p class='page'>P1</p><p class='page2'>P2</p></body></html>"

    valid = re.compile(r"<p.*>")
    print(re.search(valid,oris).group())
    valid = re.compile(r"<p.*?>")
    print(re.search(valid, oris).group())
# 输出结果
<p class='page'>P1</p><p class='page2'>P2</p></body></html>
<p class='page'>

{m}

指定应匹配前一个RE的m个副本; 较少的匹配导致整个RE不匹配。例如,a{6}将匹配正好六个’a’字符,但不匹配五个字符。

def re_m():
    oris = 'aabaaac'
    valid = re.compile(r"a{3}")
    print(re.findall(valid, oris))
# 输出结果
['aaa']

{m,n}

使得到的RE匹配前一个RE的m到n次重复,尝试匹配尽可能多的重复。例如, a{3,5}将匹配3到5个’a’字符。省略m指定零的下限,省略n指定无限上限。作为一个例子,a{4,}b将匹配'aaaab'或一千个’a’字符后跟一个’b’,但不是'aaab'。可以不省略逗号,或者将修饰符与先前描述的形式混淆。

def re_mn():
    oris = 'aabaaaacaaaaa'
    valid = re.compile(r"a{3,5}")
    print(re.findall(valid,oris))
    valid = re.compile(r"a{,3}")
    print(re.findall(valid, oris))
    valid = re.compile(r"a{1,}")
    print(re.findall(valid, oris))
# 输出结果
['aaaa', 'aaaaa']
['aa', '', 'aaa', 'a', '', 'aaa', 'aa', '']
['aa', 'aaaa', 'aaaaa']

{m,n}?

使得到的RE匹配前一个RE的m到n次重复,尝试匹配尽可能少的重复。这是前一个限定符的非贪婪版本。例如,在6个字符的字符串上'aaaaaa'a{3,5}将匹配5个'a'字符,而a{3,5}?只匹配3个字符。

\

要么转义特殊字符(允许你匹配像'*''?'等等那样的字符 ),要么发出特殊序列的信号; 下面讨论特殊序列。

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

def re_slash():
    oris = 'abc*?e'
    valid = re.compile(r"abc*?e")
    print(re.findall(valid, oris))
    valid = re.compile(r"abc\*\?e")
    print(re.findall(valid, oris))
# 输出结果
[]
['abc*?e']

[]

用于表示一组字符。在一组:

  • 字符可以单独列出,如[amk]将匹配'a', 'm'或'k'
  • 范围内的字符可以通过给两个字符,并通过-把它们连接起来,例如[a-z]将匹配任何小写ASCII字母, [0-5][0-9]将所有从00到59的两位数字,·[0-9A-Fa-f]会匹配任何十六进制数字。如果-被转义(例如[a\-z])或者如果它被放置为第一个或最后一个字符(例如[-a][a-]),它将匹配文字'-'
  • 特殊字符在内部失去特殊意义。例如, [(+*)]将匹配任何文字字符的'(','+', '*',或')'
  • 在集合中也接受诸如\w\S(在下面定义)的字符类,尽管它们匹配的字符取决于 ASCII或LOCALE模式是否有效。
  • 不在范围内的字符可以用^排除。如果集合的第一个字符是^,则将匹配该组中不存在的所有字符。例如,[^5]将匹配除了'5'之外的任何字符,并且[^^]将匹配除了'^'之外的任何字符 。 如果^它不是集合中的第一个字符,则没有特殊含义。
  • 要匹配集合中的']',请在其前面加上反斜杠,或将其放在集合的开头。例如,无论是[()[\]{}][]()[{}]都将匹配一个括号。
  • 将来可能会添加对 Unicode技术标准#18 中的嵌套集和集合操作的支持。这将改变语法,因此为了促进这种改变,FutureWarning暂时会在模棱两可的情况下提出。这包括文字集合以'['开始或含有文字字符序列'--''&&''~~',和'||'。为了避免警告,请使用反斜杠来避开它们。
    版本:python 3.7 -> 如果字符集包含将来将在语义上更改的构造,警告FutureWarning。
def re_list():
    oris = 'a-bcd^[+]'
    valid = re.compile(r"[a-d]")
    print(re.findall(valid, oris))

    valid = re.compile(r"[a\-d]")
    print(re.findall(valid, oris))

    valid = re.compile(r"[-ad]")
    print(re.findall(valid, oris))

    valid = re.compile(r"[^-ad]")
    print(re.findall(valid, oris))
    valid = re.compile(r"[-^ad+]")
    print(re.findall(valid, oris))

    valid = re.compile(r"[d\]a]")
    print(re.findall(valid, oris))
    valid = re.compile(r"[]a[d]")
    print(re.findall(valid, oris))
    print("------------------")
	oris = '[--hello]'
    valid = re.compile(r"[[s]")
    print(re.findall(valid, oris))
# 输出结果
['a', 'b', 'c', 'd']
['a', '-', 'd']
['a', '-', 'd']
['b', 'c', '^', '[', '+', ']']
['a', '-', 'd', '^', '+']
['a', 'd', ']']
['a', 'd', '[', ']']
------------------
xxx/re1.py:126: FutureWarning: Possible nested set at position 1
  valid = re.compile(r"[[s]")
['[']

|

A|B,其中A和B可以是任意RE,创建一个与A或B匹配的正则表达式。通过|这种方式可以分开任意数量的RE 。这也可以在组内使用(见下文)。扫描目标字符串时,’|‘从左到右尝试分隔的RE 。当一个模式完全匹配时,接受该分支。这意味着一旦A匹配,B将不会被进一步测试,即使它会产生更长的整体匹配。换句话说,’|'操作符从不贪心。要匹配文字'|',请使用\|或将其括在字符类中,如[|]

def re_or():
    oris = 'abc'
    valid = re.compile(r'ab|bc')
    valid1 = re.compile(r'ac|bc')
    print(re.search(valid,oris))
    print(re.search(valid1, oris))
# 输出结果
<re.Match object; span=(0, 2), match='ab'>
<re.Match object; span=(1, 3), match='bc'>

(…)

匹配括号内的正则表达式,并指示组的开始和结束; 可以在执行匹配后检索组的内容,并且稍后可以在字符串中与\number 特殊序列匹配,如下所述。要匹配的文字'('')'使用\(\),或将它们括中括号间:[(][)]

def re_group():
    # 匹配分开的两个数字
    oris = 'abc45fg'
    valid = re.compile(r'(\d)(\d)')
    print(re.search(valid, oris).groups())
    print(re.search(valid, oris).group(1))
    print(re.search(valid, oris).group(2))
    # 匹配括号
    oris = 'abc(45)fg'
    valid = re.compile(r'[(]\d\d[)]')
    print(re.search(valid, oris).groups())
# 输出结果
('4', '5')
4
5
()

(?..)

这是一种扩展符号('?'跟随一个'('除此之外没有意义)。'?'之后的第一个字符确定构造的含义和进一步语法。扩展通常不会创建新组; (?P<name>...)是这条规则的唯一例外。以下是当前支持的扩展。

(?aiLmsux)

(集合'a','i','L','m', 's','u','x'中的一个或多个字母。)该组匹配空字符串; 这些字母为整个常规设置相应的标志: re.A ( ASCII-only matching), re.I(ignore case),re.L(locale dependent), re.M(multi-line),re.S(dot match all), re.U(Unicode匹配)和re.X(verbose)表达。(标志在模块内容中描述。)如果您希望将标志包含在正则表达式的一部分中,而不是将标志参数传递给 re.compile()函数,这将非常有用。应首先在表达式字符串中使用标志。

(?:…)

常规括号内的非捕获版本。匹配括号内的正则表达式,但在执行匹配或稍后在模式中引用后,无法检索组匹配的子字符串 。


译者实例:

def re_fen_not_catch():
    oris = 'abcaBcdeabc'
    # 情况1
    m= re.search(r'(abc)(.*?)(\1)', oris)
    print("正常括号分组1:"+m.group(1))
    print("正常括号分组2:"+m.group(2))
	# 情况2
    valid = re.compile(r'(?:abc)(.*)(\1)')
    m = re.search(valid,oris)
    print("非捕获括号分组1:"+m.group(1))
    print("非捕获括号分组2:"+m.group(2))
    
    # 情况3
    oris = 'abcabcaBcdeabc'
    m = re.search(r'(?:abc)(abc)(.*?)(\1)', oris)
    print("非捕获括号分组3:" + m.group(1))
    print("非捕获括号分组4:" + m.group(2))
# 输出结果
正常括号分组1:abc
正常括号分组2:aBcde
非捕获括号分组1:
非捕获括号分组2:
非捕获括号分组3:abc
非捕获括号分组4:aBcde

分析:
(1)从上面的例子对比来看(...)(?:...)的区别就比较明显了,后者确实匹配正则表达式,但是不会加入分组中。
(2)情况1中,(abc)匹配到开头的'abc',将其加入分组1,使得后面可以使用分组序号(\1)group(1),中间的内容成为分组2。
(3)情况2中,(?:abc)匹配到开头的'abc',但是将其加入分组1,导致\1group(1)不存在,从而使得分组2也没有了。
(4)情况3中,对源字符串增加了'abc',此时虽然第一个'abc'没加入分组,但是第二个正常加入分组,所以就有了\1,剩余同 情况1。


(?aiLmsux-imsx:…)

(集合'a','i','L','m', 's','u','x'中的一个或多个字母,可选地接着-随后是集合'i','m','s','x'中的一个或多个字母)。字母设置或移除相应的标志,标志含义后面讲。

这些字母'a','L','u'为内联标志使用时是互斥的,所以它们不能被合并或跟随-。相反,当其中一个出现在内联组中时,它将覆盖封闭组中的匹配模式。在Unicode模式中(?a:...)切换到仅匹配ASCII,并(?u:...)切换到Unicode匹配(默认)。在字节模式中(?L:...)切换到区域设置取决于匹配,(?a:...)切换到仅匹配ASCII(默认)。此覆盖仅对窄内联组有效,并且原始匹配模式将在组外部恢复。

版本:python 3.6中的新功能。

版本:python3.7版本中的变动 -> 字母'a','L','u'可以在组中使用。

def re_reflag():
    oris = 'aBcde\nabc'
    print(re.search(r'abc', oris).groups())
    print(re.search(r'(abc)', oris,re.I).groups())
    print(re.search(r'(?i:abc)', oris, re.I).groups())
    print(re.search(r'(abc)', oris, re.M).groups())
    print(re.search(r'(?m:abc)', oris, re.M).groups())
# 输出结果
()
('aBc',)
()
('abc',)
()

(?P<name>…)

与常规括号类似,但组匹配的子字符串可通过符号组名称名称访问。组名必须是有效的Python标识符,并且每个组名只能在正则表达式中定义一次。符号组也是编号组,就像组未命名一样。

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

引用组“quote”的上下文 引用它的方法
在相同的模式内 (?P=quote)
\1
当处理匹配对象m时 m.group('quote')
m.end('quote')
传递给re.sub() 的repl 参数的字符串 \g<quote>
\g
\1
def re_pname():
    # 匹配分开的两个数字
    oris = 'abc45fg'
    valid = re.compile(r'(?P<four>\d)(?P<five>\d)')
    print(re.search(valid, oris).groups())
    print(re.search(valid, oris).group(1))
    print(re.search(valid, oris).group(2))
    print("by name-four:",re.search(valid, oris).group('four'))
    print("by name-four:", re.search(valid, oris).end('four'))
    print("by name-five:",re.search(valid, oris).group('five'))

    oris = '"abc45fg"'
    valid = re.compile(r'(?P<quote>["])(.*)(?P=quote)')
    print(re.search(valid, oris).groups())
    valid = re.compile(r'(?P<quote>["])(.*)(\1)')
    print(re.search(valid, oris).groups())
# 输出结果
('4', '5')
4
5
by name-four: 4
by name-four: 4
by name-five: 5
('"', 'abc45fg')
('"', 'abc45fg', '"')

(?P=name)

对命名组的反向引用; 它匹配前面名为name的组匹配的任何文本。

(?#…)

一条注释; 简单地忽略括号的内容。

def re_comment():
    oris = 'abc123'
    valid = re.compile(r'abc(?#comment)1')
    print(re.search(valid, oris).group(0))
# 输出结果
abc1

(?=…)

匹配后面跟的是正则表达式的位置,但不消耗任何字符串。这称为先行断言。例如,Isaac (?=Asimov)只有在'Isaac '后跟随'Asimov'时才匹配 。

def re_next():
    oris = 'abc1234'
    valid = re.compile(r'abc(?=123)')
    print(re.search(valid, oris).group(0))
    oris = 'abc2234'
    valid = re.compile(r'abc(?=123)')
    print(re.search(valid, oris))
# 输出结果
abc
None

(?!..)

匹配后面跟的不是正则表达式的位置。这是一个负面的先行断言。例如,Isaac (?!Asimov)只有在'Isaac '后没有跟'Asimov'的情况下才会匹配。

def re_not_next():
    oris = 'abc2234'
    valid = re.compile(r'abc(?!123)')
    print(re.search(valid, oris).group())
# 输出结果
abc

(?<=…)

匹配前缀是正则表达式的位置。这被称为正向的lookbehind断言。(?<=abc)def将找到匹配'abcdef',因为lookbehind将备份3个字符并检查包含的模式是否匹配。所包含的模式必须只匹配一些固定长度的串,这意味着 abc或者a|b是允许的,但a*a{3,4}不允许。请注意,以正向的lookbehind断言开头的模式在搜索字符串的开头不匹配; 你很可能想要使用 search()函数而不是match()函数:

def re_prefix_is():
    oris = 'afabc2234'
    valid = re.compile(r'(?<=abc)223')
    print(re.search(valid, oris).group())
    valid = re.compile(r'(?<=bcd|efg|abc)223')  # abc bcd efg 必需长度一样
    print(re.search(valid, oris).group())
    oris = 'afabc2234'
    valid = re.compile(r'(?<=abc)223')
    print(re.match(valid, oris)) #match只会在开头搜索
# 输出结果
223
223
None

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

(?<!..)

匹配前缀是不是正则表达式的位置。这被称为反向向的lookbehind断言。与正向lookbehind断言类似,包含的模式必须仅匹配某些固定长度的字符串。以负反向断言开始的模式可以在被搜索的字符串的开头匹配。

def re_prefix_not():
    oris = 'abc2234'
    valid = re.compile(r'(?<!abc)223')
    print(re.search(valid, oris))
    valid1 = re.compile(r'(?<!bcd|efg)223')  # bcd efg 必需长度一样
    print(re.search(valid1, oris).group())
# 输出结果
None
223

(?=...),(?!...),(?<=...),(?<!...)这四个有点绕,大家可以自己从不同的方面进行两两分组,才能有所领会。

(?(id/name)yes-pattern|no-pattern)

如果存在具有给定id或名称的组,则尝试匹配yes-pattern,如果不存在,则尝试匹配no-patternno-pattern是可选的,可以省略。例如,(<)?(\w+@\w+(?:\.\w+)+)(?(1)>|$)是一个简易的电子邮件匹配模式,这将匹配'<[email protected]>'以及'[email protected]',但不匹配'<[email protected]''[email protected]>'

def re_name_id_yesno():
    ol = ['<[email protected]>','[email protected]','<[email protected]','[email protected]>']
    valid = re.compile(r'(<)?(\w+@\w+(?:\.\w+)+)(?(1)>|$)')

    for i in range(len(ol)):
        print(i,re.match(valid,ol[i]))

    print('-'.center(50,'-'))
    for i in range(len(ol)):
        print(i,re.search(valid,ol[i]))
# 输出结果
0 <re.Match object; span=(0, 15), match='<[email protected]>'>
1 <re.Match object; span=(0, 13), match='[email protected]'>
2 None
3 None
--------------------------------------------------
0 <re.Match object; span=(0, 15), match='<[email protected]>'>
1 <re.Match object; span=(0, 13), match='[email protected]'>
2 <re.Match object; span=(1, 14), match='[email protected]'>
3 None

分析:
(1)(?:\.\w+)+这部分是为了匹配.com.cn这种多级域名的情况。
(2)对于这种一次性整体匹配,match的输出结果更准确,search不够准确,这两个函数后面再说,这里只是提一嘴。


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

\number

匹配相同编号的组的内容。组从1开始编号。例如(.+) \1匹配'the the''55 55',但不匹配'thethe'(注意组后面有空格)。此特殊序列只能用于匹配前99个组中的一个。如果number的第一个数字数是0,或数为3个八进制数字长,也不会被解释为一个组匹配,而是被解释为八进制值的字符数。在 字符'['']'中,所有数字转义都被视为字符。

def re_number():
    ol = ['china china','go go','firstfirst']
    valid = re.compile(r'(.+) \1')

    for i in range(len(ol)):
        print(i, re.match(valid, ol[i]))
# 输出结果
0 <re.Match object; span=(0, 11), match='china china'>
1 <re.Match object; span=(0, 5), match='go go'>
2 None

\A

仅匹配字符串的开头。忽略多行模式。

def re_A():
    oris = 'abc'
    oris1 = 'abc\n123'
    valid = re.compile(r'\A')
    valid1 = re.compile(r'\A',re.M)
    print(re.match(valid,oris))
    print(re.match(valid, oris1))
    print(re.match(valid1, oris))
    print(re.match(valid1, oris1))
# 输出结果
<re.Match object; span=(0, 0), match=''>
<re.Match object; span=(0, 0), match=''>
<re.Match object; span=(0, 0), match=''>
<re.Match object; span=(0, 0), match=''>

\b

匹配空字符串,但仅匹配单词的开头或结尾。单词被定义为单词字符序列。注意,正式地, \b定义为一个 \w\W字符之间的边界(反之亦然),或者在\w字符串的开头/结尾之间。这意味着,r'\bfoo\b'匹配'foo''foo.''(foo)''bar foo baz', 但不匹配'foobar','foo3'

默认情况下,Unicode字母数字是Unicode模式中使用的字母数字,但可以通过使用ASCII标志来更改。如果使用该LOCALE标志,则字边界由当前区域设置确定。在字符范围内,\b表示退格符,以便与Python的字符串文字兼容。

\B

匹配空字符串,但仅当它不在单词的开头或结尾时。这意味着,r'py\B'匹配'python''py3''py2',而不是'py''py.''py!'\B正好相反\b,因此Unicode模式中的单词字符是Unicode字母数字或下划线,尽管可以通过使用ASCII标志来更改。如果使用该LOCALE标志,则字边界由当前区域设置确定。

\d

1.对于Unicode(str)模式:
匹配任何Unicode十进制数字(即Unicode字符类别[Nd]中的任何字符)。这包括[0-9],还有许多其他数字字符。如果使用ASCII该标志,仅匹配[0-9]

2.对于8-bit(字节)模式:
匹配任何十进制数字; 这相当于[0-9]

def re_d():
    oris = '123'
    oris1 = '123\u0031\uff11\uff12'
    valid = re.compile(r'\d')
    valid1 = re.compile(r'\d',re.A)
    print(re.findall(valid, oris))
    print(re.findall(valid, oris1))
    print(re.findall(valid1, oris1))
# 输出结果
['1', '2', '3']
['1', '2', '3', '1', '1', '2']
['1', '2', '3', '1']

上面的\uff11\ff12为全角阿拉伯数字1,2,在这里也会被匹配到,所有可以指定re.A标志,只保留ascii的数字。

\D

匹配任何非十进制数字的字符。这与之相反\d。如果使用ASCII标志,则相当于[^0-9]

def re_D():
    oris = 'abc123'
    oris1 = 'abc123\u0031\x31\uff11\uff12'

    valid = re.compile(r'\D')
    valid1 = re.compile(r'\D',re.A)
    print(re.findall(valid, oris))
    print(re.findall(valid, oris1))
    print(re.findall(valid1, oris1))
# 输出结果
['a', 'b', 'c']
['a', 'b', 'c']
['a', 'b', 'c', '1', '2']

\s

1.对于Unicode(str)模式:
匹配Unicode空白字符(其中包括[ \t\n\r\f\v] 和许多其他字符,例如许多语言中排版规则强制要求的非破坏性空格)。如果使用ASCII标志,则仅 匹配[ \t\n\r\f\v]

2.对于8-bit(字节)模式:
匹配只考虑ASCII字符集中的空格字符; 这相当于[ \t\n\r\f\v]

def re_s():
    oris = '\tabc 123\nend\r123'
    oris1 = '\tabc 123\nend\r123\u00a0\u0020\u3000'

    valid = re.compile(r'\s')
    valid1 = re.compile(r'\s',re.A)
    print(re.findall(valid, oris))
    print(re.findall(valid, oris1))
    print(re.findall(valid1, oris))
    print(re.findall(valid1, oris1))
# 输出结果
['\t', ' ', '\n', '\r']
['\t', ' ', '\n', '\r', '\xa0', ' ', '\u3000']
['\t', ' ', '\n', '\r']
['\t', ' ', '\n', '\r', ' ']

1.不间断空格\u00A0,主要用在office中,让一个单词在结尾处不会换行显示,快捷键ctrl+shift+space ;
2.半角空格(英文符号)\u0020,代码中常用的;
3.全角空格(中文符号)\u3000,中文文章中使用;

\S

匹配任何不是空格字符的字符。这与\s相反。如果使用ASCII标志,则相当于。[^ \t\n\r\f\v]

def re_S():
    oris = '\tabc 123\nend\r123'
    oris1 = '\tabc 123\nend\r123\u00a0\u0020\u3000'

    valid = re.compile(r'\S')
    valid1 = re.compile(r'\S',re.A)
    print(re.findall(valid, oris))
    print(re.findall(valid, oris1))
    print(re.findall(valid1, oris))
    print(re.findall(valid1, oris1))
# 输出结果
['a', 'b', 'c', '1', '2', '3', 'e', 'n', 'd', '1', '2', '3']
['a', 'b', 'c', '1', '2', '3', 'e', 'n', 'd', '1', '2', '3']
['a', 'b', 'c', '1', '2', '3', 'e', 'n', 'd', '1', '2', '3']
['a', 'b', 'c', '1', '2', '3', 'e', 'n', 'd', '1', '2', '3', '\xa0', '\u3000']

\w

1.对于Unicode(str)模式:
匹配Unicode字符; 这包括大多数可以成为任何语言单词的一部分的字符,以及数字和下划线。如果使用ASCII标志,则仅匹配 [a-zA-Z0-9_]
2.对于8位(字节)模式:
匹配ASCII字符集中被认为是字母数字的字符; 这相当于[a-zA-Z0-9_]。如果使用LOCALE标志,则匹配当前语言环境和下划线中被视为字母数字的字符。

def re_w():
    oris = 'eE_2中!?('
    
    valid = re.compile(r'\w')
    valid1 = re.compile(r'\w',re.A)
    print(re.findall(valid, oris))
    print(re.findall(valid1, oris))
# 输出结果
['e', 'E', '_', '2', '中']
['e', 'E', '_', '2']

\W

匹配任何不是单词字符的字符。这与\w相反。如果使用ASCII标志,则相当于[^a-zA-Z0-9_]。如果使用LOCALE标志,则匹配当前语言环境和下划线中被视为字母数字的字符。

def re_W():
    oris = 'eE_2中!?('

    valid = re.compile(r'\W')
    valid1 = re.compile(r'\W',re.A)
    print(re.findall(valid, oris))
    print(re.findall(valid1, oris))
# 输出结果
['!', '?', '(']
['中', '!', '?', '(']

\Z

仅匹配字符串末尾的匹配项。忽略多行模式。


正则表达式解析器也接受Python字符串文字支持的大多数标准转义:
\a      \b      \f      \n
\r      \t      \u      \U
\v      \x      \\

(注意,\b它用于表示单词边界,并且仅在字符类中表示“退格”。)

'\u''\U'转义序列只能在Unicode模式中识别。在字节模式中,它们是错误的。

八进制转义包含在有限的形式中。如果第一个数字是0,或者如果有三个八进制数字,则认为它是八进制数。否则,它是一个组引用。对于字符串文字,八进制转义的长度最多为三位数。

在3.3版本更改:'\u''\U'转义序列已被添加。

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

2. re模块的方法

该模块定义了几个函数,常量和异常。一些函数是编译正则表达式的全功能方法的简化版本。大多数重要的应用程序总是使用compiled 形式。

Python 3.6 变更:标志常量现在是RegexFlag实例,它是的enum.Int的子类 。

re.compile(pattern, flags=0)

编译一个正则表达式模式为 一个正则表达式对象,其可用于match(),search()以及其他方法,说明如下。

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

序列

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

相当于
result = re.match(pattern, string)

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

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

re.A / re.ASCII

\w,\W,\b,\B,\d,\D,\s和\S 只匹配ASCII字符,而不匹配整个的Unicode字符。这仅对Unicode模式有意义,对于字节模式将被忽略。对应于内联标志(?a)

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

re.DEBUG

显示有关已编译表达式的调试信 。没有相应的内联标志。

re.I / re.IGNORECASE

执行不区分大小写的匹配; 表达式[A-Z]也会匹配小写字母。除非使用re.ASCII标志禁用非ASCII匹配,否则完全Unicode匹配(例如Ü匹配 ü)也有效。除非使用re.LOCALE标志,否则当前区域设置不会更改此标志的效果。对应于内联标志(?i)

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

re.L / re.LOCALE

让\w,\W,\b,\B和区分大小写的匹配取决于当前的语言环境。该标志只能用于字节模式。不鼓励使用此标志,因为语言环境机制非常不可靠,它一次只处理一个“文化”,它只适用于8-bit 语言环境。默认情况下,Python 3中已经为Unicode(str)模式启用了Unicode匹配,并且它能够处理不同的语言环境/语言。对应于内联标志(?L)

在版本3.6中更改:re.LOCALE仅可用于字节模式且与re.ASCII不兼容。

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

re.M / re.MULTILINE

指定时,模式字符'^'匹配字符串的开头和每行的开头(紧跟在每个换行符之后); 并且模式字符'$'匹配字符串的末尾和每行的结尾(紧接在每个换行符之前)。默认情况下,'^' 仅匹配字符串的开头,并且'$'仅匹配字符串的结尾,紧接在字符串末尾的换行符(如果有)之前。对应于内联标志(?m)

re.S / re.DOTALL

使'.'特殊字符与任何字符匹配,包括换行符; 没有此标志,'.'将匹配除换行符之外的任何内容。对应于内联标志(?s)

re.X / re.VERBOSE

此标志允许您编写看起来更好且更易读的正则表达式,允许您在视觉上分离模式的逻辑部分并添加注释。在该模式内的空白被忽略,除了在字符串内,或处于反斜杠之后,或token(类似*?(?:(?P<...>)内。当一行包含一个#不在字符类中并且前面没有非转义反斜杠时,将忽略从最左边到行尾的所有字符 。对应于内联标志(?x)

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

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

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

扫描字符串,查找正则表达式模式生成匹配项的第一个位置 ,并返回相应的匹配对象( match object)。如果字符串中没有位置与模式匹配则返回None; 请注意,这与在字符串中的某个点找到零长度匹配不同。


def re_search():
    oris = 'hello\nMr\nleng'
    valid = re.compile(r'\Aleng$')

    print(re.search(valid, oris))
    valid2 = re.compile(r'leng$')
    print(re.search(valid2, oris))
# 输出结果
None
<re.Match object; span=(9, 13), match='leng'>

译者注:从上面的例子也能看出search扫描整个字符串,那也就意味着,可以跨多行查找。但是与跨行查找不同的是将多行当做一行,所以总结起来就是忽略多行的换行符。

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

如果字符串开头的零个或多个字符与正则表达式模式匹配,则返回相应的匹配对象。如果字符串与模式不匹配则返回None; 请注意,这与零长度匹配不同。

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

如果要在字符串中的任何位置找到匹配项, 请改为使用search()(另请参阅search()vs match())。

def re_match():
    oris = 'hello\nhello\nleng'

    valid = re.compile(r'h.*',re.M)
    print(re.match(valid, oris))
    valid = re.compile(r'leng', re.M)
    print(re.match(valid, oris))
# 输出结果
<re.Match object; span=(0, 5), match='hello'>
None

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

如果整个字符串与正则表达式模式匹配,则返回相应的匹配对象。如果字符串与模式不匹配则返回None; 请注意,这与零长度匹配不同。

版本3.4中的新功能。

def re_full_match():
    oris = 'hellohelloleng\nleng'

    valid = re.compile(r'hello',re.M)
    print(re.match(valid,oris))

    print(re.fullmatch(valid, oris))
    valid = re.compile(r'hellohelloleng[\n]leng', re.M)
    print(re.fullmatch(valid, oris))
# 输出结果
<re.Match object; span=(0, 5), match='hello'>
None
<re.Match object; span=(0, 19), match='hellohelloleng\nleng'>

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

按模式的出现拆分字符串。如果在模式中使用捕获括号,则模式中所有组的文本也将作为结果列表的一部分返回。如果maxsplit非零,则最多发生maxsplit次拆分,并且字符串的其余部分将作为列表的最后一个元素返回。

>>> 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中已更改:添加了对可能与空字符串匹配的模式进行拆分的支持。

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

返回的所有非重叠的匹配模式的字符串,作为字符串列表返回。该字符串进行扫描左到右,并匹配以发现的顺序返回。如果模式中存在一个或多个组,则返回组列表; 如果模式有多个组,这将是一个元组列表。结果中包含空匹配。

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

def re_findall():
    oris = 'hello111helloleng12\nleng'

    valid = re.compile(r'(hello)(.*)(\1)')
    print(re.findall(valid,oris))
    valid = re.compile(r'(?P<name>leng)(.*)(?P=name)',re.DOTALL)
    print(re.findall(valid, oris))
# 输出结果
[('hello', '111', 'hello')]
[('leng', '12\n')]

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

返回一个迭代器,在字符串中的RE 模式的所有非重叠匹配上产生匹配对象。该字符串 进行从左到右扫描,并以发现的顺序返回匹配结果。结果中包含空匹配。

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

def re_finditer():
    oris = 'hello111helloleng12\nleng'

    valid = re.compile(r'leng',re.DOTALL)
    print(re.finditer(valid,oris))
    
    for m in re.finditer(valid,oris):
        print(m)
# 输出结果
<callable_iterator object at 0x00C39F70>
<re.Match object; span=(13, 17), match='leng'>
<re.Match object; span=(20, 24), match='leng'>

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

返回通过替换repl替换字符串中最左边的非重叠模式而获得的字符串。如果未找到模式, 则返回字符串不变。 repl可以是字符串或函数; 如果它是一个字符串,则处理其中的任何反斜杠转义。也就是说,\n转换为单个换行符,\r转换为回车符,依此类推。未知的转义,如\&独自留下。反向引用(例如\6)将替换为模式中第6组匹配的子字符串。例如:

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

译者实例:

def re_sub():
    orsi ='[email protected]'
    email_re = re.compile(r'(<)?((\w+)(@\w+(?:\.\w+)+))(?(1)>|$)')
    print('-'.center(30,'-'))
    print(re.match(email_re,orsi).groups())
    print(re.match(email_re, orsi).group(0))
    print(re.match(email_re, orsi).group(1))
    print(re.match(email_re, orsi).group(2))
    print(re.match(email_re, orsi).group(3))
    print(re.match(email_re, orsi).group(4))
    print('-'.center(30, '-'))

    repl = r'\3xx\4'
    print(re.sub(email_re,repl,orsi))
# 输出结果
------------------------------
('<', '[email protected]', 'lengfengyuyu', '@csdn.net')
<lengfengyuyu@csdn.net>
<
lengfengyuyu@csdn.net
lengfengyuyu
@csdn.net
------------------------------
lengfengyuyuxx@csdn.net

repl参数中使用了第三个和第四个分组,为邮件重命名。

如果repl是一个函数,则会为每个非重叠的模式调用调用它 。该函数接受单个匹配对象参数,并返回替换字符串。例如:

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

def re_sub2():
    def replp(mobj):
        if mobj.group(3) == 'lengfengyuyu':
            return mobj.group(3)+"xx"+mobj.group(4)
    orsi ='<[email protected]>'
    email_re = re.compile(r'(<)?((\w+)(@\w+(?:\.\w+)+))(?(1)>|$)')
# 输出结果
lengfengyuyuxx@csdn.net

模式可以是字符串或模式对象。

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

在字符串型repl参数中,除了上面描述的字符转义和反向引用之外, \g<name>还将使用由(?P<name>...)语法定义的命名组匹配的name子字符串。\g<number>使用相应的组号; 因此,\g<2>等同于\2\20将被解释为对组20的引用,而不是对组2的引用,后跟文字字符’0’。反向引用\g<0>替代了RE匹配的整个子字符串。

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

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

在3.6版本的改变:pattern参数中'\'与未知的ASCII字母转义会出现的错误。

在3.7版本的改变:repl参数中'\'与未知的ASCII字母转义会出现的错误。

当与先前的非空匹配相邻时,替换pattern的空匹配。

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

执行和sub()相同的操作,但返回元组(new_string, number_of_subs_made)

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

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

re.escape(pattern)

转义模式中的特殊字符。如果要匹配可能包含正则表达式元字符的任意文字字符串,这将非常有用。例如:

>>> print(re.escape('python.exe'))
python\.exe

>>> 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()中的repl参数,只应转义反斜杠。例如:

>>>
>>> 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中已更改:仅转义在正则表达式中具有特殊含义的字符。

re.purge()

清除正则表达式缓存。

exception re.error(msg, pattern=None, pos=None)

传递给此处其中一个函数的字符串不是有效的正则表达式(例如,它可能包含不匹配的括号)或在编译或匹配期间发生其他错误时引发的异常。如果字符串不包含模式匹配,则永远不会出错。错误实例具有以下附加属性:

msg
未格式化的错误消息。

pattern
正则表达式模式。

pos
编译失败的模式中的索引(可能是None)。

lineno
对应于pos的行(可能是None)。

colno
对应于pos的列(可以是None)。

版本3.5中已更改:添加了其他属性。

3. 正则表达式对象

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

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

扫描字符串,查找此正则表达式生成匹配项的第一个位置,并返回相应的匹配对象。如果字符串中没有位置与模式匹配则返回None; 请注意,这与在字符串中的某个点找到零长度匹配不同。

可选的第二个参数pos给出了搜索开始的字符串中的索引; 它默认为0。这并不完全等同于切割字符串; ^模式字符在字符串的真正开始,或一个新行的开始的位置相匹配,但不一定是search开始搜索的位置。

可选参数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"
None

Pattern.match(string[, pos[, endpos]])

如果字符串开头的零个或多个字符与此正则表达式匹配,则返回相应的匹配对象。如果字符串与模式不匹配则返回None; 请注意,这与零长度匹配不同。

可选的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'>

如果要在字符串中的任何位置找到匹配项,请改为使用search() 。

Pattern.fullmatch(string[, pos[, endpos]])

如果整个字符串与此正则表达式匹配,则返回相应的 匹配对象。如果字符串与模式不匹配则返回None; 请注意,这与零长度匹配不同。

可选的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中的新功能。

Pattern.split(string, maxsplit=0)

与split()函数相同,使用编译的模式。

Pattern.findall(string[, pos[, endpos]])

与findall()函数类似,使用编译的模式,但也接受可选的pos和endpos参数,限制搜索区域,如search()。

Pattern.finditer(string[, pos[, endpos]])

与finditer()函数类似,使用编译的模式,但也接受可选的pos和endpos参数,限制搜索区域,如search()。

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

与sub()函数相同,使用编译的模式。

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

与subn()函数相同,使用编译的模式。

Pattern.flags

正则表达式匹配标志。这是给compile()的标志,可以是任何(?...)内联标志和隐式标志的组合,例如UNICODE模式是否为Unicode字符串。

Pattern.groups

模式中捕获组的数量。

Pattern.groupindex

字典映射由(?P<id>)组号定义的任何符号组名称。如果模式中没有使用符号组,则字典为空。

Pattern.pattern

编译模式对象的模式字符串。

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

4.匹配对象

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

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

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

Match.group([group1, …])

功能:返回匹配到的一个或多个子组。
(1)如果只给了group函数一个参数,则返回的结果为单个字符串;
(2)如果给了group函数多个参数,则返回的结果为一个元组,元组长度匹配提供的参数的个数。
(3)如不提供参数,group1参数默认为零(返回整个匹配)。
(4)如果groupN参数为零,则相应的返回值是整个匹配字符串;
(5) 如果N在范围1-99中,则它是与相应的带括号的组匹配的字符串。如果N为负数或大于模式中定义的组数,则会引发IndexError异常。
(6)如果一个组是pattern的一部分,并且pattern没找到匹配,那么结果就是None
(7)如果一个组是包含在多次匹配的模式中的一部分,则返回最后一个匹配项。

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

(8)如果正则表达式使用(?P<name>...)语法,则groupN 参数也可以是按组名称标识组的字符串。如果字符串参数未在模式中用作组名, 则会引发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'

命名组也可以通过其索引引用:

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

如果一个组匹配多次,则只能访问最后一个匹配(针对第7条):

>>>
>>> m = re.match(r"(..)+", "a1b2c3")  # Matches 3 times.
>>> m.group(1)                        # Returns only the last match.
'c3'

译者实例

def mo_group():
    oris = '[email protected]'
    valid = re.compile(r'(\d+)(?P<username>.*)(@)\1(.)+',re.DOTALL)
    m = re.match(valid,oris)
    if m:
        print(m.groups())
        print("m:", m.group())
        print("m0:",m.group(0))
        print("m1:",m.group(1))
        print("m2:", m.group(2))
        print("m3:", m.group(3))
        print("m4:", m.group(4)) # .匹配`.com`四次,所以只显示最后一个匹配m
        # print("m5:",m.group(5)) # 报错,因为5不存在
        print("m[username]:", m.group("username"))
        print(m.group(1,2,3,4))
    else:
        print("wow,no thing mathc!!!")
# 输出结果
('163', 'lengfyy', '@', 'm')
m: 163lengfyy@163.com
m0: 163lengfyy@163.com
m1: 163
m2: lengfyy
m3: @
m4: m
m[username]: lengfyy
('163', 'lengfyy', '@', 'm')

Match.expand(template)

功能:使用match对象中的组编号,对一个新字符串(template)进行替换和转义,返回替换后的字符串,如sub()方法所做。
变化:版本3.5中做出更改,不匹配的组将替换为空字符串。
(1)如\n会被转义为适当字符,数字反向引用(\1\2)和命名反向引用(\g<1>\g<name>)将被相应组的内容替换。

例子(在Match.group方法中例子的基础上):

def mo_expand():
    template = r'\g<username>loveqian\3\1.com'
    print(m.expand(template))
# 输出结果
lengfyyloveqian@163.cn

在此例子中,我们在template字符串中使用了分组\1,\3\g<username>注意使用r前缀。

Match.__getitem__(g)

功能:这与m.group方法相同
变化:版本3.6中的新功能
这样可以更轻松地从匹配中访问单个组:

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

译者注:如果你定义了(?P<username>...)并且匹配到了结果,也可以使用m['username']来访问匹配到的结果。

Match.groups(default=None)

功能:返回包含匹配的所有子组的元组。
(1)default参数用于那些没有参加匹配的组; 默认显示为None

例如:

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

如果我们创建小数位并且后面的所有内容都是可选的,那么并非所有组都可以参与匹配。除非给出默认参数,否则这些组将默认为None(针对第1点):

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

译者实例:

def re_groups():
    oris1 = '[email protected]'
    valid1 = re.compile(r'(\d+)(?P<con>.*)(@)\1(\d)?(\d)?', re.DOTALL)
    m1 = re.match(valid1, oris1)
    print(m1.groups())
    print(m1.groups('不存在'))
# 输出结果
('163', 'lengfyy', '@', None, None)
('163', 'lengfyy', '@', '不存在', '不存在')

Match.groupdict(default=None)

功能:返回包含匹配的所有已命名子组的字典,由子组名作为键。
(1)默认参数用于那些没有参加匹配的组; 它默认为None。例如:

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

译者例子:


def re_dict():
    oris1 = '[email protected]'
    valid1 = re.compile(r'(?P<title>\d+)(?P<username>.*)(?P<at>@)(?P=title)(?P<d1>\d)?(?P<d2>\d)?', re.DOTALL)
    m1 = re.match(valid1, oris1)
    print(m1.groupdict())
    print(m1.groupdict('不存在'))
# 输出结果
{'title': '163', 'username': 'lengfyy', 'at': '@', 'd1': None, 'd2': None}
{'title': '163', 'username': 'lengfyy', 'at': '@', 'd1': '不存在', 'd2': '不存在'}

Match.start([group])

Match.end([group])

功能:返回由group匹配的子字符串的在原字符串中开始和结束的索引;
(1)group默认为零(表示整个匹配的子字符串)。
(2)如果组存在,但无助于匹配,则返回-1。
(3)对有助于匹配的对象m和组g,m.group(g)等价于
string[m.start(g):m.end(g)]

(4)请注意,如果组匹配到空字符串,则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():]
'[email protected]'

译者实例

def re_start_end():
    oris = '[email protected]'
    valid = re.compile(r'(\d+)(?P<con>.*)(@)\1(\d)?',re.DOTALL)
    m = re.match(valid,oris)
    if m:
        print("m1:",m.group(1))
        print("m2:", m.group(2))
        print("m4:", m.group(4))
        print("m1-s-e :",m.start(1),m.end(1))
        print("m2-s-e :", m.start(2), m.end(2))
        print("m4-s-e :", m.start(4), m.end(4))
    else:
        print("wow,no thing mathc!!!")
# 输出结果
m1: 163
m2: lengfyy
m4: None
m1-s-e : 0 3
m2-s-e : 3 10
m4-s-e : -1 -1

Match.span([group])

功能:对于匹配m,返回范围2元组(m.start(group), m.end(group))
(1)请注意,如果group没有为匹配做出贡献,那么就会返回(-1, -1)
(2)group默认为零,整个匹配。

Match.pos

功能:传递给search() 和match()方法的正则表达式对象的pos值。
这是RE引擎开始寻找匹配项的字符串索引。

Match.endpos

功能:传递给search() 和match()方法的正则表达式对象的endpos值。
这是RE引擎不会超出的字符串索引。

Match.lastindex

功能:返回最后匹配的组的整数索引,如果根本没有匹配的组,返回None
例如,用模式(a)b((a)(b))以及((ab))去匹配'ab',会有lastindex == 1,如果模式是(a)(b),会有lastindex == 2
从内向外。

Match.lastgroup

最后匹配的捕获组的名称,如果组没有名称,或者根本没有匹配的组返回None

Match.re

正则表达式对象,match()或 search()方法使用这个对象来匹配实例。

Match.string

传递给match()或search()的字符串。
变化:版本3.7中做出更改,添加了对copy.copy()和的支持copy.deepcopy()。匹配对象被视为原子。

5. 正则表达式例子

5.1 配对检查

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

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

假设您正在编写扑克程序,其中玩家的手被表示为5个字符的字符串,每个字符代表一张牌,“a”代表A,“k”代表K,“q”代Q,“j”代表J, “t”代表10,2~9就是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 = 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',)>"

要找出该对包含哪个卡,可以按以下方式使用group()匹配对象的方法:

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

5.2 模拟scanf()

Python目前没有等效的scanf()。正则表达式通常比scanf()格式字符串更强大,但也更冗长 。下表提供了scanf()格式标记和正则表达式之间的一些或多或少的等效映射。

scanf() 字符 正则表达式
%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

5.3 search和match

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

例如:


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

5.4 制作电话簿

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

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

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

最后,将每个条目拆分为包含名字,姓氏,电话号码和地址的列表。因为地址有空格,我们使用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']]

:?模式匹配last name之后的冒号,这样才不会在结果列表中出现。随着maxsplit=4,我们将街道名称和门牌号码分开:

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

5.5 打乱文本

sub()用字符串或函数的结果替换模式的每次出现。此示例演示如何使用sub()函数来 “打乱”文本,或者随机化句子中每个单词中除第一个和最后一个字符之外的所有字符的顺序:

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

5.6 找到所有副词

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

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

5.7 查找所有副词及其位置

如果想要获得关于模式的所有匹配的更多信息而不是匹配的文本,finditer()则有用,因为它提供匹配对象而不是字符串。继续前面的例子,如果作家想要在某些文本中找到所有副词及其位置,他们将按以下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

5.8 原始字符串表示法

原始字符串表示法(r"text")保持正则表达式原意。没有它,正则表达式中的每个反斜杠(\)都必须以另一个为前缀来转义它。例如,以下两行代码在功能上是相同的:

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

当想要匹配文字反斜杠时,必须在正则表达式中对其进行转义。使用原始字符串表示法,这意味着r"\\"。如果没有原始字符串表示法,必须使用"\\\\",使以下代码行功能相同:

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

5.9 编写一个标记生成器

一个标记生成器或扫描仪 对一个分析的字符串进行分组。这是编写编译器或解释器的有用的第一步。

文本类别使用正则表达式指定。该技术是将它们组合成单个主正则表达式并循环连续匹配:

import collections
import re

Token = collections.namedtuple('Token', ['type', '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()
        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)

猜你喜欢

转载自blog.csdn.net/lengfengyuyu/article/details/84638714