0

0

高级正则表达式技术(Python版)

黄舟

黄舟

发布时间:2017-02-07 16:39:34

|

1424人浏览过

|

来源于php中文网

原创

正则表达式是从信息中搜索特定的模式的一把瑞士军刀。它们是一个巨大的工具库,其中的一些功能经常被忽视或未被充分利用。今天我将向你们展示一些正则表达式的高级用法。

举个例子,这是一个我们可能用来检测电话美国电话号码的正则表达式:

r'^(1[-\s.])?(\()?\d{3}(?(2)\))[-\s.]?\d{3}[-\s.]?\d{4}$'

我们可以加上一些注释和空格使得它更具有可读性。

r'^'
r'(1[-\s.])?' # optional '1-', '1.' or '1'
r'(\()?' # optional opening parenthesis
r'\d{3}' # the area code
r'(?(2)\))' # if there was opening parenthesis, close it
r'[-\s.]?' # followed by '-' or '.' or space
r'\d{3}' # first 3 digits
r'[-\s.]?' # followed by '-' or '.' or space
r'\d{4}$' # last 4 digits

让我们把它放到一个代码片段里:

import re
numbers = [ "123 555 6789",
"1-(123)-555-6789",
"(123-555-6789",
"(123).555.6789",
"123 55 6789" ]
for number in numbers:
pattern = re.match(r'^'
r'(1[-\s.])?' # optional '1-', '1.' or '1'
r'(\()?' # optional opening parenthesis
r'\d{3}' # the area code
r'(?(2)\))' # if there was opening parenthesis, close it
r'[-\s.]?' # followed by '-' or '.' or space
r'\d{3}' # first 3 digits
r'[-\s.]?' # followed by '-' or '.' or space
r'\d{4}$\s*',number) # last 4 digits
if pattern:
print '{0} is valid'.format(number)
else:
print '{0} is not valid'.format(number)

输出,不带空格:

立即学习Python免费学习笔记(深入)”;

123 555 6789 is valid
1-(123)-555-6789 is valid
(123-555-6789 is not valid
(123).555.6789 is valid
123 55 6789 is not valid

正则表达式是 python 的一个很好的功能,但是调试它们很艰难,而且正则表达式很容易就出错。

幸运的是,python 可以通过对 re.compile 或 re.match 设置 re.DEBUG (实际上就是整数 128) 标志就可以输出正则表达式的解析树。

import re
numbers = [ "123 555 6789",
"1-(123)-555-6789",
"(123-555-6789",
"(123).555.6789",
"123 55 6789" ]
for number in numbers:
pattern = re.match(r'^'
r'(1[-\s.])?' # optional '1-', '1.' or '1'
r'(\()?' # optional opening parenthesis
r'\d{3}' # the area code
r'(?(2)\))' # if there was opening parenthesis, close it
r'[-\s.]?' # followed by '-' or '.' or space
r'\d{3}' # first 3 digits
r'[-\s.]?' # followed by '-' or '.' or space
r'\d{4}$', number, re.DEBUG) # last 4 digits
if pattern:
print '{0} is valid'.format(number)
else:
print '{0} is not valid'.format(number)

解析树

at_beginning
max_repeat 0 1
subpattern 1
literal 49
in
literal 45
category category_space
literal 46
max_repeat 0 2147483648
in
category category_space
max_repeat 0 1
subpattern 2
literal 40
max_repeat 0 2147483648
in
category category_space
max_repeat 3 3
in
category category_digit
max_repeat 0 2147483648
in
category category_space
subpattern None
groupref_exists 2
literal 41
None
max_repeat 0 2147483648
in
category category_space
max_repeat 0 1
in
literal 45
category category_space
literal 46
max_repeat 0 2147483648
in
category category_space
max_repeat 3 3
in
category category_digit
max_repeat 0 2147483648
in
category category_space
max_repeat 0 1
in
literal 45
category category_space
literal 46
max_repeat 0 2147483648
in
category category_space
max_repeat 4 4
in
category category_digit
at at_end
max_repeat 0 2147483648
in
category category_space
123 555 6789 is valid
1-(123)-555-6789 is valid
(123-555-6789 is not valid
(123).555.6789 is valid
123 55 6789 is not valid

贪婪和非贪婪

在我解释这个概念之前,我想先展示一个例子。我们要从一段 html 文本寻找锚标签:

import re
html = 'Hello <a href="http://pypix.com" title="pypix">Pypix</a>'
m = re.findall('<a.*>.*<\/a>', html)
if m:
print m

结果将在意料之中:

['<a href="http://pypix.com" title="pypix">Pypix</a>']

我们改下输入,添加第二个锚标签:

import re
html = 'Hello <a href="http://pypix.com" title="pypix">Pypix</a>' \
'Hello <a href="http://example.com" title"example">Example</a>'
m = re.findall('<a.*>.*<\/a>', html)
if m:
print m

结果看起来再次对了。但是不要上当了!如果我们在同一行遇到两个锚标签后,它将不再正确工作:

['<a href="http://pypix.com" title="pypix">Pypix</a>Hello <a href="http://example.com" title"example">Example</a>']

这次模式匹配了第一个开标签和最后一个闭标签以及在它们之间的所有的内容,成了一个匹配而不是两个 单独的匹配。这是因为默认的匹配模式是“贪婪的”。

当处于贪婪模式时,量词(比如 * 和 +)匹配尽可能多的字符。

当你加一个问号在后面时(.*?)它将变为“非贪婪的”。

import re
html = 'Hello <a href="http://pypix.com" title="pypix">Pypix</a>' \
'Hello <a href="http://example.com" title"example">Example</a>'
m = re.findall('<a.*?>.*?<\/a>', html)
if m:
print m

现在结果是正确的。

['<a href="http://pypix.com" title="pypix">Pypix</a>', '<a href="http://example.com" title"example">Example</a>']

前向界定符和后向界定符

一个前向界定符搜索当前的匹配之后搜索匹配。通过一个例子比较好解释一点。

下面的模式首先匹配 foo,然后检测是否接着匹配 bar:

import re
strings = [ "hello foo", # returns False
"hello foobar" ] # returns True
for string in strings:
pattern = re.search(r'foo(?=bar)', string)
if pattern:
print 'True'
else:
print 'False'

这看起来似乎没什么用,因为我们可以直接检测 foobar 不是更简单么。然而,它也可以用来前向否定界定。 下面的例子匹配foo,当且仅当它的后面没有跟着 bar。

import re
strings = [ "hello foo", # returns True
"hello foobar", # returns False
"hello foobaz"] # returns True
for string in strings:
pattern = re.search(r'foo(?!bar)', string)
if pattern:
print 'True'
else:
print 'False'

后向界定符类似,但是它查看当前匹配的前面的模式。你可以使用 (?> 来表示肯定界定,(?

下面的模式匹配一个不是跟在 foo 后面的 bar。

import re
strings = [ "hello bar", # returns True
"hello foobar", # returns False
"hello bazbar"] # returns True
for string in strings:
pattern = re.search(r'(?<!foo)bar',string)
if pattern:
print 'True'
else:
print 'False'

条件(IF-Then-Else)模式

正则表达式提供了条件检测的功能。格式如下:

(?(?=regex)then|else)

条件可以是一个数字。表示引用前面捕捉到的分组。

比如我们可以用这个正则表达式来检测打开和闭合的尖括号:

import re
strings = [ "<pypix>", # returns true
"<foo", # returns false
"bar>", # returns false
"hello" ] # returns true
for string in strings:
pattern = re.search(r'^(<)?[a-z]+(?(1)>)$', string)
if pattern:
print 'True'
else:
print 'False'

在上面的例子中,1 表示分组 (

ECShop GBK
ECShop GBK

ECSHOP是一款开源免费的网上商店系统。由专业的开发团队升级维护,为您提供及时高效的技术支持,您还可以根据自己的商务特征对ECSHOP进行定制,增加自己商城的特色功能。 ECShop网店系统 V2.7.3 Release 1106正式版发布版本提高了用户体验,优化代码,提升安全性,对原有产品各功能线进行梳理合理优化。此版本后台新增云服务,方便用户查看版本和最新补丁信息,同时提供应用服务。新增 银

下载

条件也可以是界定符。

无捕获组

分组,由圆括号括起来,将会捕获到一个数组,然后在后面要用的时候可以被引用。但是我们也可以不捕获它们。

我们先看一个非常简单的例子:

import re
string = 'Hello foobar'
pattern = re.search(r'(f.*)(b.*)', string)
print "f* => {0}".format(pattern.group(1)) # prints f* => foo
print "b* => {0}".format(pattern.group(2)) # prints b* => bar

现在我们改动一点点,在前面加上另外一个分组 (H.*):

import re
string = 'Hello foobar'
pattern = re.search(r'(H.*)(f.*)(b.*)', string)
print "f* => {0}".format(pattern.group(1)) # prints f* => Hello
print "b* => {0}".format(pattern.group(2)) # prints b* => bar

模式数组改变了,取决于我们在代码中怎么使用这些变量,这可能会使我们的脚本不能正常工作。 现在我们不得不找到代码中每一处出现了模式数组的地方,然后相应地调整下标。 如果我们真的对一个新添加的分组的内容没兴趣的话,我们可以使它“不被捕获”,就像这样:

import re
string = 'Hello foobar'
pattern = re.search(r'(?:H.*)(f.*)(b.*)', string)
print "f* => {0}".format(pattern.group(1)) # prints f* => foo
print "b* => {0}".format(pattern.group(2)) # prints b* => bar

通过在分组的前面添加 ?:,我们就再也不用在模式数组中捕获它了。所以数组中其他的值也不需要移动。

命名组

像前面那个例子一样,这又是一个防止我们掉进陷阱的方法。我们实际上可以给分组命名, 然后我们就可以通过名字来引用它们,而不再需要使用数组下标。格式是:(?Ppattern) 我们可以重写前面那个例子,就像这样:

import re
string = 'Hello foobar'
pattern = re.search(r'(?P<fstar>f.*)(?P<bstar>b.*)', string)
print "f* => {0}".format(pattern.group('fstar')) # prints f* => foo
print "b* => {0}".format(pattern.group('bstar')) # prints b* => bar

现在我们可以添加另外一个分组了,而不会影响模式数组里其他的已存在的组:

import re
string = 'Hello foobar'
pattern = re.search(r'(?P<hi>H.*)(?P<fstar>f.*)(?P<bstar>b.*)', string)
print "f* => {0}".format(pattern.group('fstar')) # prints f* => foo
print "b* => {0}".format(pattern.group('bstar')) # prints b* => bar
print "h* => {0}".format(pattern.group('hi')) # prints b* => Hello

使用回调函数

在 Python 中 re.sub() 可以用来给正则表达式替换添加回调函数。

让我们来看看这个例子,这是一个 e-mail 模板:

import re
template = "Hello [first_name] [last_name], \
Thank you for purchasing [product_name] from [store_name]. \
The total cost of your purchase was [product_price] plus [ship_price] for shipping. \
You can expect your product to arrive in [ship_days_min] to [ship_days_max] business days. \
Sincerely, \
[store_manager_name]"
# assume dic has all the replacement data
# such as dic['first_name'] dic['product_price'] etc...
dic = {
"first_name" : "John",
"last_name" : "Doe",
"product_name" : "iphone",
"store_name" : "Walkers",
"product_price": "$500",
"ship_price": "$10",
"ship_days_min": "1",
"ship_days_max": "5",
"store_manager_name": "DoeJohn"
}
result = re.compile(r'\[(.*)\]')
print result.sub('John', template, count=1)

注意到每一个替换都有一个共同点,它们都是由一对中括号括起来的。我们可以用一个单独的正则表达式 来捕获它们,并且用一个回调函数来处理具体的替换。

所以用回调函数是一个更好的办法:

import re
template = "Hello [first_name] [last_name], \
Thank you for purchasing [product_name] from [store_name]. \
The total cost of your purchase was [product_price] plus [ship_price] for shipping. \
You can expect your product to arrive in [ship_days_min] to [ship_days_max] business days. \
Sincerely, \
[store_manager_name]"
# assume dic has all the replacement data
# such as dic['first_name'] dic['product_price'] etc...
dic = {
"first_name" : "John",
"last_name" : "Doe",
"product_name" : "iphone",
"store_name" : "Walkers",
"product_price": "$500",
"ship_price": "$10",
"ship_days_min": "1",
"ship_days_max": "5",
"store_manager_name": "DoeJohn"
}
def multiple_replace(dic, text):
pattern = "|".join(map(lambda key : re.escape("["+key+"]"), dic.keys()))
return re.sub(pattern, lambda m: dic[m.group()[1:-1]], text)
print multiple_replace(dic, template)

不要重复发明轮子

更重要的可能是知道在什么时候不要使用正则表达式。在许多情况下你都可以找到 替代的工具。

解析 [X]HTML

Stackoverflow 上的一个答案用一个绝妙的解释告诉了我们为什么不应该用正则表达式来解析 [X]HTML。

你应该使用使用 HTML 解析器,Python 有很多选择:

  • ElementTree 是标准库的一部分

  • BeautifulSoup 是一个流行的第三方库

  • lxml 是一个功能齐全基于 c 的快速的库

后面两个即使是处理畸形的 HTML 也能很优雅,这给大量的丑陋站点带来了福音。

ElementTree 的一个例子:

from xml.etree import ElementTree
tree = ElementTree.parse('filename.html')
for element in tree.findall('h1'):
print ElementTree.tostring(element)

其他

在使用正则表达式之前,这里有很多其他可以考虑的工具。

以上就是高级正则表达式技术(Python版)的内容,更多相关内容请关注PHP中文网(www.php.cn)!

相关文章

python速学教程(入门到精通)
python速学教程(入门到精通)

python怎么学习?python怎么入门?python在哪学?python怎么学才快?不用担心,这里为大家提供了python速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
苹果官网入口与在线访问指南_中国站点快速直达与iPhone查看方法
苹果官网入口与在线访问指南_中国站点快速直达与iPhone查看方法

本专题汇总苹果官网最新可用入口及中国站点访问方式,涵盖官网直达链接、iPhone官方页面查看方法与常见访问说明,帮助用户快速进入苹果官方网站,便捷了解产品信息与官方服务。

4

2026.02.24

Asianfanfics官网入口与访问指南_AFF官方平台最新登录地址
Asianfanfics官网入口与访问指南_AFF官方平台最新登录地址

本专题系统整理Asianfanfics(AFF)官方网站最新可用入口,涵盖官方平台最新直达地址、官网登录方式及中文访问指引,帮助用户快速、安全地进入AFF平台浏览与使用相关内容。

9

2026.02.24

Go分布式服务治理与链路追踪实践
Go分布式服务治理与链路追踪实践

本专题围绕 Go 语言在分布式系统中的服务治理实践展开,系统讲解服务注册与发现、配置中心、限流熔断、灰度发布以及分布式链路追踪方案。通过完整项目示例,帮助开发者构建可观测性完善、稳定性高、易扩展的 Go 微服务架构体系,提升线上系统问题定位与运维效率。

1

2026.02.24

Python自动化测试与持续集成实战
Python自动化测试与持续集成实战

本专题聚焦 Python 在自动化测试与持续集成中的工程化实践,系统讲解测试框架选型、测试用例设计、接口自动化、测试数据管理及测试报告生成。结合 CI/CD 流水线场景,讲解如何将自动化测试融入持续集成流程,实现代码提交即自动测试,提升项目交付质量与团队协作效率。

0

2026.02.24

Java高并发系统设计与性能调优
Java高并发系统设计与性能调优

本专题围绕 Java 在高并发业务场景下的系统设计与性能调优展开,系统讲解线程模型、并发容器、锁机制、异步编程及 JVM 性能调优思路。结合真实高并发业务场景,深入分析接口限流、线程池配置、热点资源优化与系统稳定性保障方案,帮助开发者构建高性能、高可用的 Java 后端系统。

5

2026.02.24

pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法
pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法

本专题系统整理pixiv网页版官网入口及登录访问方式,涵盖官网登录页面直达路径、在线阅读入口及快速进入方法说明,帮助用户高效找到pixiv官方网站,实现便捷、安全的网页端浏览与账号登录体验。

1257

2026.02.13

微博网页版主页入口与登录指南_官方网页端快速访问方法
微博网页版主页入口与登录指南_官方网页端快速访问方法

本专题系统整理微博网页版官方入口及网页端登录方式,涵盖首页直达地址、账号登录流程与常见访问问题说明,帮助用户快速找到微博官网主页,实现便捷、安全的网页端登录与内容浏览体验。

402

2026.02.13

Flutter跨平台开发与状态管理实战
Flutter跨平台开发与状态管理实战

本专题围绕Flutter框架展开,系统讲解跨平台UI构建原理与状态管理方案。内容涵盖Widget生命周期、路由管理、Provider与Bloc状态管理模式、网络请求封装及性能优化技巧。通过实战项目演示,帮助开发者构建流畅、可维护的跨平台移动应用。

252

2026.02.13

TypeScript工程化开发与Vite构建优化实践
TypeScript工程化开发与Vite构建优化实践

本专题面向前端开发者,深入讲解 TypeScript 类型系统与大型项目结构设计方法,并结合 Vite 构建工具优化前端工程化流程。内容包括模块化设计、类型声明管理、代码分割、热更新原理以及构建性能调优。通过完整项目示例,帮助开发者提升代码可维护性与开发效率。

39

2026.02.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
AngularJS教程
AngularJS教程

共24课时 | 3.8万人学习

【李炎恢】ThinkPHP8.x 后端框架课程
【李炎恢】ThinkPHP8.x 后端框架课程

共50课时 | 4.7万人学习

Swoft2.x速学之http api篇课程
Swoft2.x速学之http api篇课程

共16课时 | 1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号