news 2026/5/28 18:47:45

19 - 正则表达式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
19 - 正则表达式

19 - 正则表达式

正则表达式(Regular Expression,简称 regex)是一种文本匹配的工具。说白了就是用一套"暗号"来描述你想找的文本模式。


基础语法

先说个前提,正则表达式不是 Python 特有的,几乎所有编程语言都支持。所以学会了到处能用。

导入 re 模块

importre

最简单的匹配

普通字符就匹配它自己:

result=re.search(r"hello","say hello world")print(result)# <re.Match object; span=(4, 9), match='hello'>

re.search在字符串里找第一个匹配。找到了返回 Match 对象,找不到返回None

注意字符串前面的r(raw string),因为正则里经常用反斜杠,加r就不用双写反斜杠了。


元字符

这些字符在正则里有特殊含义:

字符含义例子
.任意一个字符(除换行)h.t→ hat, hot, h3t
\d数字[0-9]\d\d→ 匹配两位数字
\D非数字\D+→ 匹配非数字部分
\w字母数字下划线\w+→ 匹配一个"单词"
\W非字母数字下划线
\s空白字符(空格、Tab、换行)
\S非空白字符
^字符串开头^hello→ 以 hello 开头
$字符串结尾world$→ 以 world 结尾
# 匹配手机号(简化版)re.search(r"\d{11}","我的号码是13812345678")# 匹配邮箱开头re.search(r"^\w+@","xiaoming@example.com")

量词

控制前面的元素出现几次:

量词含义例子
*0 次或多次\d*→ 任意位数字(包括 0 位)
+1 次或多次\d+→ 至少一位数字
?0 次或 1 次colou?r→ color 或 colour
{n}恰好 n 次\d{4}→ 4 位数字
{n,m}n 到 m 次\d{2,4}→ 2-4 位数字
{n,}至少 n 次\d{3,}→ 至少 3 位数字
# 匹配年份re.search(r"\d{4}","发表于2024年")# 匹配价格(1-3位数字加可选的小数部分)re.search(r"\d{1,3}(\.\d{1,2})?","价格:99.99元")

字符集[]

方括号里列出允许的字符:

# 匹配元音字母re.findall(r"[aeiou]","hello world")# ['e', 'o', 'o']# 匹配大写字母re.findall(r"[A-Z]","Hello World Python")# ['H', 'W', 'P']# 取反(^放在[]里面表示"不是这些")re.findall(r"[^0-9]","abc123def")# ['a', 'b', 'c', 'd', 'e', 'f']# 范围re.findall(r"[a-z]+","Hello World")# ['ello', 'orld']

分组()

用圆括号把一部分正则包起来,形成一个组:

# 提取日期中的年月日text="日期:2024-05-25"match=re.search(r"(\d{4})-(\d{2})-(\d{2})",text)ifmatch:print(match.group(1))# 2024(第一个组)print(match.group(2))# 05print(match.group(3))# 25print(match.groups())# ('2024', '05', '25')

命名分组

给组起个名字,比数字更直观:

match=re.search(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})",text)ifmatch:print(match.group("year"))# 2024print(match.group("month"))# 05print(match.group("day"))# 25

非捕获分组(?:...)

有时候你只是需要用括号来做优先级或量词,不想捕获:

# (?:...) 不创建组match=re.search(r"(?:https?://)([\w.]+)","https://www.example.com")print(match.group(1))# www.example.com(第一组就是域名,没有协议那组)

re 模块常用函数

re.search — 找第一个匹配

match=re.search(r"\d+","abc 123 def 456")print(match.group())# 123(只找到第一个)

re.match — 从开头匹配

# match 只从字符串开头匹配print(re.match(r"\d+","123abc"))# 匹配成功print(re.match(r"\d+","abc123"))# None(开头不是数字)

matchsearch的区别:match必须从开头开始匹配,searchanywhere 都行。

re.findall — 找所有匹配

# 找所有数字numbers=re.findall(r"\d+","我有 3 个苹果和 5 个橘子")print(numbers)# ['3', '5']# 如果有分组,返回组的内容dates=re.findall(r"(\d{4})-(\d{2})","2024-01 2024-02 2024-03")print(dates)# [('2024', '01'), ('2024', '02'), ('2024', '03')]

re.finditer — 返回迭代器

findall类似,但返回 Match 对象的迭代器,适合大量匹配时省内存:

formatchinre.finditer(r"\d+","1 22 333"):print(f"位置{match.start()}-{match.end()}:{match.group()}")# 位置 0-1: 1# 位置 2-4: 22# 位置 5-8: 333

re.sub — 替换

# 把数字替换成 *result=re.sub(r"\d+","*","我有 3 个苹果和 5 个橘子")print(result)# 我有 * 个苹果和 * 个橘子# 用函数做替换defdouble(match):returnstr(int(match.group())*2)result=re.sub(r"\d+",double,"3 和 5")print(result)# 6 和 10# 脱敏手机号defmask_phone(match):phone=match.group()returnphone[:3]+"****"+phone[7:]result=re.sub(r"1\d{10}",mask_phone,"号码:13812345678")print(result)# 号码:138****5678

re.split — 分割

# 按数字分割parts=re.split(r"\d+","abc123def456ghi")print(parts)# ['abc', 'def', 'ghi']# 按多种分隔符分割parts=re.split(r"[,;|]","a,b;c|d")print(parts)# ['a', 'b', 'c', 'd']

编译正则

如果同一个正则要用很多次,先编译可以提高性能:

# 编译一次email_pattern=re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+")# 使用多次print(email_pattern.findall("联系我:a@b.com 或 c@d.org"))print(email_pattern.search("邮箱:test@example.com"))

贪婪与非贪婪

量词默认是贪婪的——尽可能多地匹配:

text="<h1>标题</h1>"match=re.search(r"<.+>",text)print(match.group())# <h1>标题</h1>(匹配了整个字符串!)

?变成非贪婪(尽可能少地匹配):

match=re.search(r"<.+?>",text)print(match.group())# <h1>(只匹配到第一个 >)
贪婪非贪婪
**?
++?
{n,m}{n,m}?

常用标志

在正则末尾或re.compile的第二个参数中设置:

# re.IGNORECASE (re.I) — 忽略大小写re.search(r"hello","HELLO",re.IGNORECASE)# 匹配成功# re.MULTILINE (re.M) — 多行模式,^ 和 $ 匹配每行text="第一行\n第二行\n第三行"re.findall(r"^\w+",text,re.MULTILINE)# ['第一行', '第二行', '第三行']# re.DOTALL (re.S) — 让 . 匹配换行符re.search(r"a.b","a\nb",re.DOTALL)# 匹配成功# 组合使用re.search(r"pattern",text,re.I|re.M)

实际例子

验证邮箱

defis_valid_email(email):pattern=r"^[\w.+-]+@[\w-]+\.[\w.]+$"returnbool(re.match(pattern,email))print(is_valid_email("test@example.com"))# Trueprint(is_valid_email("not-an-email"))# False

提取 URL

text="请访问 https://www.example.com 或 http://test.org/path?q=1"urls=re.findall(r"https?://[\w./\-?=&]+",text)print(urls)# ['https://www.example.com', 'http://test.org/path?q=1']

解析日志

log='[2024-05-25 14:30:00] ERROR: 数据库连接失败 (timeout=30s)'pattern=r'\[(?P<time>[\d\- :]+)\] (?P<level>\w+): (?P<message>.+)'match=re.search(pattern,log)ifmatch:print(f"时间:{match.group('time')}")print(f"级别:{match.group('level')}")print(f"信息:{match.group('message')}")

字符串清理

text=" hello world \n\n python "# 多个空白替换为单个空格cleaned=re.sub(r"\s+"," ",text).strip()print(cleaned)# "hello world python"

本章小结

  • \d\w\s是最常用的元字符
  • *+?{n,m}控制匹配次数
  • ()分组,(?P<name>...)命名分组
  • re.search找第一个,re.findall找所有,re.sub替换
  • 量词默认贪婪,加?变非贪婪
  • 频繁使用的正则先re.compile编译

面试题

Q1:re.searchre.match有什么区别?

点击查看答案
  • re.search:在整个字符串中查找第一个匹配,匹配位置不限
  • re.match:只从字符串开头匹配,开头不匹配就返回 None
re.search(r"\d+","abc123")# 匹配成功(123)re.match(r"\d+","abc123")# None(开头不是数字)re.match(r"\d+","123abc")# 匹配成功(123)

如果需要 search 但只匹配开头,可以用^锚点:re.search(r"^\d+", text)

Q2:贪婪匹配和非贪婪匹配有什么区别?

点击查看答案

贪婪匹配(默认)尽可能多地匹配字符,非贪婪匹配尽可能少地匹配。

text="<a>hello</a>"re.search(r"<.+>",text).group()# '<a>hello</a>'(贪婪)re.search(r"<.+?>",text).group()# '<a>'(非贪婪)

在量词后加?切换为非贪婪模式:*?+?{n,m}?

常见场景:提取 HTML 标签、JSON 字段时通常用非贪婪。

Q3:re.findall在有分组和没分组时返回值有什么不同?

点击查看答案
  • 没有分组:返回匹配到的完整字符串列表

    re.findall(r"\d+","a1 b22 c333")# ['1', '22', '333']
  • 有一个分组:返回组内容的列表

    re.findall(r"(\d+)","a1 b22")# ['1', '22']
  • 有多个分组:返回元组列表

    re.findall(r"(\w+)=(\d+)","a=1 b=2")# [('a', '1'), ('b', '2')]

如果只需要部分匹配结果,用分组可以精确控制返回内容。

Q4:如何匹配一个 IP 地址?

点击查看答案

简单版(不验证范围):

r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"

严格版(每段 0-255):

r"(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)"

实际项目中推荐用ipaddress标准库验证:

importipaddresstry:ipaddress.ip_address("192.168.1.1")# 合法exceptValueError:# 不合法

正则适合从文本中提取疑似 IP 的字符串,验证合法性用专用库更可靠。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/28 18:47:09

深度解析:如何高效使用 Uber APK Signer 进行 Android 应用签名

深度解析&#xff1a;如何高效使用 Uber APK Signer 进行 Android 应用签名 【免费下载链接】uber-apk-signer A cli tool that helps signing and zip aligning single or multiple Android application packages (APKs) with either debug or provided release certificates.…

作者头像 李华
网站建设 2026/5/28 18:47:02

OPC中国_什么是OPC一人公司

在AI智能体快速发展的今天&#xff0c;“OPC一人公司”正在成为越来越多人关注的新型职业模式。很多人第一次接触OPC中国时&#xff0c;都会产生一个问题&#xff1a;什么是OPC一人公司&#xff1f;简单来说&#xff0c;OPC一人公司是一种借助AI智能体、自动化工具和数字化能力…

作者头像 李华
网站建设 2026/5/28 18:46:14

选择题专练数据库原理精选30题

答案在主页。 一、 事务与并发控制 (8题) 关于多版本并发控制&#xff0c;以下哪种场景下&#xff0c;MVCC机制仍可能产生“幻读”问题&#xff1f; A. 在READ COMMITTED隔离级别下&#xff0c;事务A读取一个范围的数据后&#xff0c;事务B在该范围内插入新记录并提交&#xf…

作者头像 李华
网站建设 2026/5/28 18:46:13

ZYZ28 2026.5.26 Round 记录

ZYZ28 2026.5.26 Round 记录 A - 我要在家睡觉&#xff01; 原题链接&#xff1a;LGP11605 [PA 2016] 运算 / Jedynki 分析 写过…… 正解 #include <bits/stdc.h> using namespace std; string ans ""; void work(int k){if (k 1){ans "1"…

作者头像 李华
网站建设 2026/5/28 18:42:54

如何用Obsidian Projects实现高效项目管理的5个实用技巧

如何用Obsidian Projects实现高效项目管理的5个实用技巧 【免费下载链接】obsidian-projects Plain text project planning in Obsidian 项目地址: https://gitcode.com/gh_mirrors/ob/obsidian-projects 在信息碎片化的时代&#xff0c;项目管理工具的选择往往决定了工…

作者头像 李华
网站建设 2026/5/28 18:42:38

GEO优化和传统SEO有什么区别

很多企业主第一次听到GEO时&#xff0c;第一反应往往是&#xff1a;“这不就是高级一点的SEO吗&#xff1f;”这个直觉可以理解&#xff0c;毕竟两者都在围绕“搜索可见性”做文章。但从底层逻辑、优化对象、竞争规则到结果形态&#xff0c;GEO和传统SEO有着本质区别。一、优化…

作者头像 李华