🐍 第06课:字符串处理

—— 正则表达式与文本魔法

🏆 正则表达式5个实战
✅ Python验证通过

📌 本课目标

1️⃣ 字符串高级操作

# 分割与连接
csv_line = "张三,28,北京,Python开发"
fields = csv_line.split(",")
print(fields)  # ['张三', '28', '北京', 'Python开发']

# join 性能远优于 + 连接
result = " | ".join(["A", "B", "C"])
print(result)  # A | B | C

# partition - 分成三部分
url = "https://example.com/path"
protocol, _, rest = url.partition("://"
print(protocol, rest)  # https example.com/path

# 多种对齐方式
text = "Python"
print(text.ljust(20, "."))   # Python..............
print(text.rjust(20, "."))   # ..............Python
print(text.center(20, "="))  # =======Python=======
print(text.zfill(20))         # 00000000000000Python

# strip 系列去除空白
dirty = "  hello world  "
print(dirty.strip())    # "hello world"
print(dirty.lstrip())   # "hello world  "
print(dirty.rstrip())   # "  hello world"

# translate - 高性能字符替换
import string
table = str.maketrans("0123456789", "零一二三四五六七八九")
print("2024年".translate(table))  # 二零二四年

2️⃣ 正则表达式基础

语法含义示例
.任意字符(除换行)a.c → abc, a1c
\d数字 [0-9]\d+ → 123
\w单词字符 [a-zA-Z0-9_]\w+ → hello_1
\s空白字符a\sb → a b
^行首^Hello
$行尾world$
*0次或多次ab*c → ac, abc
+1次或多次ab+c → abc, abbc
?0次或1次ab?c → ac, abc
{n,m}n到m次\d{3,4} → 123, 1234
[...]字符集[aeiou] → 元音
(...)捕获组(\d+)-(\d+)
|cat|dog

3️⃣ re 模块核心函数

import re

# 1. search - 搜索第一个匹配
m = re.search(r'\d+', "订单号: ORD12345 已发货")
if m:
    print(m.group())   # 12345
    print(m.start())   # 8  起始位置
    print(m.end())     # 13 结束位置

# 2. match - 从字符串开头匹配
m = re.match(r'Hello', "Hello World")
assert m is not None

# 3. findall - 找到所有匹配
numbers = re.findall(r'\d+', "a1b22c333")
print(numbers)  # ['1', '22', '333']

# 4. finditer - 迭代所有匹配(获取位置信息)
for m in re.finditer(r'\d+', "a1b22c333"):
    print(f"位置{m.start()}: {m.group()}")

# 5. sub - 替换
clean = re.sub(r'\s+', ' ', "  多余   空白  ")
print(clean)  # " 多余 空白 "

# 6. split - 分割
parts = re.split(r'[,;|]', "a,b;c|d")
print(parts)  # ['a', 'b', 'c', 'd']

# 编译正则(重复使用时提升性能)
pattern = re.compile(r'\d+')
print(pattern.findall("abc123def456"))  # ['123', '456']

# 常用标志
# re.IGNORECASE - 忽略大小写
# re.MULTILINE  - ^$ 匹配每行首尾
# re.DOTALL     - . 匹配换行符
# re.VERBOSE    - 允许注释和空白

4️⃣ 实战1:验证手机号

import re

def validate_phone(phone: str) -> bool:
    """验证中国大陆手机号"""
    pattern = r'^1[3-9]\d{9}$'
    return bool(re.match(pattern, phone))

# 测试
assert validate_phone("13812345678") == True
assert validate_phone("12345678901") == False  # 不以1[3-9]开头
assert validate_phone("1381234567") == False   # 不足11位
print("✅ 手机号验证完成")

5️⃣ 实战2:提取邮箱

import re

def extract_emails(text: str) -> list[str]:
    """从文本中提取所有邮箱地址"""
    pattern = r'[\w.+-]+@[\w-]+\.[\w.]+'
    return re.findall(pattern, text)

text = """
联系:support@example.com 或 admin@test.org.cn
无效:not@email @missing.com
另外:user.name+tag@company.co.uk
"""
emails = extract_emails(text)
print(emails)
# ['support@example.com', 'admin@test.org.cn', 'user.name+tag@company.co.uk']
assert "support@example.com" in emails
print("✅ 邮箱提取完成")

6️⃣ 实战3:解析日志

import re

def parse_nginx_log(line: str) -> dict | None:
    """解析 Nginx 访问日志行"""
    pattern = r'(?P\S+) \S+ \S+ \[(?P \
              r(?P\S+) (?P\S+) \S+" ' \
              r(?P\d+) (?P\d+)'
    m = re.match(pattern, line)
    if m:
        return m.groupdict()
    return None

log_line = '192.168.1.1 - - [10/Oct/2023:13:55:36 +0800] "GET /api/users HTTP/1.1" 200 1234'
result = parse_nginx_log(log_line)
assert result["ip"] == "192.168.1.1"
assert result["status"] == "200"
assert result["method"] == "GET"
print("✅ 日志解析完成")

7️⃣ 实战4:清洗HTML标签

import re

def strip_html(html: str) -> str:
    """去除HTML标签,保留文本内容"""
    # 去除script和style内容
    text = re.sub(r']*>.*?', '', html, flags=re.DOTALL|re.IGNORECASE)
    text = re.sub(r']*>.*?', '', text, flags=re.DOTALL|re.IGNORECASE)
    # 去除所有HTML标签
    text = re.sub(r'<[^>]+>', '', text)
    # 处理HTML实体
    text = text.replace('&', '&').replace('<', '<').replace('>', '>')
    # 压缩空白
    text = re.sub(r'\s+', ' ', text).strip()
    return text

html = '<h1>标题</h1><p>段落</p>'
result = strip_html(html)
assert '标题' in result and '段落' in result
print("✅ HTML清洗完成")

8️⃣ 实战5:密码强度检测

import re

def check_password_strength(password: str) -> dict:
    """检测密码强度"""
    checks = {
        "长度>=8": len(password) >= 8,
        "包含大写": bool(re.search(r'[A-Z]', password)),
        "包含小写": bool(re.search(r'[a-z]', password)),
        "包含数字": bool(re.search(r'\d', password)),
        "包含特殊字符": bool(re.search(r'[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]', password)),
    }
    score = sum(checks.values())
    if score >= 5:
        level = "强"
    elif score >= 3:
        level = "中"
    else:
        level = "弱"
    return {"score": score, "level": level, "checks": checks}

result = check_password_strength("MyP@ss123")
assert result["level"] == "强"
assert result["score"] == 5
print("✅ 密码强度检测完成")

9️⃣ 验证脚本

#!/usr/bin/env python3
"""第06课 字符串处理验证"""
import re

def test_string_ops():
    """字符串高级操作"""
    assert " | ".join(["A","B","C"]) == "A | B | C"
    assert "Python".ljust(10, ".") == "Python...."
    assert "Python".center(10, "=") == "==Python=="
    table = str.maketrans("0123456789", "零一二三四五六七八九")
    assert "2024".translate(table) == "二零二四"
    print("✅ 字符串高级操作测试通过")

def test_regex_phone():
    """手机号验证"""
    pattern = r'^1[3-9]\d{9}$'
    assert bool(re.match(pattern, "13812345678"))
    assert not bool(re.match(pattern, "12345678901"))
    print("✅ 手机号验证测试通过")

def test_regex_email():
    """邮箱提取"""
    text = "联系:support@example.com 和 admin@test.org"
    emails = re.findall(r'[\w.+-]+@[\w-]+\.[\w.]+', text)
    assert "support@example.com" in emails
    print("✅ 邮箱提取测试通过")

def test_regex_log():
    """日志解析"""
    line = '192.168.1.1 - - [10/Oct/2023:13:55:36 +0800] "GET /api HTTP/1.1" 200 1234'
    pattern = r'(?P\S+) \S+ \S+ \[(?P \
              r(?P\S+) (?P\S+) \S+" (?P\d+) (?P\d+)'
    m = re.match(pattern, line)
    assert m.group("ip") == "192.168.1.1"
    assert m.group("status") == "200"
    print("✅ 日志解析测试通过")

def test_password_strength():
    """密码强度"""
    pwd = "MyP@ss123"
    checks = {
        "len": len(pwd) >= 8,
        "upper": bool(re.search(r'[A-Z]', pwd)),
        "lower": bool(re.search(r'[a-z]', pwd)),
        "digit": bool(re.search(r'\d', pwd)),
        "special": bool(re.search(r'[!@#$%^&*]', pwd)),
    }
    assert all(checks.values())
    print("✅ 密码强度测试通过")

if __name__ == "__main__":
    test_string_ops()
    test_regex_phone()
    test_regex_email()
    test_regex_log()
    test_password_strength()
    print("\n🎉 第06课全部验证通过!")
✅ 字符串高级操作测试通过 ✅ 手机号验证测试通过 ✅ 邮箱提取测试通过 ✅ 日志解析测试通过 ✅ 密码强度测试通过 🎉 第06课全部验证通过!

🔑 本课要点

  1. join > +——字符串连接用 join,性能差距巨大
  2. 命名捕获组 (?P<name>...)——让正则可读性大增
  3. re.VERBOSE——复杂正则用此标志加注释
  4. 编译正则——重复使用的正则务必预编译
  5. 贪婪 vs 非贪婪——.*? 非贪婪,.* 贪婪