Python核心编程——第1章 正则表达式 笔记

  • 第 1 章 正则表达式
    • 1.1 简介/动机
    • 1.2 特殊符号和字符
      • 常见正则表达式符号和特殊字符
      • 1.2.1 使用择一匹配符号匹配多个正则表达式模式
      • 1.2.2 匹配任意单个字符
      • 1.2.3 从字符串起始或者结尾或者单词边界匹配
      • 1.2.4 创建字符集
      • 1.2.5 限定范围和否定
      • 1.2.6 使用闭包操作符实现存在性和频数匹配
      • 1.2.7 表示字符集的特殊字符
      • 1.2.8 使用圆括号指定分组
      • 1.2.9 扩展表示法
    • 1.3 正则表达式和 Python 语言
      • re 模块:核心函数和方法
      • Python代码

第 1 章 正则表达式

1.1 简介/动机

1.2 特殊符号和字符

常见正则表达式符号和特殊字符

表 示 法 描 述 正则表达式示例
符号
literal 匹配文本字符串的字面值 literal foo
re1|re2 匹配正则表达式 re1 或者 re2 foo|bar
. 匹配任何字符(除了\n 之外) b.b
^ 匹配字符串起始部分 ^Dear
$ 匹配字符串终止部分 /bin/*sh$
* 匹配 0 次或者多次前面出现的正则表达式 [A-Za-z0-9]*
+ 匹配 1 次或者多次前面出现的正则表达式 [a-z]+.com
? 匹配 0 次或者 1 次前面出现的正则表达式 goo?
{N} 匹配 N 次前面出现的正则表达式 [0-9]{3}
{M,N} 匹配 M~N 次前面出现的正则表达式 [0-9]{5,9}
[…] 匹配来自字符集的任意单一字符 [aeiou]
[..x−y..] 匹配 x~y 范围中的任意单一字符 [0-9], [A-Za-z]
[\^…] 不匹配此字符集中出现的任何一个字符,包括某一范围的字符(如果在此字符集中出现) [\^aeiou], [\^A-Za-z0-9]
(*|+|?|{})? 用于匹配上面频繁出现/重复出现符号的非贪婪版本(*、 +、 ?、 {}) .*?[a-z]
(…) 匹配封闭的正则表达式,然后另存为子组 ([0-9]{3})?,f(oo
特殊字符
\d 匹配任何十进制数字,与[0-9]一致(\D 与\d 相反,不匹配任何非数值型的数字) data\d+.txt
\w 匹配任何字母数字字符,与[A-Za-z0-9_]相同(\W 与之相反) [A-Za-z_]\w+
\s 匹配任何空格字符,与[\n\t\r\v\f]相同(\S 与之相反) of\sthe
\b 匹配任何单词边界(\B 与之相反) \bThe\b
\N 匹配已保存的子组 N(参见上面的(…)) price: \16
\c 逐字匹配任何特殊字符 c(即,仅按照字面意义匹配,不匹配特殊含义) \., \\, \*
\A(\Z) 匹配字符串的起始(结束)(另见上面介绍的^和$) \ADear
扩展表示法
(?iLmsux) 在正则表达式中嵌入一个或者多个特殊“标记” 参数(或者通过函数/方法) (?x),(?im)
(?:…) 表示一个匹配不用保存的分组 (?:\w+.)*
(?P…) 像一个仅由 name 标识而不是数字 ID 标识的正则分组匹配 (?P)
(?P=name) 在同一字符串中匹配由(?P (?P=data)
(?#…) 表示注释,所有内容都被忽略 (?#comment)
(?=…) 匹配条件是如果…出现在之后的位置,而不使用输入字符串;称作正向前视断言 (?=.com)
(?!…) 匹配条件是如果…不出现在之后的位置,而不使用输入字符串;称作负向前视断言 (?!.net)
(?<=…) 匹配条件是如果…出现在之前的位置,而不使用输入字符串;称作正向后视断言 (?<=800-)
(? 匹配条件是如果…不出现在之前的位置,而不使用输入字符串;称作负向后视断言 (?
(?(id\/name)Y|N ) 如果分组所提供的 id 或者 name(名称)存在,就返回正则表达式的条件匹配 Y,如果不存在,就返回 N; |N 是可选项 (?(1)y|x)

1.2.1 使用择一匹配符号匹配多个正则表达式模式

正则表达式模式 匹配的字符串
at|home at、 home
r2d2|c3po r2d2、 c3po
bat|bet|bit bat、 bet、 bit

1.2.2 匹配任意单个字符

正则表达式模式 匹配的字符串
f.o 匹配在字母“f”和“o”之间的任意一个字符;例如 fao、 f9o、 f#o 等
.. 任意两个字符
.end 匹配在字符串 end 之前的任意一个字符

1.2.3 从字符串起始或者结尾或者单词边界匹配

正则表达式模式 匹配的字符串
^From 任何以 From 作为起始的字符串
/bin/tcsh$ 任何以/bin/tcsh 作为结尾的字符串
^Subject: hi$ 任何由单独的字符串 Subject: hi 构成的字符串
the 任何包含 the 的字符串
\bthe 任何以 the 开始的字符串
\bthe\b 仅仅匹配单词 the
\Bthe 任何包含但并不以 the 作为起始的字符串

1.2.4 创建字符集

正则表达式模式 匹配的字符串
b[aeiu]t bat、 bet、 bit、 but
[cr][23][dp][o2] 一个包含四个字符的字符串,第一个字符是“c”或“r”,然后是“2”或“3”,后面是“d”或“p”,最后要么是“o”要么是“2”。例如, c2do、 r3p2、 r2d2、 c3po 等

1.2.5 限定范围和否定

正则表达式模式 匹配的字符串
z.[0-9] 字母“z”后面跟着任何一个字符,然后跟着一个数字
[r-u][env-y][us] 字母“r”、“s”、“t”或者“u”后面跟着“e”、“n”、“v”、“w”、“x”或者“y”,然后跟着“u”或者“s”
[^aeiou] 一个非元音字符(练习:为什么我们说“非元音”而不是“辅音”?)
[^\t\n] 不匹配制表符或者\n
[“-a] 在一个 ASCII 系统中,所有字符都位于“”和“a”之间,即 34~97 之间

1.2.6 使用闭包操作符实现存在性和频数匹配

星号或者星号操作符(*)将匹配其左边的正则表达式出现零次或者多次的情况(在计算机编程语言和编译原理中,该操作称为 Kleene 闭包)。加号(+)操作符将匹配一次或者多次出现的正则表达式(也叫做正闭包操作符),问号(?)
操作符将匹配零次或者一次出现的正则表达式。
还有大括号操作符({}),里面或者是单个值或者是一对由逗号分隔的值。这将最终精确地匹配前面的正则表达式 N 次(如果是{N})或者一定范围的次数;例如, {M, N}将匹配 M~N 次出现。这些符号能够由反斜线符号转义; *匹配星号,等等。
注意,在之前的表格中曾经多次使用问号(重载), 这意味着要么匹配 0 次,要么匹配 1次,或者其他含义:如果问号紧跟在任何使用闭合操作符的匹配后面, 它将直接要求正则表达式引擎匹配尽可能少的次数。
“尽可能少的次数”是什么意思?当模式匹配使用分组操作符时,正则表达式引擎将试图“吸收”匹配该模式的尽可能多的字符。这通常被叫做贪婪匹配。问号要求正则表达式引擎去“偷懒”,如果可能,就在当前的正则表达式中尽可能少地匹配字符,留下尽可能多的字符给后面的模式(如果存在)。

正则表达式模式 匹配的字符串
[dn]ot? 字母“d”或者“n”,后面跟着一个“o”,然后是最多一个“t”,例如, do、 no、 dot、 not
0?[1-9] 任何数值数字, 它可能前置一个“0”,例如, 匹配一系列数(表示从 1~9 月的数值),不管是一个还是两个数字
[0-9]{15,16} 匹配 15 或者 16 个数字(例如信用卡号码)
]+> 匹配全部有效的(和无效的) HTML 标签
[KQRBNP][a-h][1-8]-[a-h][1-8] 在“长代数”标记法中,表示国际象棋合法的棋盘移动(仅移动,不包括吃子和将军)。即“K”、“Q”、“R”、“B”、“N”或“P”等字母后面加上“a1”~“h8”之间的棋盘坐标。前面的坐标表示从哪里开始走棋,后面的坐标代表走到哪个位置(棋格)上

1.2.7 表示字符集的特殊字符

正则表达式模式 匹配的字符串
\w+-\d+ 一个由字母数字组成的字符串和一串由一个连字符分隔的数字
[A-Za-z]\w* 第一个字符是字母;其余字符(如果存在)可以是字母或者数字
\d{3}-\d{3}-\d{4} 美国电话号码的格式,前面是区号前缀,例如 800-555-1212
\w+@\w+.com [email protected] 格式表示的简单电子邮件地址

1.2.8 使用圆括号指定分组

正则表达式模式 匹配的字符串
\d+(.\d*)? 表示简单浮点数的字符串;也就是说,任何十进制数字,后面可以接一个小数点和零个或者多个十进制数字,例如“0.004”、“2”、“75.”等
(Mr?s?.)?[A-Z][a-z]*[A-Za-z-]+ 名字和姓氏,以及对名字的限制(如果有,首字母必须大写,后续字母小写),全名前可以有可选的“Mr.”、“Mrs.”、“Ms.”或者“M.”作为称谓,以及灵活可选的姓氏,可以有多个单词、 横线以及大写字母

1.2.9 扩展表示法

正则表达式模式 匹配的字符串
(?:\w+.)* 以句点作为结尾的字符串,例如“google.”、“twitter.”、“facebook.”,但是这些匹配不会保存下来供后续的使用和数据检索
(?#comment) 此处并不做匹配,只是作为注释
(?=.com) 如果一个字符串后面跟着“.com”才做匹配操作,并不使用任何目标字符串
(?!.net) 如果一个字符串后面不是跟着“.net”才做匹配操作
(?<=800-) 如果字符串之前为“800-”才做匹配,假定为电话号码, 同样,并不使用任何输入字符串
(? 如果一个字符串之前不是“192.168.”才做匹配操作,假定用于过滤掉一组 C 类 IP 地址
(?(1)y|x) 如果一个匹配组 1(\1)存在, 就与 y 匹配; 否则, 就与 x 匹配

1.3 正则表达式和 Python 语言

re 模块:核心函数和方法

函数/方法 描 述
仅仅是 re 模块函数
compile(pattern, flags = 0) 使用任何可选的标记来编译正则表达式的模式,然后返回一个正则表达式对象
re 模块函数和正则表达式对象的方法
match(pattern, string, flags=0) 尝试使用带有可选的标记的正则表达式的模式来匹配字符串。如果匹配成功,就返回匹配对象; 如果失败,就返回 None
search(pattern, string, flags=0) 使用可选标记搜索字符串中第一次出现的正则表达式模式。 如果匹配成功,则返回匹配对象; 如果失败,则返回 None
findall(pattern, string [, flags] ) 查找字符串中所有(非重复)出现的正则表达式模式,并返回一个匹配列表
finditer(pattern, string [, flags] ) 与 findall()函数相同,但返回的不是一个列表,而是一个迭代器。 对于每一次匹配,迭代器都返回一个匹配对象
split(pattern, string, max=0) 根据正则表达式的模式分隔符, split 函数将字符串分割为列表,然后返回成功匹配的列表,分隔最多操作 max 次(默认分割所有匹配成功的位置)
re 模块函数和正则表达式对象方法
sub(pattern, repl, string, count=0) 使用 repl 替换所有正则表达式的模式在字符串中出现的位置,除非定义 count, 否则就将替换所有出现的位置(另见 subn()函数,该函数返回替换操作的数目)
purge() 清除隐式编译的正则表达式模式
常用的匹配对象方法(查看文档以获取更多信息)
group(num=0) 返回整个匹配对象,或者编号为 num 的特定子组
groups(default=None) 返回一个包含所有匹配子组的元组(如果没有成功匹配,则返回一个空元组)
groupdict(default=None) 返回一个包含所有匹配的命名子组的字典,所有的子组名称作为字典的键(如果没有成功匹配,则返回一个空字典)
常用的模块属性(用于大多数正则表达式函数的标记)
re.I、 re.IGNORECASE 不区分大小写的匹配
re.L、 re.LOCALE 根据所使用的本地语言环境通过\w、 \W、 \b、 \B、 \s、 \S 实现匹配
re.M、 re.MULTILINE ^和$分别匹配目标字符串中行的起始和结尾,而不是严格匹配整个字符串本身的起始和结尾
re.S、 rer.DOTALL “.”(点号)通常匹配除了\n(换行符)之外的所有单个字符;该标记表示“.”(点号)能够匹配全部字符
re.X、 re.VERBOSE 通过反斜线转义, 否则所有空格加上#(以及在该行中所有后续文字)都被忽略,除非在一个字符类中或者允许注释并且提高可读性

模式匹配发生之前,正则表达式模式必须编译成正则表达式对象。由于正则表达式在执行过程中将进行多次比较操作,因此强烈建议使用预编译。而且,既然正则表达式的编译是必需的,那么使用预编译来提升执行性能无疑是明智之举。 re.compile()能够提供此功能。

Python代码

import re

# 使用 match()方法匹配字符串
m = re.match('foo', 'food on the table')  # 匹配成功

# 使用 search()在一个字符串中查找模式(搜索与匹配的对比)
m = re.match('foo', 'seafood')  # 匹配失败
m = re.search('foo', 'seafood')  # 使用 search() 代替,搜索成功

# 匹配多个字符串
bt = 'bat|bet|bit'  # 正则表达式模式: bat、 bet、 bit
m = re.match(bt, 'bat')  # 'bat' 是一个匹配
m = re.match(bt, 'blt')  # 对于 'blt' 没有匹配

m = re.match(bt, 'He bit me!')  # 不能匹配字符串
m = re.search(bt, 'He bit me!')  # 通过搜索查找 'bit'

# 匹配任何单个字符
anyend = '.end'
m = re.match(anyend, 'bend')  # 点号匹配 'b'
m = re.match(anyend, 'end')  # 不匹配任何字符,结果为空

m = re.match(anyend, '\\nend')  # .匹配任何字符(除了\n 之外),结果为空

m = re.search(anyend, 'The end.')  # 在搜索中匹配 ' '

patt314 = '3.14'  # 表示正则表达式的点号
m = re.match(patt314, '3014')  # 点号匹配'0'
m = re.match(patt314, '3.14')  # 点号匹配 '.'

pi_patt = '3\.14'  # 表示字面量的点号 (dec. point)
m = re.match(pi_patt, '3.14')  # 精确匹配

# 创建字符集([ ])
m = re.match('[cr][23][dp][o2]', 'c3po')  # 匹配 'c3po'
m = re.match('[cr][23][dp][o2]', 'c2do')  # 匹配 'c2do'
m = re.match('r2d2|c3po', 'c2do')  # 不匹配 'c2do'
m = re.match('r2d2|c3po', 'r2d2')  # 匹配 'r2d2'

# 重复、特殊字符以及分组
patt = '\w+@(\w+\.)?\w+\.com'
re.match(patt, '[email protected]')
re.match(patt, '[email protected]')

patt = '\w+@(\w+\.)*\w+\.com'  # 允许任意数量的中间子域名存在。将“?”改为“*.
re.match(patt, '[email protected]')

m = re.match('\w\w\w-\d\d\d', 'abc-123')
m = re.match('\w\w\w-\d\d\d', 'abc-xyz')  # 结果为空

m = re.match('(\w\w\w)-(\d\d\d)', 'abc-123')
m.group()  # abc-123 完整匹配
m.group(0)  # abc-123 完整匹配
m.group(1)  # abc 子组 1
m.group(2)  # 123 子组 2
m.groups()  # ('abc', '123') 全部子组

m = re.match('ab', 'ab')  # 没有子组
m.group()  # ab 完整匹配
m.groups()  # () 所有子组

m = re.match('(a(b))', 'ab')  # 两个子组
m.group()  # 'ab' 完整匹配
m.group(1)  # 'ab'子组 1
m.group(2)  # 'b' 子组 2
m.groups()  # ('ab', 'b')所有子组

# 匹配字符串的起始和结尾以及单词边界
m = re.search('^The', 'The end.')  # 匹配
m = re.search('^The', 'end. The')  # 不作为起始
m = re.search(r'\bthe', 'bite the dog')  # 在边界
m = re.search(r'\bthe', 'bitethe dog')  # 有边界 ,结果为空
m = re.search(r'\Bthe', 'bitethe dog')  # 没有边界

if m is not None:
    print(m.group())
else:
    print('结果为空')

# 使用 findall()和 finditer()查找每一次出现的位置
re.findall('car', 'car')  # ['car']
re.findall('car', 'scary')  # ['car']
re.findall('car', 'carry the barcardi to the car')  # ['car', 'car', 'car']

s = 'This and that.'
re.findall(r'(th\w+) and (th\w+)', s, re.I)  # [('This', 'that')]
re.finditer(r'(th\w+) and (th\w+)', s, re.I).__next__().groups()  # ('This', 'that')
re.finditer(r'(th\w+) and (th\w+)', s, re.I).__next__().group()  # 'This and that'
re.finditer(r'(th\w+) and (th\w+)', s, re.I).__next__().group(1)  # 'This'
re.finditer(r'(th\w+) and (th\w+)', s, re.I).__next__().group(2)  # 'that'
[g.groups() for g in re.finditer(r'(th\w+) and (th\w+)', s, re.I)]  # [('This', 'that')]

# 使用 sub()和 subn()搜索与替换
re.sub('X', 'Mr. Smith', 'attn: X\n\nDear X,\n')  # 'attn: Mr. Smith\012\012Dear Mr. Smith,\012'
re.subn('X', 'Mr. Smith', 'attn: X\n\nDear X,\n')  # ('attn: Mr. Smith\012\012Dear Mr. Smith,\012', 2)
print(re.sub('X', 'Mr. Smith', 'attn: X\n\nDear X,\n'))
# attn: Mr. Smith
#
# Dear Mr. Smith,
#

re.sub('[ae]', 'X', 'abcdef')  # 'XbcdXf'
re.subn('[ae]', 'X', 'abcdef')  # ('XbcdXf', 2)
# 将美式的日期表示法MM/DD/YY{,YY}格式转换为其他国家常用的格式 DD/MM/YY{,YY}
re.sub(r'(\d{1,2})/(\d{1,2})/(\d{2}|\d{4})', r'\2/\1/\3', '2/20/91')  # '20/2/91'
re.sub(r'(\d{1,2})/(\d{1,2})/(\d{2}|\d{4})', r'\2/\1/\3', '2/20/1991')  # '20/2/1991'

# 在限定模式上使用 split()分隔字符串
re.split(':', 'str1:str2:str3')  # ['str1', 'str2', 'str3']

# 普通字符串分割
DATA = (
    'Mountain View, CA 94040',
    'Sunnyvale, CA',
    'Los Altos, 94023',
    'Cupertino 95014',
    'Palo Alto CA',
)
for datum in DATA:
    print(re.split(', |(?= (?:\d{5}|[A-Z]{2})) ', datum))
# 输出:
# ['Mountain View', 'CA', '94040']
# ['Sunnyvale', 'CA']
# ['Los Altos', '94023']
# ['Cupertino', '95014']
# ['Palo Alto', 'CA']

# re.I/IGNORECASE ,re.M/MULTILINE
re.findall(r'(?i)yes', 'yes? Yes. YES!!')  # ['yes', 'Yes', 'YES']
re.findall(r'(?i)th\w+', 'The quickest way is through thistunnel.')  # ['The', 'through', 'this']
re.findall(r'(?im)(^th[\w ]+)', """
This line is the first,
another line,
that line, it's the best
""")  # ['This line is the first', 'that line']

# re.S/DOTALL
re.findall(r'th.+', '''
The first line
the second line
the third line
''')  # ['the second line', 'the third line']
re.findall(r'(?s)th.+', '''
The first line
the second line
the third line
''')  # ['the second line\nthe third line\n']

# re.X/VERBOSE 该标记允许用户通过抑制在正则表达式中使用空白符(除了在字符类中或者在反斜线转义中)来创建更易读的正则表达式。
# 此外,散列、注释和井号也可以用于一个注释的起始,只要它们不在一个用反斜线转义的字符类中。
re.search(r'''(?x)
\((\d{3})\) # 区号
[ ] # 空白符
(\d{3}) # 前缀
- # 横线
(\d{4}) # 终点数字
''', '(800) 555-1212').groups()  # ('800', '555', '1212')

# (?:…) 通过使用该符号,可以对部分正则表达式进行分组,但是并不会保存该分组用于后续的检索或者应用。
# 当不想保存今后永远不会使用的多余匹配时,这个符号就非常有用。
re.findall(r'http://(?:\w+\.)*(\w+\.com)', 'http://google.com http://www.google.com http://code.google.com')
# ['google.com', 'google.com', 'google.com']
re.search(r'\((?P\d{3})\) (?P\d{3})-(?:\d{4})', '(800) 555-1212').groupdict()
# {'areacode': '800', 'prefix': '555'}

# 可以同时一起使用 (?P) 和 (?P=name)符号。前者通过使用一个名称标识符而不是使用从 1 开始增加到 N 的增量数字来保存匹配,
# 如果使用数字来保存匹配结果,我们就可以通过使用\1,\2 ,\N \来检索。如下所示,可以使用一个类似风格的\g来检索它们。
re.sub(r'\((?P\d{3})\) (?P\d{3})-(?:\d{4})', '(\g) \g-xxxx', '(800) 555-1212')
# '(800) 555-xxxx'

# 使用后者,可以在一个相同的正则表达式中重用模式,而不必稍后再次在(相同)正则表达式中指定相同的模式。
# 例如, 在本示例中,假定让读者验证一些电话号码的规范化。如下所示为一个丑陋并且压缩的版本,后面跟着一个正确使用的 (?x), 使代码变得稍许易读。
bool(re.match(
    r'\((?P\d{3})\) (?P\d{3})-(?P\d{4}) (?P=areacode)-(?P=prefix)-(?P=number)1(?P=areacode)(?P=prefix)(?P=number)',
    '(800) 555-1212 800-555-1212 18005551212'))
# True
bool(re.match(r'''(?x)
    # match (800) 555-1212, save areacode, prefix, no.
    \((?P\d{3})\)[ ](?P\d{3})-(?P\d{4})
    # space
    [ ]
    # match 800-555-1212
    (?P=areacode)-(?P=prefix)-(?P=number)
    # space
    [ ]
    # match 18005551212
    1(?P=areacode)(?P=prefix)(?P=number)
''', '(800) 555-1212 800-555-1212 18005551212'))  # True

# 可以使用(?=) 和(?!…)符号在目标字符串中实现一个前视匹配,而不必实际上使用这些字符串。
# 前者是正向前视断言,后者是负向前视断言。在后面的示例中,我们仅仅对姓氏为“van Rossum”的人的名字感兴趣,
# 下一个示例中,让我们忽略以“noreply”或者“postmaster”开头的e-mail地址。
re.findall(r'\w+(?= van Rossum)',
           '''
           Guido van Rossum
           Tim Peters第 1 章 正则表达式 27
           Alex Martelli
           Just van Rossum
           Raymond Hettinger
           ''')  # ['Guido', 'Just']
# 以下代码片段用于演示findall()和finditer()的区别;我们使用后者来构建一个使用相同登录名但不同域名的e-mail地址列表
# (在一个更易于记忆的方法中,通过忽略创建用完即丢弃的中间列表)。
re.findall(r'(?m)^\s+(?!noreply|postmaster)(\w+)',
           '''
           [email protected]
           [email protected]
           [email protected]
           [email protected]
           [email protected]
           ''')  # ['sales', 'eng', 'admin']
['%[email protected]' % e.group(1) for e in
 re.finditer(r'(?m)^\s+(?!noreply|postmaster)(\w+)',
             '''
             [email protected]
             [email protected]
             [email protected]
             [email protected]
             [email protected]
             ''')]  # ['[email protected]', '[email protected]', '[email protected]']

# 最后一个示例展示了使用条件正则表达式匹配。假定我们拥有另一个特殊字符,它仅仅包含字母“x”和“y”,
# 我们此时仅仅想要这样限定字符串:两字母的字符串必须由一个字母跟着另一个字母。
# 换句话说,你不能同时拥有两个相同的字母;要么由“x”跟着“y”,要么相反。
bool(re.search(r'(?:(x)|y)(?(1)y|x)', 'xy'))  # True
bool(re.search(r'(?:(x)|y)(?(1)y|x)', 'xx'))  # False

# 匹配字符串
data = 'Thu Feb 15 17:46:04 2007::[email protected]::1171590364-6-8'
patt = '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'
m = re.match(patt, data)
m.group()  # entire match 'Thu'
m.group(1)  # subgroup 1 'Thu'
m.groups()  # all subgroups ('Thu',)

patt = '^(\w{3})'
m = re.match(patt, data)
m.group()  # 'Thu'
m.group(1)  # m.group(1)

patt = '^(\w){3}'
m = re.match(patt, data)
m.group()  # 'Thu'
m.group(1)  # 'u'
# 当我们访问子组 1 时,出现字母“u”的原因是子组 1 持续被下一个字符替换。
# 换句话说,m.group(1)以字母“T”作为开始,然后变为“h”,最终被替换为“u”。

patt = '\d+-\d+-\d+'
re.search(patt, data).group()  # entire match
# '1171590364-6-8'

patt = '.+\d+-\d+-\d+'
re.match(patt, data).group()  # entire match
# 'Thu Feb 15 17:46:04 2007::[email protected]::1171590364-6-8'

patt = '.+(\d+-\d+-\d+)'
re.match(patt, data).group(1)  # subgroup 1
# '4-6-8

# 发生了什么?我们将提取 1171590364-6-8,而不仅仅是 4-6-8。第一个整数的其余部分在哪儿?
# 问题在于正则表达式本质上实现贪婪匹配。
# 这就意味着对于该通配符模式,将对正则表达式从左至右按顺序求值,而且试图获取匹配该模式的尽可能多的字符。
# 在之前的示例中,使用“.+”获取从字符串起始位置开始的全部单个字符,包括所期望的第一个整数字段。
# \d+仅仅需要一个数字,因此将得到“4”,
# 其中.+匹配了从字符串起始部分到所期望的第一个数字的全部内容:
# “Thu Feb 15 17:46:04 2007::[email protected]::117159036”

# 其中的一个方案是使用“非贪婪”操作符“?”。
# 可以在“*”、“+”或者“?”之后使用该操作符。
# 该操作符将要求正则表达式引擎匹配尽可能少的字符。
# 因此,如果在“.+”之后放置一个“?”,我们将获得所期望的结果
patt = '.+?(\d+-\d+-\d+)'
re.match(patt, data).group(1)  # subgroup 1
# '1171590364-6-8'

# 另一个实际情况下更简单的方案,就是把“::”作为字段分隔符。
# 读者可以仅仅使用正则字符串 strip(':: ')方法获取所有的部分,
# 然后使用 strip('-')作为另一个横线分隔符,就能够获取最初想要查询的三个整数。


end

你可能感兴趣的:(《Python核心编程》笔记)