MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python创建正则字符集的方式

2022-08-161.4k 阅读

一、正则表达式基础回顾

在深入探讨 Python 创建正则字符集的方式之前,让我们先简单回顾一下正则表达式的基本概念。正则表达式(Regular Expression,常简写为 regex 或 RE)是一种用于描述、匹配和处理字符串模式的强大工具。它通过特定的字符和语法规则来定义字符串的模式,从而实现对字符串的高效搜索、替换等操作。

在正则表达式中,字符集是一个非常重要的概念。字符集定义了一组字符,匹配时只要字符串中的某个字符属于这个字符集,就认为匹配成功。例如,字符集 [abc] 表示匹配 abc 中的任意一个字符。

二、Python 中的 re 模块

Python 通过内置的 re 模块来支持正则表达式操作。在使用正则表达式之前,需要先导入 re 模块:

import re

re 模块提供了一系列函数,如 re.search()re.match()re.findall() 等,用于在字符串中执行正则表达式匹配操作。而创建正则字符集则是构建这些正则表达式的基础步骤之一。

三、简单字符集的创建

(一)列举字符

最基本的创建字符集的方式就是在方括号 [] 中列举出所有希望匹配的字符。例如,要匹配字符 abc,可以这样定义字符集:

pattern = re.compile(r'[abc]')
text = "abcdef"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

在上述代码中,re.compile(r'[abc]') 创建了一个正则表达式对象,该对象可以用于匹配字符串中 abc 中的任意一个字符。re.search() 函数在字符串 text 中搜索匹配的字符,找到后通过 match.group() 输出匹配到的字符。

(二)范围字符集

除了列举单个字符,还可以使用 - 来指定字符范围。例如,[a - z] 表示匹配任意小写字母,[0 - 9] 表示匹配任意数字。

pattern = re.compile(r'[a - z]')
text = "Hello123World"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

这里的 [a - z] 字符集将匹配字符串 text 中的第一个小写字母 e。需要注意的是,范围字符集是按照字符的 ASCII 码顺序来定义的。例如,[A - Z] 匹配大写字母,因为大写字母在 ASCII 码表中是连续排列的。

(三)排除字符集

在字符集的开头加上 ^ 符号,可以创建排除字符集。即匹配不在字符集中的字符。例如,[^a - z] 表示匹配任意非小写字母的字符。

pattern = re.compile(r'[^a - z]')
text = "Hello123World"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

在这个例子中,[^a - z] 字符集将匹配字符串 text 中的第一个非小写字母字符 H

四、特殊字符在字符集中的处理

(一)转义字符

在字符集中,一些特殊字符需要进行转义才能表示其字面意义。例如,方括号 [] 本身在正则表达式中有特殊含义,要匹配 [] 字符,需要在前面加上反斜杠 \

pattern = re.compile(r'[\[\]]')
text = "This is a [test] string"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

这里 [\[\]] 字符集可以匹配字符串 text 中的 [] 字符。同样,反斜杠 \ 本身也是特殊字符,要匹配反斜杠字符,需要写成 \\

pattern = re.compile(r'[\\]')
text = "C:\\Program Files"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

(二)点号 .

在正则表达式中,点号 . 通常表示匹配除换行符 \n 之外的任意字符。但在字符集中,点号 . 失去了这种特殊含义,仅表示匹配点号字符本身。

pattern = re.compile(r'[.]')
text = "This is a sentence."
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

(三)竖线 |

竖线 | 在正则表达式中用于表示逻辑或的关系,但在字符集中,它也失去了这种特殊功能,仅表示匹配竖线字符本身。

pattern = re.compile(r'[|]')
text = "a|b"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

(四)星号 *、加号 + 和问号 ?

星号 *、加号 + 和问号 ? 在正则表达式中有重复匹配的特殊含义,但在字符集中,它们仅表示匹配自身字符。

pattern = re.compile(r'[*+?]')
text = "a*b+c?"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

五、组合字符集

(一)嵌套字符集

字符集可以进行嵌套使用,例如 [[a - z][0 - 9]] 表示匹配一个小写字母或一个数字。虽然这种写法在功能上与 [a - z0 - 9] 类似,但在某些复杂情况下,嵌套字符集可以更清晰地表达逻辑。

pattern = re.compile(r'[[a - z][0 - 9]]')
text = "a1b2c3"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

(二)合并字符集

可以通过将多个字符集写在一起的方式来合并字符集。例如,[a - zA - Z][0 - 9] 表示匹配一个字母(大小写均可)后面跟着一个数字。

pattern = re.compile(r'[a - zA - Z][0 - 9]')
text = "a1B2c3"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

在这个例子中,[a - zA - Z][0 - 9] 字符集组合首先匹配一个字母,然后匹配一个数字,所以会匹配到字符串 text 中的 a1

六、预定义字符集

(一)数字字符集 \d

\d 是一个预定义字符集,它等价于 [0 - 9],用于匹配任意一个数字字符。

pattern = re.compile(r'\d')
text = "abc123def"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

(二)非数字字符集 \D

\D\d 相反,它等价于 [^0 - 9],用于匹配任意一个非数字字符。

pattern = re.compile(r'\D')
text = "abc123def"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

(三)空白字符集 \s

\s 用于匹配任意空白字符,包括空格、制表符 \t、换行符 \n、回车符 \r 等。它等价于 [ \t\n\r\f\v]

pattern = re.compile(r'\s')
text = "Hello\nWorld"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

(四)非空白字符集 \S

\S\s 相反,用于匹配任意非空白字符,等价于 [^ \t\n\r\f\v]

pattern = re.compile(r'\S')
text = "Hello\nWorld"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

(五)单词字符集 \w

\w 用于匹配任意单词字符,包括字母、数字和下划线,等价于 [a - zA - Z0 - 9_]

pattern = re.compile(r'\w')
text = "Hello_123"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

(六)非单词字符集 \W

\W\w 相反,用于匹配任意非单词字符,等价于 [^a - zA - Z0 - 9_]

pattern = re.compile(r'\W')
text = "Hello_123!"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

七、在字符集中使用 Unicode 字符

Python 的正则表达式对 Unicode 有很好的支持。可以在字符集中直接使用 Unicode 字符来定义匹配模式。例如,要匹配中文字符,可以使用 [\u4e00-\u9fff] 这个字符集,它涵盖了大部分常用的中文字符。

pattern = re.compile(r'[\u4e00-\u9fff]')
text = "你好,世界!"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

如果要匹配更广泛的 Unicode 字符范围,可以根据 Unicode 标准来指定范围。例如,[\U0001F600-\U0001F64F] 可以匹配大部分常见的表情符号。

pattern = re.compile(r'[\U0001F600-\U0001F64F]')
text = "😄 Hello World"
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

八、动态生成字符集

在实际应用中,有时需要根据程序的运行时条件动态生成正则字符集。例如,根据用户输入的一组字符来构建字符集。

user_input = input("请输入一组字符: ")
char_set = '[' + ''.join(user_input) + ']'
pattern = re.compile(char_set)
text = "This is a test string with " + user_input
match = pattern.search(text)
if match:
    print(f"匹配到: {match.group()}")

在上述代码中,首先获取用户输入的字符,然后将其构建成字符集格式,最后根据这个动态生成的字符集进行正则表达式匹配。

九、字符集与正则表达式分组的结合

(一)字符集在分组中的使用

正则表达式中的分组是通过圆括号 () 来定义的。字符集可以与分组结合使用,以实现更复杂的匹配逻辑。例如,([a - z]+)\d 表示先匹配一个或多个小写字母(作为一个分组),然后匹配一个数字。

pattern = re.compile(r'([a - z]+)\d')
text = "abc123def456"
match = pattern.search(text)
if match:
    print(f"整体匹配到: {match.group()}")
    print(f"分组匹配到: {match.group(1)}")

在这个例子中,match.group() 返回整个匹配的字符串 abc1match.group(1) 返回第一个分组匹配到的字符串 abc

(二)分组对字符集的影响

分组也可以影响字符集的作用范围。例如,(a|b)[0 - 9] 表示要么匹配 a 后面跟着一个数字,要么匹配 b 后面跟着一个数字。而 [a|b]([0 - 9]) 则表示匹配 ab 中的一个字符,然后匹配一个数字并将其作为一个分组。

pattern1 = re.compile(r'(a|b)[0 - 9]')
pattern2 = re.compile(r'[a|b]([0 - 9])')
text = "a1b2"
match1 = pattern1.search(text)
match2 = pattern2.search(text)
if match1:
    print(f"pattern1 匹配到: {match1.group()}")
if match2:
    print(f"pattern2 整体匹配到: {match2.group()}")
    print(f"pattern2 分组匹配到: {match2.group(1)}")

通过对比这两个例子,可以更清楚地看到分组对字符集作用范围的影响。

十、字符集的性能考虑

在使用正则字符集时,性能是一个需要考虑的因素。复杂的字符集和嵌套结构可能会导致匹配速度变慢。例如,一个包含大量字符列举和嵌套的字符集,在匹配长字符串时,需要进行更多的字符比较操作。

import time

# 复杂字符集
pattern1 = re.compile(r'[a - zA - Z0 - 9!@#$%^&*()_+-=][a - zA - Z0 - 9!@#$%^&*()_+-=][a - zA - Z0 - 9!@#$%^&*()_+-=]')
# 简单字符集
pattern2 = re.compile(r'[a - z]{3}')

text = "a" * 1000000

start_time1 = time.time()
match1 = pattern1.search(text)
end_time1 = time.time()

start_time2 = time.time()
match2 = pattern2.search(text)
end_time2 = time.time()

print(f"复杂字符集匹配时间: {end_time1 - start_time1} 秒")
print(f"简单字符集匹配时间: {end_time2 - start_time2} 秒")

在上述代码中,通过对比复杂字符集和简单字符集在匹配长字符串时的时间消耗,可以明显看到简单字符集的匹配速度更快。因此,在设计正则字符集时,应尽量保持简洁,避免不必要的复杂性,以提高程序的运行效率。

同时,预定义字符集如 \d\w 等在性能上通常比手动列举字符范围更高效,因为它们经过了优化。在可能的情况下,应优先使用预定义字符集。

另外,如果需要对大量字符串进行重复匹配操作,建议使用 re.compile() 方法将正则表达式编译成对象,而不是每次都使用临时的正则表达式字符串。这样可以提高性能,因为编译后的对象可以被缓存和复用。

pattern = re.compile(r'[a - z]')
for _ in range(10000):
    text = "abcdef"
    match = pattern.search(text)

相比之下,每次都使用临时字符串进行匹配:

for _ in range(10000):
    text = "abcdef"
    match = re.search(r'[a - z]', text)

后者由于每次都需要重新编译正则表达式,性能会明显低于前者。

通过合理设计字符集结构、优先使用预定义字符集以及编译正则表达式对象等方式,可以有效提高正则表达式在处理字符串时的性能,尤其是在处理大量数据的场景下。