Chapter 02 / 10

字符类与预定义字符集

方括号字符类、范围、取反、预定义类 \d \w \s 及 Unicode 属性

字符类(Character Class)

字符类用方括号 [...] 定义,匹配括号内的任意一个字符。字符类相当于把多个选项压缩成一个元字符位置。

字符类(Character Class)
[...] 定义的一组字符,引擎在该位置匹配括号内的任意一个字符。与 | 的区别:字符类只匹配单个字符,而 | 可以匹配多字符的模式。[abc] 等价于 a|b|c,但更简洁高效。
范围(Range)
在字符类中用 - 表示字符范围:[a-z] 匹配所有小写字母,[0-9] 匹配所有数字。范围基于字符的 ASCII/Unicode 码点顺序,因此 [A-z] 会包含 [ \ ] ^ _ ` 等意外字符——这是常见的误用。
取反字符类(Negated Character Class)
[ 后面紧接 ^,表示"不是这些字符中的任何一个"。[^aeiou] 匹配任何非元音字母的单个字符(包括数字、标点、空格等)。
/[aeiou]/g
hello world
字符类 [aeiou] 匹配任意一个元音字母
# 基本字符类
re.findall(r'[aeiou]', 'hello world')
# → ['e', 'o', 'o']

# 字符范围
re.findall(r'[a-z]+', 'Hello World 123')
# → ['ello', 'orld']  (只匹配小写字母)

re.findall(r'[a-zA-Z]+', 'Hello World 123')
# → ['Hello', 'World']  (大小写字母都匹配)

# 数字范围
re.findall(r'[0-9]+', 'abc 123 def 456')
# → ['123', '456']

# 取反字符类
re.findall(r'[^aeiou\s]+', 'hello world')
# → ['h', 'll', 'w', 'rld']  (非元音且非空格的字符序列)

字符类内的特殊规则

在字符类内部,大多数元字符失去其特殊含义,但有例外:

字符在字符类内的行为说明
]结束字符类(需转义为 \]放在最开头 []] 可以匹配字面 ]
-表示范围(在首尾或转义后为字面 -)[-az][az-][a\-z]
^紧接 [ 时表示取反其他位置是字面 ^
\转义(仍然有效)[\d] 匹配数字
.字面点号(不需要转义)[.?!] 匹配 . ? !
* + ?字面量词字符(不需要转义)[*+?] 匹配这三个字符
# 在字符类中匹配字面特殊字符
re.findall(r'[.!?]', 'Hello! How are you?')
# → ['!', '?']  (. ! ? 在字符类内都是字面字符)

# - 放在末尾,避免被解析为范围
re.findall(r'[a-z0-9-]+', 'hello-world_2026')
# → ['hello-world', '2026']  (- 在末尾,是字面连字符)

# 错误示例:[A-z] 包含了意外字符
import string
# [A-z] 的 ASCII 范围是 65-122,包含 [ \ ] ^ _ `
# 应该使用 [A-Za-z] 而不是 [A-z]
WARNING([A-z] 陷阱)不要使用 [A-z]!ASCII 中 Z(90)和 a(97)之间有 6 个字符:[ \ ] ^ _ `。正确写法是 [A-Za-z] 或使用预定义类 [a-zA-Z]。同理,[0-Z] 也包含了大量意外的标点符号。

预定义字符类(Predefined Character Classes)

常用字符集有简短的预定义写法,避免每次手写范围:

\d — 数字
等价于 [0-9](ASCII 模式)。在 Unicode 模式下匹配更广泛的数字字符(如阿拉伯数字、全角数字)。\D 是其反义,匹配非数字字符,等价于 [^0-9]
\w — 单词字符
等价于 [a-zA-Z0-9_](ASCII 模式):字母、数字和下划线。在 Unicode 模式下也匹配其他语言的字母。\W 是其反义,匹配非单词字符(标点、空格等)。
\s — 空白字符
匹配空格 、制表符 \t、换行 \n、回车 \r、换页 \f、垂直制表符 \v\S 是其反义,匹配非空白字符。
预定义类等价写法(ASCII 模式)含义
\d[0-9]数字 0-9
\D[^0-9]非数字
\w[a-zA-Z0-9_]单词字符(字母+数字+下划线)
\W[^a-zA-Z0-9_]非单词字符
\s[ \t\n\r\f\v]空白字符
\S[^ \t\n\r\f\v]非空白字符
/\w+@\w+\.\w+/g
Contact: user@example.com or admin@test.org
\w+ 匹配一个或多个单词字符,简化了邮箱的基本结构
# \d 匹配数字
re.findall(r'\d+', 'a1b22c333')
# → ['1', '22', '333']

# \w 匹配单词字符
re.findall(r'\w+', 'hello, world! 123')
# → ['hello', 'world', '123']

# \s 匹配空白(常用于分割)
re.split(r'\s+', '  hello   world  ')
# → ['', 'hello', 'world', '']

# \S 匹配非空白(提取不含空格的词)
re.findall(r'\S+', 'hello   world  2026')
# → ['hello', 'world', '2026']

在字符类中使用预定义类

预定义类可以嵌入字符类中,与其他字符组合:

# 字母或数字(\w 的子集,不包含下划线)
re.findall(r'[a-zA-Z\d]+', 'hello_world 123')
# → ['hello', 'world', '123']  (下划线被排除)

# 空白字符或逗号(用于分割 CSV 风格数据)
re.split(r'[\s,]+', 'a, b,c  d')
# → ['a', 'b', 'c', 'd']

# 非数字非空白(提取文字部分)
re.findall(r'[^\d\s]+', 'abc 123 def 456')
# → ['abc', 'def']

特殊转义序列

除了 \d \w \s,正则还支持其他重要的转义序列:

转义序列含义说明
\n换行符(LF)Unix/Linux 行尾
\r回车符(CR)Windows 行尾的第一个字符(\r\n)
\t制表符Tab 键
\0空字符(NUL)二进制数据处理
\xHH十六进制 ASCII 字符\x41 = 'A'
\uHHHHUnicode 字符\u4e2d = '中'(JS 语法)
\N{name}Unicode 命名字符Python: \N{SNOWMAN} = '☃'

单词边界(\b)的工作原理

\b 是一个零宽断言——它匹配一个位置,不消耗字符。理解它的工作原理有助于正确使用:

\b 的精确定义
\b 匹配 \w\W 之间的位置(或字符串开头/结尾与 \w 之间的位置)。具体来说:左边是单词字符(\w)且右边是非单词字符(\W),或者相反。字符串的开头和结尾被视为非单词字符。
# \b 精准匹配完整单词
re.findall(r'\bcat\b', 'cat cats catch concatenate')
# → ['cat']  (只有 'cat' 是完整单词)

re.findall(r'\bcat\w*', 'cat cats catch concatenate')
# → ['cat', 'cats', 'catch']  (以 cat 开头的单词)

# \b 的工作原理示意
# 字符串:" cat "
# 位置:  0 1 2 3 4
# 字符:  ' ' 'c' 'a' 't' ' '
# \b 在位置 1(空格→c,\W→\W... 等等)
# 实际上 \b 在 pos1 (空格与c之间) 和 pos4 (t与空格之间)

# 常见陷阱:\b 对连字符、下划线不生效
re.findall(r'\bcat\b', 'e-cat')
# → ['cat']  (- 是 \W,所以 e-cat 中 cat 前有 \b)

re.findall(r'\bcat\b', '_cat')
# → []  (_ 是 \w,所以 _cat 中 cat 前没有 \b)
INFO(\b 与中文)在处理中文文本时,\b 的表现可能出乎意料。中文汉字不属于 \w(ASCII 模式下),因此汉字与汉字之间的位置会被 \b 匹配,导致意外行为。处理中文时,一般不依赖 \b,而是使用中文分词工具或显式的边界模式。

Unicode 属性转义(ES2018 / Python regex 库)

对于需要匹配任意语言文字的场景,标准的 \w 不够用——它在 ASCII 模式下只匹配英文字母。Unicode 属性转义提供了更精确的控制:

// JavaScript(ES2018+),需要 u 标志
/\p{Letter}+/u.test('Héllo')        // true(包含重音字母)
/\p{Script=Han}/u.test('汉字')      // true(汉字)
/\p{Script=Hiragana}/u.test('あ')   // true(平假名)
/\p{Decimal_Number}/u.test('2')    // true(全角数字)

# Python:标准 re 模块对 Unicode 的支持有限
# 使用第三方 regex 库获得完整的 Unicode 属性支持
# pip install regex
import regex
regex.findall(r'\p{Han}+', 'hello 你好 world 世界')
# → ['你好', '世界']

POSIX 字符类(在 PCRE 中可用)

在 ERE(扩展正则表达式)和 PCRE 中,[:name:] 形式的 POSIX 字符类在方括号内使用:

POSIX 类等价写法含义
[:alpha:][a-zA-Z]字母
[:digit:][0-9]数字
[:alnum:][a-zA-Z0-9]字母或数字
[:lower:][a-z]小写字母
[:upper:][A-Z]大写字母
[:space:][ \t\n\r\f\v]空白字符
[:punct:]标点符号集标点符号
INFO(POSIX 类在 Python/JS 中的支持)Python 的 re 模块不支持 POSIX 字符类。JavaScript 也不支持。这种语法仅在 grep、awk、sed、PostgreSQL 等工具中有效。在 Python/JS 中使用对应的 \d\w\s 或明确的范围 [a-zA-Z]

常见字符类模式总结

# 验证:仅包含字母和数字
re.fullmatch(r'[a-zA-Z0-9]+', username)

# 提取:十六进制数字(如颜色值)
re.findall(r'#[0-9a-fA-F]{6}', 'color: #ff5733 and #abc123')
# → ['#ff5733', '#abc123']

# 提取:标点符号
re.findall(r'[^\w\s]', 'hello, world! it\'s fine.')
# → [',', '!', "'", '.']

# 提取:非 ASCII 字符(中文、日文等)
re.findall(r'[^\x00-\x7F]+', 'hello 世界 world')
# → ['世界']  (\x00-\x7F 是 ASCII 范围)

# 匹配:文件扩展名(图片文件)
re.findall(r'\.\w+$', 'photo.jpg', re.IGNORECASE)
# → ['.jpg']

# 验证:密码包含大写、小写、数字
# 用三个独立的 fullmatch 检查更清晰:
def has_upper(s): return bool(re.search(r'[A-Z]', s))
def has_lower(s): return bool(re.search(r'[a-z]', s))
def has_digit(s): return bool(re.search(r'\d', s))

小结

本章要点