Python 核心编程 第三版 第一章 Python 正则表达式

学习了很多基础,今天我们来分享《Python 核心编程》第三版 第一章 Python 正则表达式相关的内容,它可以使我们在使用字符串时得到很大的帮助,用好正则表达式,可以让我们少写很多代码,特别时与处理字符串相关的代码。
说明:由于第二版是基于Python 2 的,而Python 2 将在不久之后就不提供支持,所以,我们也只更新基础内容,虽然Python 2 和Python 3 在基础内容也有不少区别,但是,学好Python 2 基础内容,很容易就过渡到Python 3。对于第二版 第14章之后的内容,就不在提供更新,因为其中的内容都在第三版中了,都是Python的应用,第三版的代码库也更新到了Python 3.
可以关注我的微信公众号【Python Dao】,也可以扫描下方二维码关注我,我们一起学习交流。

Python Dao

一 简介

正则表达式为高级的文本模式匹配、抽取、与/或文本形式的搜索和替换功能提供了基础。

Python 通过标准库中的 re 模块来支持正则表达式

Python 中两种方法完成模式匹配:

  • “搜索”(searching),即在字符串任意部分中搜索匹配的模式;
  • “匹配”(matching)是指判断一个字符串能否从起始处全部或者部分地匹配某个模式。
  • 搜索通过 search()函数或方法来实现,而匹配通过调用 match()函数或方法实现。

二 特殊符号和字符

最常见的特殊符号和字符,即所谓的元字符

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

表 示 法 描 述 示例
符号
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|u)bar
特殊字符
\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)

点号或者句点(.) 符号匹配除了换行符\n 以外的任何字符(Python 正则表达式有一个编译标记[S 或者 DOTALL],该标记能够推翻这个限制,使点号能够匹配换行符)。

匹配字符串的开始位置,使用脱字符(^)或者特殊字符\A
匹配字符串的末尾位置,使用美元符号($)或者\Z

\b 将用于匹配一个单词的边界,这意味着如果一个模式必须位于单词的起始部分,就不管该单词前面(单词位于字符串中间)是否有任何字符(单词位于行首)。

\B 将匹配出现在一个单词中间的模式(即,
不是单词边界)

方括号,该正则表达式能够匹配一对方括号中包含的任何字符。

方括号中两个符号中间用连字符(-)连接,用于指定一个字符的范围

* 匹配零次或多次,Kleene闭包

+ 匹配一次或多次,正闭包操作符

? 匹配零次或一次,如果问号紧跟在任何使用闭合操作符的匹配后面,它将直接要求正则表达式引擎匹配尽可能少的次数,叫非贪婪匹配

当模式匹配使用分组操作符时,正则表达式引擎将试图“吸收”匹配该模式的尽可能多的字符。这通常被叫做贪婪匹配

使用圆括号指定分组

扩展表示法 :以问号开始(?…) ,通常用于在判断匹配之前提供标记,实现一个前视(或者后视)匹配,或者条件检查。

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

第三 正则表达式和 Python 语言

  1. 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 通过反斜线转义, 否则所有空格加上#(以及在该行中所有后续文字)都被忽略,除非在一个字符类中或者允许注释并且提高可读性

① Python 1.5.2 版中新增; 2.4 版中增加 flags 参数。

② Python 2.2 版中新增; 2.4 版中增加 flags 参数。

③ Python 2.7 和 3.1 版中增加 flags 参数

是否编译编译正则表达式 ?

使用预编译的代码对象比直接使用字符串要快,因为解释器在执行字符串形式的代码前都必须把字符串编译成代码对象。

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


  1. 使用 compile()函数编译正则表达式

对于一些特别的正则表达式编译,可选的标记可能以参数的形式给出,比如re.IGNORECASE、 re.MULTILINE、 re.DOTALL、re.VERBOSE 等 ,它们可以通过按位或操作符(|)合并。 这些标记也可以作为参数适用于大多数 re 模块函数。


  1. 匹配对象以及 group()和 groups()方法

当处理正则表达式时,除了正则表达式对象之外,还有另一个对象类型:匹配对象

匹配对象是是成功调用 match()或者 search()返回的对象。

主要有两个 方法:group() 和groups()

group()要么返回整个匹配对象,要么根据要求返回特定子组

groups()则仅返回一个包含唯一或者全部子组的元组

若无子组要求,则当group()仍然返回整个匹配时,groups()返回一个空元组


  1. 使用 match()方法匹配字符串

match()函数试图从字符串的起始部分对模式进行匹配。

若匹配成功,返回一个匹配对象;

若匹配失败,返回 None,匹配对象的 group()方法能够用于显示那个成功的匹配。

注:match() 只能从字符串的开始位置匹配,如果开始位置没有找到匹配的字符串,则匹配失败。

import re

match = re.match("foo","foo")
if match is not None:
    print match.group() # foo

  1. 使用 search()在一个字符串中查找模式(搜索与匹配的对比)

search()的工作方式与 match()完全一致,不同之处在于 search()会用它的字符串参数,在任意位置对给定正则表达式模式搜索第一次出现的匹配情况。如果搜索到成功的匹配,就会返回一个匹配对象; 否则, 返回 None。

import re
m = re.search("foo","seafood")
if m is not None:
    print m.group() # foo

match()和 search()都使用可选的标记参数。

注意:等价的正则表达式对象方法使用可选的 posendpos 参数来指定目标字符串的搜索范围。


  1. 匹配多个字符串

match()和search()使用比较

pattern = "bat|bet|bit"
mc = re.match(pattern,"bat")
if mc is not None:
    print mc.group()# bat

mc1 = re.match(pattern,"blt")# None
if mc1 is not None:
    print mc1.group() 

mc2 = re.match(pattern,"He bit me!")# None
if mc2 is not None:
    print mc2.group()

ms = re.search(pattern,"He bit me!")
if ms is not None:
    print ms.group()# bit

  1. 匹配任何单个字符

点号(.) 匹配任何一个非换行符(\n),需要精确匹配点号(.),需要在模式中使用转义(\.)


  1. 创建字符集([ ])

使用[] 可以自定义一个匹配的字符集

m = re.match('[cr][23][dp][o2]', 'c3po')# 匹配 'c3po'
if m is not None: m.group() # c3po

  1. 重复、特殊字符以及分组

正则表达式中最常见的情况包括特殊字符的使用、 正则表达式模式的重复出现,以及使用圆括号对匹配模式的各部分进行分组和提取操作。

import re
pattern = "\w+@(\w+\.)*\w+\.com"
s1 = re.search(pattern,"[email protected]")
print s1.group()

s1 = re.search(pattern,"[email protected]")
print s1.group()

s1 = re.search(pattern,"[email protected]")
print s1.group()

使用 group()方法访问每个独立的子组以及 groups()方法以获取一个包含所有匹配子组的元组 :

import re
m = re.match("(\w\w\w)-(\d\d\d)","abc-124")
print m.group() # abc-124
# 获取子组 1
print m.group(1) # abc
# 获取子组 2
print m.group(2) # 124
# 获取所有匹配的子组
print m.groups() # ('abc', '124')

  1. 匹配字符串的起始和结尾以及单词边界

通常情况下,在正则表达式中使用原始字符串是个好主意。

import re
m = re.search("^The", "The end.")
if m is not None: print(m.group()) # The
    
m = re.search('^The', 'end. The') # 不作为起始
if m is not None: print m.group() # None

m = re.search(r'\bthe', 'bite the dog') # 在边界
if m is not None: print m.group() # the
    
m = re.search(r'\bthe', 'bitethe dog') # 有边界
if m is not None: print m.group() # None
    
    
m = re.search(r'\Bthe', 'bitethe dog') # 没有边界
if m is not None: print m.group() # the

  1. 使用 findall()和 finditer()查找每一次出现的位置

findall()查询字符串中某个正则表达式模式全部非重复出现情况。

findall()总是返回一个列表

import re
fa = re.findall("car","car")
print fa  # ['car']

fa = re.findall("car", "carry")
print fa # ['car']

fa = re.findall('car', 'carry the carry barcardi to the car')
print fa # ['car', 'car', 'car', 'car']

finditer()函数 是一个与 findall()函数类似但是更节省内存的变体。 两者之间以及和其他变体函数之间的差异(很明显不同于返回的是一个迭代器还是列表)在于,和返回的匹配字符串相比, finditer()在匹配对象中迭代

在单个字符串中两个不同分组之间的差别

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

注意,使用 finditer()函数完成的所有额外工作都旨在获取它的输出来匹配 findall()的输出。

findall()和 finditer()方法的版本支持可选的 pos 和 endpos参数, 这两个参数用于控制目标字符串的搜索边界


  1. 使用 sub()和 subn()搜索与替换

sub()和 subn() ,两者几乎一样,都是将某字符串中所有匹配正则表达式的部分进行某种形式的替换。sub()返回替换后的字符串

subn()还返回一个表示替换的总数,替换后的字符串和表示替换总数的数字一起作为一个拥有两个元素的元组返回。

import re
sub = re.sub("X","Ms. Smith","att: X\n\nDear, X\n")
print sub

subn = re.subn("X","Ms. Smith","att: X\n\nDear, X\n")
print subn

用匹配对象的 group()方法除了能够取出匹配分组编号外,还可以使用\N,其中 N 是在替换字符串中使用的分组编号。

下面的代码仅仅只是将美式的日期表示法MM/DD/YY{,YY}格式转换为其他国家常用的格式 DD/MM/YY{,YY} :

import re

s = re.sub(r'(\d{1,2})/(\d{1,2})/(\d{2}|\d{4})', r'\2/\1/\3', '2/20/91')  # Yes, Python is...
print s

s = re.sub(r'(\d{1,2})/(\d{1,2})/(\d{2}|\d{4})', r'\2/\1/\3', '2/20/1991')  # ... 20+ years old!
print s

  1. 在限定模式上使用 split()分隔字符串

若给定分隔符不是使用特殊符号来匹配多重模式的正则表达式,则re.split()与
str.split()的工作方式相同

import re

split = re.split(":","str1:str2:str3")
print split # ['str1', 'str2', 'str3']

re.split()还支持更复杂的正则表达式匹配来作为分隔符


  1. 扩展符号

通过使用 (?iLmsux) 系列选项,用户可以直接在正则表达式里面指定一个或者多个标记,而不是通过 compile()或者其他 re 模块函数。

使用 re.I/IGNORECASE 和 在 re.M/MULTILINE 实现多行混合:

import re
# 不区分大小写
findall = re.findall(r"(?i)yes","yes? Yes YES!!!")
print findall # ['yes', 'Yes', 'YES']
# 不区分大小写
findall = re.findall(r"(?i)th\w+","The quickest way is through this tunnel")
print findall # ['The', 'through', 'this']
# 使用“多行”
findall = re.findall(r"(?im)(^th[\w ]+)", """
this line is the first,
another line,
that line, it's the best.
""")
print findall # ['this line is the first', 'that line']

使用 re.S/DOTALL。该标记表明点号(.) 能够用来表示\n 符号(反之其通常
用于表示除了\n 之外的全部字符):

import re

findall = re.findall(r"th.+","""
The first line,
The second line,
The third line.
""")
print findall   # ['third line.']
# 使用 re.S/DOTALL,匹配换行符
findall = re.findall(r"(?s)th.+","""
The first line,
the second line,
the third line.
""")
print findall # ['the second line,\nthe third line.\n']

re.X/VERBOSE 标记允许用户通过抑制在正则表达式中使用空白符(除了在字符类中或者在反斜线转义中)来创建更易读的正则表达式。此外,散列、注释和井号也可以用于一个注释的起始,只要它们不在一个用反斜线转义的字符类中。

import re

s = re.search(r"""(?x)
\((\d{3})\)     # 区号
[ ]             # 空白符
(\d{3})         # 前缀
-               # 横线
(\d{4})         # 终点数字
""", '(800) 555-1212').groups()
print s  #('800', '555', '1212')

(?:…)符号将更流行;通过使用该符号,可对部分正则表达式进行分组,但并不会保存该分组用于后续的检索或应用。当不想保存之后不会使用的多余匹配时,此符号就非常有用。

import re

findall = re.findall(r"http://(?:\w+\.)*(\w+\.com)","http://google.com http://www.google.com http://code.google.com")
print findall # ['google.com', 'google.com', 'google.com']

s = re.search(r"\((?P\d{3})\) (?P\d{3})-(?:\d{4})","(800) 555-1212").groupdict()
print s # {'areacode': '800', 'prefix': '555'}

可以同时一起使用 (?P) 和 (?P=name)符号 。

(?P)通过使用一个名称标识符而不是使用从 1 开始增加到 N 的增量数字来保存匹配,如果使用数字来保存匹配结果,可以通过使用\1,\2 ...,\N 来检索。

import re

sub = re.sub(r'\((?P\d{3})\) (?P\d{3})-(?:\d{4})','(\g) \g-xxxx', '(800) 555-1212')
print sub  # (800) 555-xxxx

(?P=name)可以在一个相同的正则表达式中重用模式,而不必稍后再次在(相同)
正则表达式中指定相同的模式。

import re

b = 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'))
print b
# ?x 表示可在正则表达式中嵌入注释,表示re.X标记
>>> 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

(?=...)(?!…)符号在目标字符串中实现一个前视匹配,而不必实际上使用这些字符串。

(?=...)是正向前视断言,(本人加:返回匹配项所在行的匹配项前面的内容)

(?!…)是负向前视断言,(本人加:返回不匹配项)

# (?=...)正向前视断言
>>> re.findall(r'\w+(?= van Rossum)',
... '''
... Guido van Rossum
... Tim Peters
... Alex Martelli
... Just van Rossum
... Raymond Hettinger
... ''')
['Guido', 'Just']
# (?!...)负向前视断言
>>> 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

  1. 杂项

如果有符号同时用于 ASCII 和正则表达式,就会发生问题,因此建议使用 Python 的原始字符串来避免产生问题。

\w 和\W 字母数字字符集同时受 re.L/LOCALE 和 Unicode(re.U/UNICODE)标记所影响。

你可能感兴趣的:(Python 核心编程 第三版 第一章 Python 正则表达式)