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

Python使用正则表达式进行数据验证

2023-03-062.9k 阅读

Python 使用正则表达式进行数据验证

正则表达式基础概念

在深入探讨 Python 中如何使用正则表达式进行数据验证之前,我们先来回顾一下正则表达式的基本概念。正则表达式是一种用于描述、匹配和处理字符串模式的强大工具。它通过特殊的字符和语法来定义模式,然后根据这个模式在文本中查找、替换或验证数据。

字符类

字符类是正则表达式中的一个重要概念,它允许我们匹配一组字符中的任意一个。例如,[abc] 表示匹配 abc 中的任意一个字符。如果我们想要匹配一个小写字母,可以使用 [a - z],这表示匹配从 az 的任意一个小写字母。同样,[A - Z] 用于匹配大写字母,[0 - 9] 用于匹配数字。

元字符

正则表达式中有一些具有特殊含义的字符,称为元字符。比如 .,它可以匹配除换行符 \n 之外的任意一个字符。例如,模式 b.t 可以匹配 batbetbit 等字符串。另外,^ 表示匹配字符串的开头,$ 表示匹配字符串的结尾。例如,^hello 表示匹配以 hello 开头的字符串,world$ 表示匹配以 world 结尾的字符串。

量词

量词用于指定前面的字符或字符组出现的次数。常见的量词有 *(表示零次或多次)、+(表示一次或多次)、?(表示零次或一次)。例如,ab* 可以匹配 aababbabbb 等字符串,因为 b 可以出现零次或多次。而 ab+ 则只能匹配 ababbabbb 等字符串,因为 b 至少要出现一次。ab? 只能匹配 aab,因为 b 最多出现一次。

Python 中的 re 模块

Python 提供了内置的 re 模块来处理正则表达式。要使用正则表达式,首先需要导入这个模块。

import re

re.match() 函数

re.match() 函数用于从字符串的开头开始匹配模式。如果匹配成功,它返回一个匹配对象;如果匹配失败,则返回 None

import re

pattern = r'hello'
string = 'hello world'
match = re.match(pattern, string)
if match:
    print('匹配成功')
else:
    print('匹配失败')

在上述代码中,我们定义了一个模式 hello,然后使用 re.match() 函数在字符串 'hello world' 上进行匹配。由于字符串是以 hello 开头的,所以匹配成功。

re.search() 函数

re.search() 函数在整个字符串中搜索模式,而不仅仅是从开头开始。它同样返回一个匹配对象(如果找到匹配)或 None(如果未找到匹配)。

import re

pattern = r'world'
string = 'hello world'
match = re.search(pattern, string)
if match:
    print('匹配成功')
else:
    print('匹配失败')

这里我们搜索字符串 'hello world' 中是否包含 world,使用 re.search() 函数可以找到匹配,因为 world 虽然不在字符串开头,但在整个字符串中存在。

re.findall() 函数

re.findall() 函数用于在字符串中找到所有匹配模式的子字符串,并以列表的形式返回。

import re

pattern = r'\d+'
string = '我有 10 个苹果,他有 5 个橘子'
matches = re.findall(pattern, string)
print(matches)

在这个例子中,模式 \d+ 表示匹配一个或多个数字。re.findall() 函数在字符串中找到所有的数字子字符串,并返回一个列表 ['10', '5']

使用正则表达式进行数据验证

验证邮箱地址

邮箱地址是常见的需要验证的数据格式。一个有效的邮箱地址通常遵循一定的模式,例如 username@domain.com。我们可以使用正则表达式来验证邮箱地址的格式是否正确。

import re


def validate_email(email):
    pattern = r'^[a-zA-Z0 - 9_.+-]+@[a-zA-Z0 - 9 -]+\.[a-zA-Z0 - 9-.]+$'
    if re.match(pattern, email):
        return True
    else:
        return False


email1 = 'test@example.com'
email2 = 'test.example.com'
print(validate_email(email1))
print(validate_email(email2))

在上述代码中,模式 ^[a-zA-Z0 - 9_.+-]+@[a-zA-Z0 - 9 -]+\.[a-zA-Z0 - 9-.]+$ 的含义如下:

  • ^:表示字符串的开头。
  • [a-zA-Z0 - 9_.+-]+:表示用户名部分,可以包含字母、数字、下划线、点、加号和减号,并且至少出现一次。
  • @:邮箱地址中的 @ 符号。
  • [a-zA-Z0 - 9 -]+:表示域名部分,可以包含字母、数字和减号,至少出现一次。
  • \.:这里的 \ 是转义字符,因为 . 在正则表达式中有特殊含义,所以需要转义,表示实际的点号。
  • [a-zA-Z0 - 9-.]+:表示域名后缀部分,可以包含字母、数字、点和减号,至少出现一次。
  • $:表示字符串的结尾。

验证手机号码

在中国,手机号码通常是 11 位数字,并且以 1 开头。我们可以用正则表达式来验证手机号码的格式。

import re


def validate_phone(phone):
    pattern = r'^1[3 - 9]\d{9}$'
    if re.match(pattern, phone):
        return True
    else:
        return False


phone1 = '13800138000'
phone2 = '12345678901'
print(validate_phone(phone1))
print(validate_phone(phone2))

这里的模式 ^1[3 - 9]\d{9}$ 含义为:

  • ^:字符串开头。
  • 1:手机号码必须以 1 开头。
  • [3 - 9]:第二位数字必须是 3 到 9 之间的一个数字。
  • \d{9}:后面跟着 9 个数字。
  • $:字符串结尾。

验证身份证号码

身份证号码是 18 位数字,最后一位可能是数字也可能是 X。我们可以通过正则表达式进行初步验证。

import re


def validate_id_card(id_card):
    pattern = r'^[1 - 9]\d{5}(18|19|20)\d{2}(0[1 - 9]|1[0 - 2])(0[1 - 9]|[12]\d|3[01])\d{3}[\dXx]$'
    if re.match(pattern, id_card):
        return True
    else:
        return False


id_card1 = '11010519491231002X'
id_card2 = '11010519491231002A'
print(validate_id_card(id_card1))
print(validate_id_card(id_card2))

模式 ^[1 - 9]\d{5}(18|19|20)\d{2}(0[1 - 9]|1[0 - 2])(0[1 - 9]|[12]\d|3[01])\d{3}[\dXx]$ 的详细解释:

  • ^:字符串开头。
  • [1 - 9]:地址码的第一位不能是 0。
  • \d{5}:地址码的后 5 位数字。
  • (18|19|20):表示年份的前两位,可能是 18、19 或 20。
  • \d{2}:年份的后两位数字。
  • (0[1 - 9]|1[0 - 2]):月份,01 到 12。
  • (0[1 - 9]|[12]\d|3[01]):日期,01 到 31。
  • \d{3}:顺序码。
  • [\dXx]:校验码,可以是数字或者 X(不区分大小写)。
  • $:字符串结尾。

分组与捕获

在正则表达式中,我们可以使用括号 () 来对模式进行分组。分组不仅可以改变量词的作用范围,还可以捕获匹配到的子字符串。

import re

pattern = r'(\d{4})-(\d{2})-(\d{2})'
string = '2023-05-10'
match = re.search(pattern, string)
if match:
    year = match.group(1)
    month = match.group(2)
    day = match.group(3)
    print(f'年: {year}, 月: {month}, 日: {day}')

在上述代码中,模式 (\d{4})-(\d{2})-(\d{2}) 中使用括号进行了分组。match.group(1) 获取第一个分组匹配到的子字符串,即年份;match.group(2) 获取月份;match.group(3) 获取日期。

命名分组

除了普通分组,Python 的正则表达式还支持命名分组。命名分组使用 (?P<name>pattern) 的形式,其中 name 是组的名称,pattern 是具体的模式。

import re

pattern = r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})'
string = '2023-05-10'
match = re.search(pattern, string)
if match:
    year = match.group('year')
    month = match.group('month')
    day = match.group('day')
    print(f'年: {year}, 月: {month}, 日: {day}')

通过命名分组,我们可以通过组名更直观地获取匹配到的子字符串,而不需要依赖组的索引位置。

正则表达式的贪婪与非贪婪模式

在正则表达式中,量词默认是贪婪的,即尽可能多地匹配字符。例如,.* 会匹配尽可能多的字符,直到字符串的结尾。

import re

string = 'aabbb'
pattern = r'a.*b'
match = re.search(pattern, string)
if match:
    print(match.group())

上述代码中,模式 a.*b 匹配到的是 aabbb,因为 .* 是贪婪的,它会一直匹配到最后一个 b

如果我们想要非贪婪模式,即尽可能少地匹配字符,可以在量词后面加上 ?。例如,.*? 表示非贪婪匹配。

import re

string = 'aabbb'
pattern = r'a.*?b'
match = re.search(pattern, string)
if match:
    print(match.group())

此时,模式 a.*?b 匹配到的是 aab,因为 .*? 是非贪婪的,它在遇到第一个 b 时就停止匹配。

正则表达式的转义

在正则表达式中,一些字符具有特殊含义,如 ^$*+?.()[]{}\ 等。如果我们想要匹配这些字符本身,就需要使用转义字符 \

import re

pattern = r'\$100'
string = '价格是 $100'
match = re.search(pattern, string)
if match:
    print(match.group())

在上述代码中,模式 \$100 表示匹配字符串 $100,这里的 \ 用于转义 $,使其失去特殊含义,只表示普通的 $ 字符。

处理多行文本

有时候我们需要处理包含多行的文本。在这种情况下,^$ 默认只匹配字符串的开头和结尾。如果我们想要让它们匹配每一行的开头和结尾,可以使用 re.MULTILINE 标志。

import re

text = '''line1
line2
line3'''
pattern = r'^line'
matches = re.findall(pattern, text, re.MULTILINE)
print(matches)

在上述代码中,模式 ^line 在默认情况下只能匹配字符串开头的 line。但是通过使用 re.MULTILINE 标志,^ 可以匹配每一行的开头,所以会找到所有以 line 开头的行。

正则表达式的性能优化

在使用正则表达式进行数据验证时,性能是一个需要考虑的因素。复杂的正则表达式可能会导致匹配速度变慢。以下是一些性能优化的建议:

  1. 简化模式:尽量避免使用过于复杂的嵌套和重复结构。例如,如果可以用简单的字符类和量词组合实现相同的功能,就不要使用复杂的递归模式。
  2. 预编译模式:使用 re.compile() 函数预编译正则表达式。预编译后的模式对象可以在多次匹配中重复使用,提高效率。
import re

pattern = re.compile(r'\d+')
string1 = '有 10 个苹果'
string2 = '他有 5 个橘子'
matches1 = pattern.findall(string1)
matches2 = pattern.findall(string2)
  1. 减少回溯:回溯是正则表达式匹配过程中,当匹配失败时,尝试不同的匹配路径的过程。过多的回溯会导致性能下降。尽量编写明确、高效的模式,减少不必要的回溯。

复杂数据验证场景

在实际应用中,数据验证可能会遇到更复杂的场景。例如,验证 HTML 标签的结构是否正确。虽然正则表达式不是处理 HTML 结构的最佳工具(因为 HTML 语法较为复杂,使用专门的 HTML 解析库如 BeautifulSoup 更合适),但我们可以用简单的正则表达式来验证一些常见的标签格式。

import re


def validate_html_tag(tag):
    pattern = r'^<([a-zA-Z]+)(\s+[\w\s="]+)?>(.*?)</\1>$'
    match = re.match(pattern, tag)
    if match:
        return True
    else:
        return False


tag1 = '<div class="test">内容</div>'
tag2 = '<div class="test">内容</span>'
print(validate_html_tag(tag1))
print(validate_html_tag(tag2))

模式 ^<([a-zA-Z]+)(\s+[\w\s="]+)?>(.*?)</\1>$ 的含义:

  • ^:字符串开头。
  • <([a-zA-Z]+):匹配开始标签,捕获标签名(如 divp 等)。
  • (\s+[\w\s="]+)?:匹配标签内的属性部分,这部分是可选的。
  • >(.*?)</\1>:匹配标签内容和结束标签,\1 是反向引用,用于匹配与开始标签相同的标签名。
  • $:字符串结尾。

通过这种方式,我们可以初步验证一些简单的 HTML 标签结构是否正确。但需要注意的是,对于复杂的 HTML 结构和嵌套,正则表达式可能无法完全准确验证。

与其他数据验证方法的结合

在实际开发中,正则表达式通常不是单独使用的,而是与其他数据验证方法结合使用。例如,在验证用户输入的年龄时,除了使用正则表达式验证输入是否为数字外,还需要检查这个数字是否在合理的范围内。

import re


def validate_age(age_str):
    if not re.match(r'^\d+$', age_str):
        return False
    age = int(age_str)
    if 0 <= age <= 120:
        return True
    else:
        return False


age1 = '25'
age2 = 'abc'
age3 = '150'
print(validate_age(age1))
print(validate_age(age2))
print(validate_age(age3))

在上述代码中,首先使用正则表达式验证输入是否为纯数字,然后将其转换为整数并检查是否在 0 到 120 的合理范围内。这种结合多种验证方法的方式可以更全面地确保数据的有效性。

总结

正则表达式是 Python 中进行数据验证的强大工具,通过合理使用 re 模块提供的函数和正则表达式的各种语法特性,我们可以有效地验证各种数据格式,从简单的邮箱地址、手机号码到复杂的自定义数据结构。同时,在实际应用中要注意性能优化和与其他验证方法的结合,以确保数据验证的准确性和高效性。在处理复杂的文本结构如 HTML 时,要根据具体情况选择合适的工具,正则表达式并非总是最佳选择,但在很多简单数据验证场景下,它能快速、灵活地解决问题。希望通过本文的介绍,你能熟练掌握在 Python 中使用正则表达式进行数据验证的技巧,并应用到实际开发中。