【Python】基础语法入门(二十九)—— 正则表达式实战:文本处理利器

系列文章:Python 基础语法入门 本文目标:从零搞懂正则表达式语法,熟练使用 Python re 模块的 match/search/findall/sub/split 等方法,掌握邮箱、手机号、身份证等常用模式,并能用正则做日志分析、HTML 提取、密码校验等实战。


一、正则能干什么

正则表达式(Regular Expression,简称 regex / regexp)是一种描述字符串模式的微型语言。用短短一行就能表达"找出所有邮箱""验证手机号格式""把日期统一成 YYYY-MM-DD"这类需求。

常见应用场景:

场景 例子
数据提取 从一篇文章里提取所有邮箱、URL、手机号
格式校验 注册时校验邮箱、手机号、身份证是否合法
文本替换 把 2023/01/01 统一替换成 2023-01-01
日志清洗 从 Nginx 日志里提取 IP、状态码、访问路径
代码处理 批量重命名变量、提取函数签名
爬虫解析 从 HTML 里提取所有 <a href="..."> 链接

如果不用正则,写这些逻辑往往要几十行 if + for + 字符串切片;用正则,一行搞定。


二、正则基础语法

正则由普通字符 和元字符组成。普通字符匹配自身,元字符有特殊含义。下面逐一讲解。

2.1 预定义字符类

写法 含义 示例
. 任意一个字符(除换行) a.c 匹配 abc、a1c
\d 一个数字(0-9) \d\d 匹配 42
\D 非数字 \D 匹配 a、!
\w 字母/数字/下划线 \w+ 匹配 hello_123
\W 非字母数字下划线 \W 匹配 @、空格
\s 空白符(空格、Tab、换行) a\sb 匹配 a b
\S 非空白符
\b 单词边界 \bcat\b 匹配单词 cat,不匹配 category
复制代码
import re
print(re.findall(r'\d', 'a1b2c3'))        # ['1', '2', '3']
print(re.findall(r'\w+', 'hello world!')) # ['hello', 'world']
print(re.findall(r'\s', 'a b\tc\nd'))     # [' ', '\t', '\n']

运行结果:

复制代码
['1', '2', '3']
['hello', 'world']
[' ', '\t', '\n']

2.2 量词(重复次数)

写法 含义 示例
* 0 次或多次 ab*c 匹配 ac、abc、abbc
+ 1 次或多次 ab+c 匹配 abc、abbc,不匹配 ac
? 0 次或 1 次 colou?r 匹配 color、colour
{m} 恰好 m 次 \d{3} 匹配 123
{m,} 至少 m 次 \d{2,} 匹配 12、123、1234
{m,n} m 到 n 次 \d{2,4} 匹配 12、123、1234
复制代码
import re
print(re.findall(r'ab*c', 'ac abc abbc abbbc'))   # ['ac', 'abc', 'abbc', 'abbbc']
print(re.findall(r'\d{3}', '12 123 1234 12345'))  # ['123', '123', '123', '123']
print(re.findall(r'\d{2,4}', '1 12 123 1234 12345'))  # ['12', '123', '1234', '1234']

运行结果:

复制代码
['ac', 'abc', 'abbc', 'abbbc']
['123', '123', '123', '123']
['12', '123', '1234', '1234']

2.3 边界与锚点

写法 含义
^ 字符串开头
$ 字符串结尾
\b 单词边界
复制代码
import re
print(re.findall(r'^Hello', 'Hello World'))   # ['Hello'](开头匹配)
print(re.findall(r'^Hello', 'Say Hello'))     # [](不在开头)
print(re.findall(r'World$', 'Hello World'))   # ['World'](结尾匹配)
print(re.findall(r'\bcat\b', 'cat category cat'))  # ['cat', 'cat'](只匹配独立单词)

运行结果:

复制代码
['Hello']
[]
['World']
['cat', 'cat']

2.4 字符集 []

[] 表示匹配方括号里的任意一个字符:

  • [aeiou]:匹配任一元音字母。

  • [a-z]:匹配任一小写字母。

  • [0-9a-fA-F]:匹配十六进制字符。

  • [^0-9]:^ 在开头表示取反,匹配非数字。

    import re
    print(re.findall(r'[aeiou]', 'hello world')) # ['e', 'o', 'o']
    print(re.findall(r'[a-z][0-9]', 'a1 B2 c3')) # ['a1', 'c3']
    print(re.findall(r'[^0-9]', 'ab12cd34')) # ['a', 'b', 'c', 'd']

运行结果:

复制代码
['e', 'o', 'o']
['a1', 'c3']
['a', 'b', 'c', 'd']

2.5 分组 () 与或 |

() 把一部分模式分组 ,可以整体应用量词、提取子串;| 表示或。

复制代码
import re
# (ab)+ 匹配 ab、abab、ababab
print(re.findall(r'(ab)+', 'ab abab ababab x'))   # ['ab', 'ab', 'ab']

# cat|dog 匹配 cat 或 dog
print(re.findall(r'cat|dog', 'I have a cat and a dog'))  # ['cat', 'dog']

运行结果:

复制代码
['ab', 'ab', 'ab']
['cat', 'dog']

2.6 转义字符

元字符(. * + ? ( ) [ ] { } ^ $ \ |)在正则里有特殊含义,要匹配它们本身,需用 \ 转义。

复制代码
import re
# 匹配 IP 里的点
print(re.findall(r'\d+\.\d+\.\d+\.\d+', 'IP是192.168.1.1'))  # ['192.168.1.1']
# 匹配价格里的 $
print(re.findall(r'\$\d+', 'price $100 and $200'))           # ['$100', '$200']

运行结果:

复制代码
['192.168.1.1']
['$100', '$200']

提示:在 Python 里写正则建议用原始字符串 r'...',否则 \d 会被当成转义字符报错或失效。


三、贪婪与非贪婪

3.1 什么是贪婪

默认情况下,* + {m,n} 会尽可能多地匹配,这叫"贪婪模式"。

复制代码
import re
# 想匹配 <div>...</div>,贪婪会一直匹配到最后一个 >
s = '<div>hello</div><div>world</div>'
print(re.findall(r'<div>.*</div>', s))
# ['<div>hello</div><div>world</div>']  ← 匹配了整个!

3.2 非贪婪(懒惰)

在量词后加 ? 变成非贪婪,尽可能少地匹配:

复制代码
import re
s = '<div>hello</div><div>world</div>'
print(re.findall(r'<div>.*?</div>', s))
# ['<div>hello</div>', '<div>world</div>']  ← 正确匹配每个 div

运行结果对比:

复制代码
贪婪:['<div>hello</div><div>world</div>']
非贪婪:['<div>hello</div>', '<div>world</div>']
模式 写法 行为
贪婪 * + ? {m,n} 尽可能多匹配
非贪婪 *? +? ?? {m,n}? 尽可能少匹配

经验:提取 HTML/XML 标签内容时几乎都要用非贪婪 .*?。


四、re 模块常用方法

Python 内置 re 模块,下面逐个讲解核心方法。

4.1 re.match(从头匹配)

re.match(pattern, string) 只从字符串开头 匹配,匹配成功返回 Match 对象,否则返回 None。

复制代码
import re
m = re.match(r'(\d+)-(\w+)', '100-python')
if m:
    print(m.group())     # 100-python(整体)
    print(m.group(1))    # 100(第1组)
    print(m.group(2))    # python(第2组)
    print(m.span())      # (0, 10)(匹配起止位置)

print(re.match(r'\d+', 'abc123'))   # None(开头不是数字)

运行结果:

复制代码
100-python
100
python
(0, 10)
None

4.2 re.search(搜索任意位置)

re.search 在整个字符串里找第一个匹配,不限开头。

复制代码
import re
m = re.search(r'\d+', 'abc123def456')
print(m.group())   # 123(找到第一个数字串)

运行结果:

复制代码
123

match vs search:match 必须从头开始;search 可以从任意位置。

4.3 re.findall(找所有)

返回所有匹配的字符串列表(有分组时返回分组元组列表)。

复制代码
import re
print(re.findall(r'\d+', 'a1b22c333'))           # ['1', '22', '333']
print(re.findall(r'(\w+)@(\w+)', 'a@b cc@dd'))   # [('a', 'b'), ('cc', 'dd')]

运行结果:

复制代码
['1', '22', '333']
[('a', 'b'), ('cc', 'dd')]

4.4 re.finditer

返回一个迭代器,每个元素是 Match 对象,能拿到位置信息,比 findall 更强大。

复制代码
import re
for m in re.finditer(r'\d+', 'a1b22c333'):
    print(m.group(), m.span())

运行结果:

复制代码
1 (1, 2)
22 (3, 5)
333 (6, 9)

4.5 re.sub(替换)

re.sub(pattern, repl, string, count=0) 把匹配的部分替换成 repl。

复制代码
import re
# 把日期 2023/01/01 改成 2023-01-01
s = '日期:2023/01/01 和 2023/12/31'
print(re.sub(r'/', '-', s))
# 日期:2023-01-01 和 2023-12-31

# 用分组反向引用重排日期:YYYY/MM/DD -> DD-MM-YYYY
print(re.sub(r'(\d{4})/(\d{2})/(\d{2})', r'\3-\2-\1', s))
# 日期:01-01-2023 和 31-12-2023

运行结果:

复制代码
日期:2023-01-01 和 2023-12-31
日期:01-01-2023 和 31-12-2023

回调函数替换 :repl 可以是函数,接收 Match 对象,返回替换字符串。

复制代码
import re
# 把每个数字乘以 2
s = 'a1b22c333'
result = re.sub(r'\d+', lambda m: str(int(m.group()) * 2), s)
print(result)   # a2b44c666

运行结果:

复制代码
a2b44c666

4.6 re.split(分割)

比 str.split 更强大,可以按正则模式分割。

复制代码
import re
# 按任意空白或逗号分割
print(re.split(r'[,\s]+', 'a,b c,,d  e'))
# ['a', 'b', 'c', 'd', 'e']

# 保留分隔符(加分组)
print(re.split(r'(\s+)', 'hello world  foo'))
# ['hello', ' ', 'world', '  ', 'foo']

运行结果:

复制代码
['a', 'b', 'c', 'd', 'e']
['hello', ' ', 'world', '  ', 'foo']

4.7 re.fullmatch

要求整个字符串完全匹配,常用于格式校验。

复制代码
import re
print(bool(re.fullmatch(r'\d{11}', '13800138000')))   # True(11位手机号)
print(bool(re.fullmatch(r'\d{11}', '1380013800'))))   # False(10位)

运行结果:

复制代码
True
False

五、分组与命名分组

5.1 普通分组

复制代码
import re
m = re.match(r'(\d{4})-(\d{2})-(\d{2})', '2023-08-15')
print(m.groups())       # ('2023', '08', '15')
print(m.group(1), m.group(2), m.group(3))

5.2 命名分组 (?P<name>...)

复制代码
import re
m = re.match(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', '2023-08-15')
print(m.group('year'))     # 2023
print(m.group('month'))    # 08
print(m.groupdict())       # {'year': '2023', 'month': '08', 'day': '15'}

运行结果:

复制代码
('2023', '08', '15')
2023 08 15
2023
08
{'year': '2023', 'month': '08', 'day': '15'}

命名分组可读性远高于数字编号,复杂正则强烈推荐。


六、编译与标志位

6.1 re.compile

同一条正则要多次使用时,先用 re.compile 编译成 Pattern 对象,效率更高。

复制代码
import re
pattern = re.compile(r'\d+')
print(pattern.findall('a1b22'))    # ['1', '22']
print(pattern.search('abc333'))    # <Match object>

6.2 常用标志位

标志 含义
re.IGNORECASE / re.I 忽略大小写
re.MULTILINE / re.M ^ $ 匹配每行开头结尾
re.DOTALL / re.S . 也匹配换行符
re.VERBOSE / re.X 允许正则里写注释和换行,提高可读性
复制代码
import re
# 忽略大小写
print(re.findall(r'python', 'Python PYTHON python', re.I))
# ['Python', 'PYTHON', 'python']

# MULTILINE:每行开头
text = "line1\nline2\nline3"
print(re.findall(r'^line\d', text, re.M))
# ['line1', 'line2', 'line3']

# DOTALL:. 匹配换行
print(re.findall(r'<div>.*?</div>', '<div>a\nb</div>', re.S))
# ['<div>a\nb</div>']

运行结果:

复制代码
['Python', 'PYTHON', 'python']
['line1', 'line2', 'line3']
['<div>a\nb</div>']

VERBOSE 示例(可读性极佳):

复制代码
import re
phone_re = re.compile(r"""
    ^1              # 手机号以1开头
    [3-9]           # 第二位3-9
    \d{9}           # 后面9位数字
    $
""", re.VERBOSE)

print(bool(phone_re.fullmatch('13800138000')))   # True
print(bool(phone_re.fullmatch('12800138000')))   # False(第二位不能是2)

运行结果:

复制代码
True
False

七、常用实战模式

7.1 邮箱

复制代码
import re
email_re = re.compile(r'^[\w.+-]+@[\w-]+\.[\w.]+$')
tests = ['user@example.com', 'a.b+c@d.cn', 'bad@', '@x.com', 'ok@sub.domain.org']
for t in tests:
    print(t, bool(email_re.match(t)))

运行结果:

复制代码
user@example.com True
a.b+c@d.cn True
bad@ False
@x.com False
ok@sub.domain.org True

7.2 手机号

复制代码
import re
mobile_re = re.compile(r'^1[3-9]\d{9}$')
print(bool(mobile_re.match('13800138000')))   # True
print(bool(mobile_re.match('12345678901')))   # False

7.3 身份证(18位)

复制代码
import re
# 18位:6位地区码 + 8位生日 + 3位顺序 + 1位校验(X或数字)
id_re = re.compile(r'^\d{6}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dX]$')
print(bool(id_re.match('11010119900307891X')))   # True
print(bool(id_re.match('123456789012345678')))   # False

运行结果:

复制代码
True
False

7.4 URL

复制代码
import re
url_re = re.compile(r'^https?://[\w.-]+(:\d+)?(/[\w./-]*)?$')
print(bool(url_re.match('https://www.example.com:8080/path/to/page')))
print(bool(url_re.match('ftp://x.com')))

7.5 IP 地址

复制代码
import re
ip_re = re.compile(r'^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$')
print(bool(ip_re.match('192.168.1.1')))    # True
print(bool(ip_re.match('256.1.1.1')))      # False

7.6 日期

复制代码
import re
date_re = re.compile(r'\d{4}[-/]\d{2}[-/]\d{2}')
print(date_re.findall('日志 2023-08-15 错误,2023/08/16 修复'))  
# ['2023-08-15', '2023/08/16']

7.7 中文字符

复制代码
import re
# 中文 Unicode 范围 \u4e00-\u9fa5
print(re.findall(r'[\u4e00-\u9fa5]+', 'Hello 世界,你好 world!'))
# ['世界', '你好']

运行结果:

复制代码
['2023-08-15', '2023/08/16']
['世界', '你好']

八、实战案例

下面通过几个真实开发中高频出现的场景,把前面学的语法和方法综合运用起来。每个案例都给出完整可运行代码和运行结果,建议复制到本地跑一遍,加深理解。正则的熟练度很大程度上来自实战积累,光看不练很容易忘;遇到需求先自己试着写,再对照本文写法,进步最快。

8.1 从 HTML 中提取所有链接

复制代码
import re

html = '''
<html>
  <body>
    <a href="https://www.python.org">Python</a>
    <a href='https://github.com'>GitHub</a>
    <a href="/local/path">本地</a>
    <img src="logo.png">
  </body>
</html>
'''

# 提取所有 href 的值(兼容单引号双引号)
links = re.findall(r'href=["\']([^"\']+)["\']', html)
print("所有链接:", links)

# 只提取 http/https 开头的外链
outer = re.findall(r'href=["\'](https?://[^"\']+)["\']', html)
print("外链:", outer)

运行结果:

复制代码
所有链接: ['https://www.python.org', 'https://github.com', '/local/path']
外链: ['https://www.python.org', 'https://github.com']

8.2 日志分析

模拟一段 Nginx 访问日志,提取 IP、时间、请求方法、路径、状态码。

复制代码
import re

logs = """
192.168.1.10 - - [15/Aug/2023:10:00:01 +0800] "GET /index.html HTTP/1.1" 200 1234
10.0.0.5 - - [15/Aug/2023:10:00:02 +0800] "POST /api/login HTTP/1.1" 401 89
192.168.1.10 - - [15/Aug/2023:10:00:03 +0800] "GET /about HTTP/1.1" 404 64
"""

pattern = re.compile(r'''
    (?P<ip>\d+\.\d+\.\d+\.\d+)        # IP
    \s.*?\[                            # 中间内容到 [
    (?P<time>[^\]]+)                   # 时间
    \]\s"                              # ] "
    (?P<method>\w+)\s                  # 请求方法
    (?P<path>\S+)\s                    # 路径
    HTTP/[\d.]+"\s                     # HTTP 版本
    (?P<status>\d+)                    # 状态码
''', re.VERBOSE)

for line in logs.strip().splitlines():
    m = pattern.search(line)
    if m:
        print(f"IP={m.group('ip')} 时间={m.group('time')} {m.group('method')} {m.group('path')} -> {m.group('status')}")

运行结果:

复制代码
IP=192.168.1.10 时间=15/Aug/2023:10:00:01 +0800 GET /index.html -> 200
IP=10.0.0.5 时间=15/Aug/2023:10:00:02 +0800 POST /api/login -> 401
IP=192.168.1.10 时间=15/Aug/2023:10:00:03 +0800 GET /about -> 404

8.3 密码强度校验

规则:至少 8 位,含大写、小写、数字、特殊字符。

复制代码
import re

def check_password(pwd):
    if len(pwd) < 8:
        return False, "长度至少8位"
    if not re.search(r'[A-Z]', pwd):
        return False, "需包含大写字母"
    if not re.search(r'[a-z]', pwd):
        return False, "需包含小写字母"
    if not re.search(r'\d', pwd):
        return False, "需包含数字"
    if not re.search(r'[!@#$%^&*]', pwd):
        return False, "需包含特殊字符"
    return True, "强度合格"

for p in ['123', 'abcdefgh', 'Abcdefg1', 'Abcdefg1!']:
    print(p, '->', check_password(p))

运行结果:

复制代码
123 -> (False, '长度至少8位')
abcdefgh -> (False, '需包含大写字母')
Abcdefg1 -> (False, '需包含特殊字符')
Abcdefg1! -> (True, '强度合格')

8.4 从配置文件中提取键值对

实际开发中常遇到 .env、properties 这类配置文件,用正则批量提取键值对非常方便。

复制代码
import re

config = """
# 数据库配置
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASS=secret123

# 应用配置
DEBUG=true
LOG_LEVEL=info
"""

# 匹配 KEY=VALUE(跳过注释行和空行)
items = re.findall(r'^([A-Z_]+)=(.+)$', config, re.MULTILINE)
for k, v in items:
    print(f"{k} => {v}")

运行结果:

复制代码
DB_HOST => 127.0.0.1
DB_PORT => 3306
DB_USER => root
DB_PASS => secret123
DEBUG => true
LOG_LEVEL => info

8.5 敏感信息脱敏

日志里不小心打印了手机号、身份证,上线前要脱敏处理,正则一行搞定:

复制代码
import re

text = "用户13800138000下单,身份证11010119900307891X,邮箱a@b.com"

# 手机号中间4位打星
text = re.sub(r'(1[3-9]\d)\d{4}(\d{4})', r'\1****\2', text)
# 身份证出生年月日打星
text = re.sub(r'(\d{6})\d{8}(\d{3}[\dX])', r'\1********\2', text)
# 邮箱用户名只留首字符
text = re.sub(r'(\w)\w+@', r'\1***@', text)

print(text)

运行结果:

复制代码
用户138****8000下单,身份证110101********891X,邮箱a***@b.com

脱敏是数据安全基本功,正则是实现脱敏最简洁的工具。


8.6 批量替换文本中的日期格式

文档迁移时,常需要把各种日期格式统一成标准格式,正则配合回调函数非常灵活:

复制代码
import re

doc = """
发布日期:2023年08月15日
更新:2023.08.20
历史:2023/08/01 ~ 2023/08/10
"""

# 把 中文日期、点分日期、斜杠日期 统一成 YYYY-MM-DD
def normalize(m):
    y, mo, d = m.group('y'), m.group('m'), m.group('d')
    return f"{y}-{mo}-{d}"

pattern = re.compile(r'(?P<y>\d{4})[年./-](?P<m>\d{2})[月./-](?P<d>\d{2})日?')
result = pattern.sub(normalize, doc)
print(result)

运行结果:

复制代码
发布日期:2023-08-15
更新:2023-08-20
历史:2023-08-01 ~ 2023-08-10

一个正则同时兼容三种分隔符,配合命名分组提取年月日,再统一格式------这就是正则的威力。


九、正则性能优化

正则写不好会引发灾难性回溯,导致匹配极慢甚至卡死。优化建议:

  1. 尽量精确,少用 .* :能用 [^<>]+ 就别用 .*,减少回溯。

  2. 能用非贪婪就非贪婪 :.*? 比 .* 安全。

  3. 预编译 :多次使用的正则用 re.compile。

  4. 锚定边界 :加 ^ $ 或 \b 提前失败,减少无效尝试。

  5. 避免嵌套量词 :如 (a+)+ 这类写法是回溯灾难的元凶。

  6. 复杂校验拆成多步:与其写一个超长正则,不如分几个简单正则依次判断(参考密码校验)。

    import re, time

    反面教材:嵌套量词,匹配超长字符串会卡

    bad = re.compile(r'(a+)+b')
    s = 'a' * 30
    t0 = time.time()
    bad.search(s + 'b')
    print(f"耗时 {time.time()-t0:.4f}s") # 可能明显变慢


十、正则的局限性与替代方案

正则虽强大,但并非万能。理解它的边界,才能在合适场景用合适工具,避免把正则用在不该用的地方,写出难以维护的"天书"代码。

10.1 正则无法处理嵌套结构

正则本质是正则文法 ,无法匹配任意深度的嵌套(如括号配对、HTML 标签嵌套)。例如想匹配所有 <div>...</div> 且 div 内部还能嵌套 div,正则无能为力------这属于上下文无关文法,需要栈结构来配对。

复制代码
import re
# 嵌套 div,正则无法正确配对
html = '<div>外层<div>内层</div>外层</div>'
print(re.findall(r'<div>.*?</div>', html, re.S))
# ['<div>外层<div>内层</div>']  ← 错误,多了一个未闭合的 <div>

运行结果:

复制代码
['<div>外层<div>内层</div>']

10.2 何时该用解析器

场景 推荐工具
HTML 解析 BeautifulSoup、lxml
XML 解析 xml.etree.ElementTree
JSON 解析 json 模块
配置文件 configparser、toml
复杂语法 pyparsing、ANTLR

原则:结构化数据用专用解析器,扁平文本提取用正则。

10.3 正则与字符串方法的选择

简单场景用字符串方法更清晰、更快,不必动用正则:

复制代码
# 判断是否以某串开头
'hello'.startswith('he')        # 比 re.match(r'he', 'hello') 好
# 简单替换
'abc'.replace('b', 'B')         # 比 re.sub 好
# 简单分割
'a,b,c'.split(',')              # 比 re.split 好

心法:能用字符串方法解决就别上正则;正则搞不定的嵌套结构果断换解析器。

10.4 正则调试技巧

写复杂正则时,推荐用以下方法调试,避免盲改:

  1. 在线工具 regex101.com:可视化每一步匹配,支持 Python 风格,还能生成解释。

  2. 分步构建:先写最核心的部分测试通过,再逐步加量词、分组、边界。

  3. 用 re.VERBOSE 加注释:把正则拆成多行写,每行注释说明意图。

  4. 单元测试:把正则封装成函数,用 pytest 覆盖各种边界用例。

    import re

    def extract_price(text):
    """从文本提取价格(元)"""
    pattern = re.compile(r'''
    (?:价格|价|¥|¥|$) # 前缀:价格/价/货币符号
    \s* # 可选空白
    (\d+(?:.\d+)?) # 金额(整数或小数)
    \s* # 可选空白
    (?:元|CNY|RMB)? # 可选单位
    ''', re.VERBOSE)
    m = pattern.search(text)
    return float(m.group(1)) if m else None

    print(extract_price('商品价格 99.9 元')) # 99.9
    print(extract_price('¥128')) # 128.0
    print(extract_price('无价格信息')) # None

运行结果:

复制代码
99.9
128.0
None

十一、最佳实践

  1. 始终用原始字符串 r'...',避免 \ 被Python字符串转义吃掉。
  2. 格式校验用 fullmatch ,提取用 findall / finditer。
  3. 复杂正则用 re.VERBOSE 加注释,三个月后自己还能看懂。
  4. 命名分组 (?P<name>...) 比数字编号可读性高。
  5. 预编译 re.compile 提升循环里反复匹配的性能。
  6. HTML/XML 解析别全靠正则 ,复杂结构用 BeautifulSoup、lxml 更稳。
  7. 正则不是万能:涉及嵌套结构(括号配对、HTML 标签嵌套)正则无能为力,需用解析器。
  8. 测试正则 :推荐用 regex101.com 在线调试,可视化每一步匹配。

十二、小结

到这里,正则表达式从基础语法到实战应用的内容就全部讲完了。本文系统讲解了正则表达式从语法到实战的完整链路:

  • 基础元字符 :\d \w \s . \b、量词 * + ? {m,n}、锚点 ^ $、字符集 []、分组 ()、或 |。
  • 贪婪与非贪婪 :加 ? 切换,提取标签内容必用非贪婪。
  • re 模块七大方法 :match / search / findall / finditer / sub / split / fullmatch。
  • 分组与命名分组 :groups() / groupdict()。
  • 编译与标志位 :re.I / re.M / re.S / re.X。
  • 常用模式:邮箱、手机号、身份证、URL、IP、日期、中文。
  • 三大实战:HTML 链接提取、日志分析、密码强度校验。

核心心法:先想清楚要匹配什么,再写正则;写完用 re.VERBOSE 加注释;复杂需求拆成多步,别追求一个正则通吃。

下一篇我们将进入 Web 开发,用 Flask 搭建你的第一个网站,敬请期待!


作者:全栈陈序员 系列:Python 基础语法入门 专栏:Python 从零到一 如果对你有帮助,欢迎点赞、收藏、关注 ❤️

相关推荐
Dawson Zhu1 小时前
【AI编译器入门④】编译中端:图优化Pass全解析——DCE、常量折叠、算子融合与布局变换
人工智能·语言模型·架构·aigc·agi
Wang's Blog1 小时前
Java 项目实战: 交友项目 - 发布评论
java·开发语言·交友
甲维斯1 小时前
排名算法一变,国产模型全部滑铁卢了!
人工智能
屈冠成1 小时前
ShopXO商城系统测试报告
开发语言·spring·maven·lua
wx_xkq12881 小时前
优秘智能灵秘:数字分身内容的输入、制作与审核怎样衔接
人工智能·数字人·优秘智能·数字分身·品牌内容·灵秘
前沿在线1 小时前
黄仁勋 GTC 柏林演讲完整整理:从 AI 工厂到机器人,英伟达把下一步讲透了 | 前沿在线
人工智能·ai·大模型
weixin_446260851 小时前
AgentMoCo:面向长视界智能体的动量对比记忆优化框架
人工智能
多弗朗皮卡丘1 小时前
C++STL之空间配置器
开发语言·c++·内存池·空间配置器
tommyjiatong1 小时前
什么是PHP漏洞
开发语言·php