Skip to content

数学、工具、字符、正则

更新: 2025/2/24 字数: 0 字 时长: 0 分钟

前面提到 Python 自带许多标准库,使用起来也很方便,通过关键字 import 导入即可使用。

数学

math 模块

math 模块:内置常用数学函数的标准库。

python
import math

print(math.pi)            # 输出:3.141592653589793。注释:圆周率。
print(math.e)             # 输出:2.718281828459045。注释:对数e。
print(math.ceil(5.5))     # 输出:6。注释:向上取整。
print(math.ceil(-5.5))    # 输出:-5。注释:向上取整。
print(math.floor(5.5))    # 输出:5。注释:向下取整。
print(math.floor(-5.5))   # 输出:-6。注释:向下取整。
print(math.factorial(3))  # 输出:6。注释:3的阶乘。
print(math.sqrt(9))       # 输出:3.0。注释:9的平方根。

random 模块

random 模块:内置随机数的标准库。

python
import random

"""随机数字"""
# random()返回随机生成的一个浮点小数,它在[0,1)范围内(不包括1)
print(random.random())               # 输出:0.09690599908884856
# 生成一个浮点小数,它在[70,100)范围内
print(random.random() * 30 + 70)     # 输出:71.92524201437448
# randint(M,N)返回随机生成的一个实数,它在[M, N]范围内(包括N)
print(random.randint(1, 3))          # 输出:3
# randrange(M,N)返回随机生成的一个实数,它在[M, N)范围内(不包括N)
print(random.randrange(0, 100, 10))  # 输出:30。注释:设定步长为10,产生的随机数间隔都是10的整数倍。

"""随机抽样"""
list1 = [1, 2, 3]
# choice(容器数据):抽取一次容器内的一个元素
print(random.choice(list1))        # 输出:3
# choices(容器数据, K=抽取次数):随机有放回的抽取容器内的元素(一个元素可能抽取多次)
print(random.choices(list1, k=5))  # 输出:[3, 2, 1, 2, 1]
# sample(容器数据, K=抽取次数):随机不放回的抽取容器内的元素(一个元素只能抽取一次),参数K的值不能大于容器数据的个数
print(random.sample(list1, k=2))   # 输出:[1, 3]
# shuffle(容器数据):在原列表的基础上打乱元素的所在位置,整个过程不会产生新列表
random.shuffle(list1)
print(list1)			           # 输出:[1, 3, 2]。注释:列表是可变类型,支持原地改变。

建议

字符串属于不可变对象,也就是无法改变字符串内容,而 random.shuffle() 方法是在原对象上打乱内容,因此字符串是无法使用该方法的。

heapq 模块

heapq 模块:**内部实现了堆排序算法,如果希望使用堆排序,尤其是要解决TopK问题(从序列中找到K个最大或最小元素),直接使用该模块即可。**代码如下所示:

  • heapq.nlargest(n, iterable, key=None) 遍历可迭代对象,按 key 值进行比较,找出最大的 n 个元素,降序输出。

  • heapq.nsmallest(n, iterable, key=None) 遍历可迭代对象,按 key 值进行比较,找出最小的 n 个元素,降序输出。

python
import heapq

list1 = [6, 3, 8, 11, 1, 5, 7, 3]
# 选择出列表中最大的3个元素,降序输出
print(heapq.nlargest(3, list1))   # 输出:[11, 8, 7]
# 选择出列表中最小的3个元素,升序输出
print(heapq.nsmallest(3, list1))  # 输出:[1, 3, 3]

list2 = [
    {'name': 'IBM', 'shares': 100, 'price': 91.1},
    {'name': 'AAPL', 'shares': 50, 'price': 543.22},
    {'name': 'FB', 'shares': 200, 'price': 21.09},
    {'name': 'HPQ', 'shares': 35, 'price': 31.75},
    {'name': 'YHOO', 'shares': 45, 'price': 16.35},
    {'name': 'ACME', 'shares': 75, 'price': 115.65}
]
# 找出价格最高的两只股票
print(heapq.nlargest(2, list2, key=lambda x: x['price']))  # 输出:[{'name': 'AAPL', 'shares': 50, 'price': 543.22}, {'name': 'ACME', 'shares': 75, 'price': 115.65}]
# 找出持有数量最高的两只股票
print(heapq.nlargest(2, list2, key=lambda x: x['shares']))  # 输出:[{'name': 'FB', 'shares': 200, 'price': 21.09}, {'name': 'IBM', 'shares': 100, 'price': 91.1}]

工具

itertools 模块

itertools 模块:为高效循环生成各种各样的迭代器。

python
import itertools

# 产生AB的全排列
res = [value for value in itertools.permutations('AB')]
print(res)  # 输出:[('A', 'B'), ('B', 'A')]

# 产生AB的二选一组合
res = [value for value in itertools.combinations('AB', 1)]
print(res)  # 输出:[('A'), ('B')]

# 产生AB和12的笛卡尔积
res = [value for value in itertools.product('AB', '12')]
print(res)  # 输出:[('A', '1'), ('A', '2'), ('B', '1'), ('B', '2')]

除了可以生成各种组合排列外,itertools 模块的 zip_longest 方法实现了类似内置 zip 函数的效果,具体看下面的案例:

python
import itertools

a = ('a', 'b')
b = ('1', '2', '3')
# 内置zip函数以最短的可迭代对象为准,过长的部分直接丢弃。
print(list(zip(a, b)))                    # 输出:[('a', '1'), ('b', '2')]
# zip_longest方法以最长的可迭代对象为准,缺失的部分用None补齐。
print(list(itertools.zip_longest(a, b)))  # 输出:[('a', '1'), ('b', '2'), (None, '3')]

functools 模块

functools 模块:为高阶函数而实现的,用于增强函数功能。

python
"""以斐波那契数函数为例"""
def fib(n: int) -> int:
    # 终止条件
    if n == 1 or n == 2:
        return n - 1
    # 递归调用
    return fib(n - 1) + fib(n - 2)

前面我们学习过这个斐波那契数函数,它每向后计算一个斐波拉切数所消耗的时间约等于计算上一个斐波拉切数所消耗时间的两倍。当时我们介绍了使用字典保存结果变量交换累加这两种方法来优化该函数的计算效率,不过这两种方法在一定程度上对函数的源码进行了改动,而这里通过使用 functools 模块中的 lru_cache 缓存装饰器,可以在不改动函数源码的基础上优化计算效率,代码如下:

python
# 从functools导入lru_cache缓存装饰器(它里面采用了缓存的置换算法,如果缓存放满了,它会把最近最少用到的数据给替换掉)
from functools import lru_cache

# lru_cache装饰器缓存最近结果,最大限制为10条
@lru_cache(maxsize=10)
def fib(n: int) -> int:
    # 终止条件
    if n == 1 or n == 2:
        return n - 1
    # 递归调用
    return fib(n - 1) + fib(n - 2)

建议

functools 模块中还有一个 cache 缓存装饰器,它是 lru_cache(maxsize=None) 的别名,意味着它提供了一个不受限制的缓存。这意味着缓存项的数量没有上限,所有函数调用结果都会被缓存,直到程序结束或缓存被手动清除。

字符

string 模块:负责提供常用字符串。

python
import string

print(string.whitespace)       # 输出:' \t\n\r\v\f'。注释:返回所有的空格字符。
print(string.ascii_lowercase)  # 输出:'abcdefghijklmnopqrstuvwxyz'。注释:返回所有的小写字母。
print(string.ascii_uppercase)  # 输出:'ABCDEFGHIJKLMNOPQRSTUVWXYZ'。注释:返回所有的大写字母。
print(string.ascii_letters)    # 输出:'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'。注释:返回所有的大小写字母。
print(string.octdigits)        # 输出:'01234567'。注释:返回所有的八进制数字字符串。
print(string.digits)           # 输出:'0123456789'。注释:返回所有的十进制数字字符串。
print(string.hexdigits)        # 输出:'0123456789abcdefABCDEF'。注释:返回所有的十六进制数字字符串。
print(string.punctuation)      # 输出:'!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~'。注释:返回所有的英文格式的标点符号。

正则

正则是一套对字符串进行查找、匹配、切割、替换用的规则,而正则表达式是一个符合正则规范的字符串,它具有以下特点:

  1. 正则表达式使用元字符组成。
  2. 正则表达式前面一般要加 r 来阻止转义。
  3. 正则表达式所有编程语言都可以使用。

元字符

正则元字符:具有固定正则含义的特殊符号。常用元字符如下:

符号作用案例
.匹配一个任意的字符(换行符除外)匹配一个字符 C
\w匹配一个数字、字母或者下划线的字符匹配一个字符 4
\W匹配一个非字母、数字或者下划线的字符匹配一个字符 #
\s匹配一个空白字符,包括空格,换行,制表符。匹配一个字符 \n
\S匹配一个非空白字符,除了空格,换行,制表符。匹配一个字符 A
\d匹配一个数字字符匹配一个字符 3
\D匹配一个非数字字符匹配一个字符 2
\b检测边界(边界可以是中英文的空格、逗号、句号、问号其中之一)
\B检测非边界(边界可以是中英文的空格、逗号、句号、问号其中之一)
\n匹配一个换行符
\t匹配一个制表符
\A匹配输入字符串的开始位置
\Z匹配输入字符串的结束位置
^检测字符串开始匹配以 The 开头的字符串 ^The
$检测字符串结束匹配以 The 结束的字符串 The$
[]匹配 [] 中出现的任意一个字符[0-9] 匹配任意数字,[a-zA-Z] 匹配任意大小写字母
[^]匹配不在 [] 出现的任意一个字符
*匹配 0 次或者多次(贪婪模式)
+匹配 1 次或者多次(贪婪模式)
?匹配 0 次或者 1 次(贪婪模式)
{m}匹配 m 次(贪婪模式)
{m,}匹配大于等于 m 次(贪婪模式)
{m, n}匹配至少 m 次最多 n 次(贪婪模式)
*?匹配 0 次或者多次(非贪婪模式)
+?匹配 1 次或者多次(非贪婪模式)
??匹配 0 次或者 1 次(非贪婪模式)
{m,}?匹配 m 次(非贪婪模式)
{m, n}?匹配至少 m 次最多 n 次(非贪婪模式)
``分之(结果可以是‘|’左边或右边的正则表达式匹配的结果)
()整合(匹配的时候让 () 中的正则条件变成一个整体)

警告

正则表达式符号中有很多用到 \ 反斜杠,其功能不是转义,而是表示特殊的意义。如果想在正则表达式中只表达 .\[]{}()*+?|$^ 这些字符的符号含义,需要在前面加 \ 反斜杠。

表达式

数字校验

描述正则表达式备注
数字^[0-9]*$
n位数字^\d{n}$
至少n位数字^\d{n,}$
m~n位数字^\d{m,n}$
整数^(-?[1-9]\d*)$非0开头,包括正整数和负整数
正整数^[1-9]\d*$
负整数^-[1-9]\d*$
非负整数`^(([1-9]\d*)0)$`
非正整数`^((-[1-9]\d*)0)$`
浮点数`^-?(?:[1-9]\d*.\d*0.\d*[1-9]\d*
正浮点数`^(?:[1-9]\d*.\d*0.\d*[1-9]\d*)$`
负浮点数`^-(?:[1-9]\d*.\d*0.\d*[1-9]\d*)$`
非正浮点数`^(?😦?:[1-9]\d*.\d+0.\d*[1-9]\d*)
非负浮点数`^(?:[1-9]\d*.\d+0.\d+
仅一位小数`^-?(?:0[1-9][0-9]*).[0-9]{1}$`
最少一位小数`^-?(?:0[1-9][0-9]*).[0-9]{1,}$`
最多两位小数`^-?(?:0[1-9][0-9]*).[0-9]{1,2}$`
连续重复的数字^(\d)\1+$例如:111222

字符校验

描述正则表达式备注
中文^[\u4E00-\u9FA5]+$
全角字符^[\uFF00-\uFFFF]+$
半角字符^[\u0000-\u00FF]+$
英文字符串(大写)^[A-Z]+$
英文字符串(小写)^[a-z]+$
英文字符串(不区分大小写)^[A-Za-z]+$
中文和数字`^(?:[\u4E00-\u9FA5]\d)+$`
英文和数字^[A-Za-z0-9]+$
数字、英文字母或者下划线组成的字符串^\w+$
中文、英文、数字包括下划线^[\u4E00-\u9FA5\w]+$
不含字母的字符串^[^A-Za-z]*$
连续重复的字符串^(.)\1+$例如:aabb
长度为n的字符串^.{n}$
ASCII^[ -~]$

日期和时间校验

描述正则表达式备注
日期`^\d{1,4}-(?:1[0-2]0?[1-9])-(?:0?[1-9]
日期`^(?😦?!0000)[0-9]{4}-(?😦?:0[1-9]1[0-2])-(?:0[1-9]
时间`^(?:1[0-2]0?[1-9]):[0-5]\d:[0-5]\d$`
时间`^(?:[01]\d2[0-3]):[0-5]\d:[0-5]\d$`
日期+时间`^(\d{1,4}-(?:1[0-2]0?[1-9])-(?:0?[1-9]

日常生活相关

描述正则表达式备注
中文名^[\u4E00-\u9FA5·]{2,16}$
英文名^[a-zA-Z][a-zA-Z\s]{0,20}[a-zA-Z]$
车牌号^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]$不含新能源
车牌号`^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z](?😦?:[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳])(?😦?:\d{5}[A-HJK])
火车车次^[GCDZTSPKXLY1-9]\d{1,4}$例如:G1234
手机号`^(?😦?:+00)86)?1[3-9]\d{9}$`
手机号`^(?😦?:+00)86)?1(?😦?:3[\d])
固话号码`^(?😦?:\d{3}-)?\d^(?:\d{4}-)?\d{7,8})(?:-\d+)?$`
手机IMEI码^\d{15,17}$一般是15位
邮编`^(?:0[1-7]1[0-356]
统一社会信用代码^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$
身份证号码(1代)`^[1-9]\d{7}(?:0\d10
身份证号码(2代)`^[1-9]\d{5}(?:1819
QQ号^[1-9][0-9]{4,}$一般是5到10位
微信号^[a-zA-Z][-_a-zA-Z0-9]{5,19}$一般6~20位,字母开头,可包含字母、数字、-、_,不含特殊字符
股票代码`^(s[hz]S[HZ])(000[\d]
银行卡卡号`^[1-9]{1}(?:\d\d{18})$`

互联网相关

描述正则表达式备注
域名^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(?:\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$例如:r2coding.com
网址^(?:https?:\/\/)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(?:\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$例如:https://www.r2coding.com/
带端口号的网址(或IP)^(?:https?:\/\/)?[\w-]+(?:\.[\w-]+)+:\d{1,5}\/?$例如:http://127.0.0.1:8888/
URL^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)$例如:https://www.r2coding.com/#/README?id=1
邮箱email^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$只允许英文字母、数字、下划线、英文句号、以及中划线组成,例如:[email protected]
邮箱email^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$允许汉字、字母、数字,域名只允许英文域名,例如:高子航[email protected]
用户名^[a-zA-Z0-9_-]{4,20}$4到20位
弱密码^[\w]{6,16}$6~16位,包含大小写字母和数字的组合
强密码^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@\.#$%^&*? ]).*$至少6位,包括至少1个大写字母,1个小写字母,1个数字,1个特殊字符
端口号`^(?:[0-9][1-9][0-9]
IPv4地址`^(?😦?:\d[1-9]\d
IPv4地址+端口`^(?😦?:\d[1-9]\d
IPv6地址`^(([0-9a-fA-F]{1,4}😃{7,7}[0-9a-fA-F]([0-9a-fA-F]{1,4}😃{1,7}:
IPv6地址+端口`^[(([0-9a-fA-F]{1,4}😃{7,7}[0-9a-fA-F]([0-9a-fA-F]{1,4}😃{1,7}:
子网掩码`^(?:254252
MAC地址`^(?😦?:[a-f0-9A-F]{2}😃(?:[a-f0-9A-F]{2}-){5})[a-f0-9A-F]{2}$`
Version版本号^\d+(?:\.\d+){2}$例如:12.1.1
图片后缀`.(gifpng
视频后缀`.(swfavi
图片链接`(?:https?://)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(?:.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+.+.(gifpng
视频链接`(?:https?://)?[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(?:.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+.+.(swfavi
迅雷链接thunderx?:\/\/[a-zA-Z\d]+=
ed2k链接ed2k:\/\/|file|.+|\/
磁力链接magnet:\?xt=urn:btih:[0-9a-fA-F]{40,}.*

其他

描述正则表达式备注
MD5格式`^(?:[a-f\d][A-F\d]{32})$`
BASE64格式^\s*data:(?:[a-z]+\/[a-z0-9-+.]+(?:;[a-z-]+=[a-z0-9-]+)?)?(?:;base64)?,([a-z0-9!$&',()*+;=\-._~:@/?%\s]*?)\s*$例如:data:image/jpeg;base64,xxxx==
UUID^[a-f\d]{4}(?:[a-f\d]{4}-){4}[a-f\d]{12}$例如:94f9d45a-71b0-4b3c-b69d-20c4bc9c8fdd
16进制^[A-Fa-f0-9]+$例如:FFFFFF
16进制颜色`^#?([0-9a-fA-F][0-9a-fA-F]{6})$`
SQL语句`^(?:selectdrop
Java包名^(?:[a-zA-Z_]\w*)+(?:[.][a-zA-Z_]\w*)+$例如:com.r2coding.controller
文件扩展名`.(?:docpdf
Windows文件路径^[a-zA-Z]:(?:\\[\w\u4E00-\u9FA5\s]+)+[.\w\u4E00-\u9FA5\s]+$例如:C:\Users\Administrator\Desktop\a.txt
Windows文件夹路径^[a-zA-Z]:(?:\\[\w\u4E00-\u9FA5\s]+)+$例如:C:\Users\Administrator\Desktop
Linux文件路径^\/(?:[^/]+\/)*[^/]+$例如:/root/library/a.txt
Linux文件夹路径^\/(?:[^/]+\/)*$例如:/root/library/

常用方法

re 模块:内置执行正则表达式的标准库。

单个匹配结果

  • re.match(正则表达式,字符串) 从字符串头部开始进行匹配,返回匹配成功的 re.Match 对象,否则返回 None
python
# 导入re模块
import re

# \d:匹配一个数字字符
print(re.match(r'0\d\d', '01'))         # 输出:None
print(re.match(r'0\d\d', '0190'))       # 输出:<... span=(0, 3), match='019'>

# +?:重复一次或多次,尽可能少的重复
print(re.match(r'ba+?','baaaa'))        # 输出:<... span=(0, 2), match='ba'>

# *?:重复任意次,尽可能少的重复
print(re.match(r'ba*?b','baaaaab'))     # 输出:<... span=(0, 7), match='baaaaab'>

# \b:检测边界(边界可以是空格、逗号、问号,但数字、字母不行)
print(re.match(r'\b...\b', 'jsh'))      # 输出:<... span=(0, 3), match='jsh'>
print(re.match(r'\b...\b', 'jsh   '))   # 输出:<... span=(0, 3), match='jsh'>
print(re.match(r'\b...\b', 'jsh,abc'))  # 输出:<... span=(0, 3), match='jsh'>
print(re.match(r'\b...\b', 'jshb'))     # 输出:None

# \B:检测非边界
print(re.match(r'ab\Bc', 'abc0'))       # 输出:<... span=(0, 3), match='abc'>
  • re.fullmatch(正则表达式, 字符串) 从字符串头部到末尾进行完全匹配,返回匹配成功的 re.Match 对象,否则返回 None
python
# 导入re模块
import re

# .:匹配一个任意的字符
print(re.fullmatch(r'.', 'a'))         # 输出:<...span=(0, 1), match='a'>
print(re.fullmatch(r'.', 'ab'))        # 输出:None
print(re.fullmatch(r'..', 'ab'))       # 输出:<...span=(0, 2), match='ab'>
print(re.fullmatch(r'ab', 'ab'))       # 输出:<...span=(0, 2), match='ab'>
print(re.fullmatch(r'a.', 'ab'))       # 输出:<...span=(0, 2), match='ab'>

# \w:匹配一个字符是字母、数字或者下划线
print(re.fullmatch(r'\w', '@'))        # 输出:None
print(re.fullmatch(r'\w', 'a'))        # 输出:<...span=(0, 1), match='a'>
print(re.fullmatch(r'\w', 'ab'))       # 输出:None
print(re.fullmatch(r'\w\w', 'ab'))     # 输出:<...span=(0, 2), match='ab'>
print(re.fullmatch(r'\w.\w', 'u(9'))   # 输出:<...span=(0, 3), match='u(9'>

# \W:匹配非字母、数字、下划线
print(re.fullmatch(r'\W\w', '&k'))     # 输出:<...span=(0, 2), match='&k'>

# \s:匹配一个空白字符(空格,换行,制表符)
print(re.fullmatch(r'a\sb', 'a b'))    # 输出:<...span=(0, 3), match='a b'>
print(re.fullmatch(r'a\sb', 'a\tb'))   # 输出:<...span=(0, 3), match='a\tb'>
print(re.fullmatch(r'a\sb', 'a\nb'))   # 输出:<...span=(0, 3), match='a\nb'>

# \S:匹配非空白字符
print(re.fullmatch(r'\S.', 'p8'))      # 输出:<...span=(0, 2), match='p8'>

# \d:匹配一个数字字符
print(re.fullmatch(r'\d\d\d', '123'))  # 输出:<...span=(0, 3), match='123'>
print(re.fullmatch(r'\d\d\d', '1b3'))  # 输出:None
print(re.fullmatch(r'0\d\d', '019'))   # 输出:<...span=(0, 3), match='019'>

# \D:匹配非数字字符
print(re.fullmatch(r'\D\w', 'ab'))     # 输出:<...span=(0, 2), match='ab'>

# \B:检测非边界
print(re.fullmatch(r'ab\Bc', 'abc'))   # 输出:<...span=(0, 3), match='abc'>

# ^:检测字符串开始
print(re.fullmatch(r'^The', 'The'))    # 输出:<...span=(0, 3), match='The'>

# []:匹配[]中出现的任意'一个字符'
print(re.fullmatch(r'abc[1a]', 'abca'))     # 输出:<...span=(0, 4), match='abca'>
print(re.fullmatch(r'abc[1a]', 'abc1'))     # 输出:<...span=(0, 4), match='abc1'>
print(re.fullmatch(r'abc[1a]', 'abc1a'))    # 输出:None
print(re.fullmatch(r'[\d_]abc', '1abc'))    # 输出:<...span=(0, 4), match='1abc'>
print(re.fullmatch(r'[\d_]abc', '_abc'))    # 输出:<...span=(0, 4), match='_abc'>
print(re.fullmatch(r'[\d_]abc', 'aabc'))    # 输出:None

# [^]:匹配不在[]出现的任意一个字符
print(re.fullmatch(r'abc[^\da]', 'abc1'))   # 输出:None
print(re.fullmatch(r'abc[^\da]', 'abca'))   # 输出:None
print(re.fullmatch(r'abc[^\da]', 'abcw'))   # 输出:<...span=(0, 4), match='abcw'>

# *:匹配0次或者多次
print(re.fullmatch(r'\d*abc', 'abc'))  	    # 输出:<... span=(0, 3), match='abc'>
print(re.fullmatch(r'\d*abc', '1abc'))      # 输出:<... span=(0, 4), match='1abc'>
print(re.fullmatch(r'\d*abc', '321abc'))    # 输出:<... span=(0, 6), match='321abc'>
print(re.fullmatch(r'\d*abc', '12a34abc'))  # 输出:None

# +:匹配一次或者多次
print(re.fullmatch(r'\d+\w*', '12as'))	    # 输出:<... span=(0, 4), match='12as'>

# ?:匹配0次或者1次
print(re.fullmatch(r'[a-z]?123', 'A123'))            # 输出:<... span=(0, 4), match='c123'>
print(re.fullmatch(r'[+]?[1-9][0-9]*', '+120'))      # 输出:<... span=(0, 4), match='+120'>

# {N}:匹配N次
print(re.fullmatch(r'[12ab]{3}abc', 'aaaabc'))       # 输出:<... span=(0, 6), match='aaaabc'>
print(re.fullmatch(r'[12ab]{3}abc', '21babc'))       # 输出:<... span=(0, 6), match='21babc'>

# |:分之
print(re.fullmatch(r'\d{3}|[a-z]{3}', 'wba'))        # 输出:<... span=(0, 3), match='wba'>

# ():整合
print(re.fullmatch(r'(abc){3}', ('abcabcabc')))      # 输出:<... span=(0, 9), match='abcabcabc'>
print(re.fullmatch(r'(\d\w[0-3]){2}', ('3a3')))      # 输出:None
print(re.fullmatch(r'(\d\w[0-3]){2}', ('3a37b1')))   # 输出:<...span=(0, 6), match='3a37b1'>

# \N:匹配前面第N个组中匹配到的内容
print(re.fullmatch(r'([1-9][a-z]{2})\1', '8hh8hh'))  # 输出:<...span=(0, 6), match='8hh8hh'>

# 在正则表达式中单纯表达 . \ [] {} () * + ? | $ ^ 这些有特殊意义字符,需要在前面加'\'
print(re.fullmatch(r'\d+\.\d', '1299'))  		     # 输出:None
print(re.fullmatch(r'\d+\.\d', '12.9'))  		     # 输出:<... span=(0, 4), match='12.9'>
  • re.search(正则表达式,字符串) 从字符串任意位置开始到任意位置结束进行部分匹配,返回第一个匹配成功re.Match 对象,否则返回 None
python
# 导入re模块
import re

print(re.search(r'\d{2,}[a-z]', 'sha23n--877m0899'))  # 输出:<...span=(3, 6), match='23n'>

以上方法除了都只返回一个 re.Match 对象结果以外,还有一个共同点就是,若没有匹配到符合正则的字符串时,返回 None ,若成功匹配到符合正则的字符串时,返回一个 re.Match 对象,因此我们还需要记住 re.Match 对象的使用方法。代码案例如下:

python
# 导入re模块
import re

# 使用正则提取字符串
res = re.match('\w\w\w', 'hks')
print(res)          # 输出:<...span=(0, 3), match='hks'>。注释:返回re.Match对象。
print(res.group())  # 输出:hks。注释:group方法获取匹配到的结果。
print(res.span())   # 输出:(0, 3)。注释:span方法获取匹配的范围。
print(res.start())  # 输出:0。注释:start方法获取匹配到的开始下标。
print(res.end())    # 输出:3。注释:end方法获取匹配到的结束下标。
print(res.string)   # 输出:hks。注释:string属性获取被匹配的原字符串。

# 通过(?P<名>正则)格式为匹配的每部分命名
res = re.match('(?P<one>\w)(?P<two>\w)(?P<three>\w)', 'hks')
print(res)                 # 输出:<...span=(0, 3), match='hks'>
print(res.group())         # 输出:hks
print(res.group('one'))    # 输出:h
print(res.group('two'))    # 输出:k
print(res.group('three'))  # 输出:s

警告

再提醒一下,如果正则表达式没有匹配到结果,则返回的结果是 None,就不能使用上面 re.Match 对象的方法,否则会报错。

多个匹配结果

  • re.finditer(正则表达式,字符串) 以迭代器形式返回字符串中满足正则表达式匹配的所有re.Match 对象,否则返回一个空的迭代器对象
python
# 导入re模块
import re

result = re.finditer(r'[a-zA-Z](\d+)', '12a123buy=236ok98s')  # 注释:返回一个迭代器赋值给result变量。
print([match for match in result])  # 输出:[<...span=(2, 6), match='a123'>, <...span=(14, 17), match='k98'>]

result = re.finditer(r'[a-zA-Z](\d+)11', '12a123buy=236ok98s')  # 注释:返回一个迭代器赋值给result变量。
print([match for match in result])  # 输出:[]
  • re.findall(正则表达式,字符串) 以列表形式返回字符串中满足正则表达式的所有子字符串,否则就返回空列表 []
python
# 导入re模块
import re

# 找到The
print(re.findall(r'The', 'abc hThekl'))       # 输出:['The']

# 以The开头
print(re.findall(r'^The', 'abc hThekl'))      # 输出:[]
print(re.findall(r'^The', 'The abc hThekl'))  # 输出:['The']

# $:检测字符串结束
print(re.findall(r'abc$', 'hah abc'))         # 输出:['abc']
print(re.findall(r'abc$', 'hah abc-'))        # 输出:[]
print(re.findall(r'abc$', 'hah abc   '))      # 输出:[]

# []:在指定范围内
print(re.findall('[\u4e00-\u9fa5]', "Hello 你好 World 世界"))  # 输出:['你', '好', '世', '界']。注释:提取所有中文字符。

# ():获取约束内容
print(re.findall(r'[a-z](\d+)', 'a123b=ok98s'))  # 输出:['123', '98']。注释:匹配符合r'[a-z](\d+)'的内容,返回()里面\d+匹配的内容。

建议

在学习正则的过程中可能会听到“零宽断言”这个词,**简单来说,就是匹配满足某个正则的一个位置,但这个位置不纳入匹配结果的,所以叫“零宽”,而且这个位置的前面或后面需要满足某种正则。**例如上面正则获取约束内容 r'a(b)' 只会返回 b 的值,但匹配的时候还是按 r'ab' 去匹配。

切割以及替换

  • re.split(正则表达式,字符串) 以列表形式返回字符串中以正则表达式匹配的字符串作为分隔符进行切割的子字符串。
python
# 导入re模块
import re

print(re.split(r'\d+', 'ahsj27jk1kaj8js00'))  # 输出:['ahsj', 'jk', 'kaj', 'js']
  • re.sub(替换字符串的正则,替换后的字符串,原字符串) 在原字符串中查找符合正则的子串,替换成新的字符串
python
# 导入re模块
import re

s1 = '你好sb,你全家都是sb,大,傻,叉!'
res = re.sub(r'sb||', '*', s1)     # 注释:将指定字符串中所有'sb'、'傻'、'叉'替换成 *。
print(res)                            # 输出:你好*,你全家都是*,大,*,*!

s2 = '1234**1264'
res = re.sub(r'12[36]4', '', s2)      # 注释:将1234或1264替换为空。
print(res)                            # 输出:**

s3 = '12534**12164'
res = re.sub(r'12(53|16)4', '', s3)   # 注释:将12534或12164替换为空。
print(res)                            # 输出:**

a1 = '1234'
b1 = re.sub(r'12(3|6)4', r'a\1', a1)  # 注释:如果r'a\1'前面没有'r',则'\1'表示b'\x01'。
print(b1)                             # 输出:a3。注释:\1代表第一个()匹配的结果是'3',结合前面的字符'a',b1得结果'a3'。

a2 = '1264'
b2 = re.sub(r'12(3|6)4', r'a\1', a2)  # 注释:如果r'a\1'前面没有'r',则'\1'表示b'\x01'。
print(b2)                             # 输出:a6。注释:\1代表第一个()匹配的结果是'6',结合前面的字符'a',b2得结果'a6'。

标志修饰符

上面学习的正则常用方法中都有一个 flags 参数,它代表了正则表达式的匹配标记,也叫标志修饰符,可以包含一些可选标志修饰符来控制匹配的模式,通过它可以来指定匹配时是否忽略大小写、是否进行多行匹配、是否显示调试信息等,多个标志可以通过按位 OR(|) 它们来指定,如 re.I | re.M 被设置成 IM 标志。具体参数如下:

修饰符描述
re.I使匹配对大小写不敏感。
re.L做本地化识别(locale-aware)匹配。
re.M多行匹配,影响 ^$
re.S. 匹配包括 \n 换行在内的所有字符。
re.U根据Unicode字符集解析字符,这个标志影响 \w, \W, \b, \B
re.X该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。
re.IGNORECASE忽略大小写。
  • re.S. 匹配包括 \n 换行在内的所有字符。我们平常提到的“换行”是以 \n 进行区分的,然而正则表达式中 . 的作用是匹配除 \n 以外的任何字符,也就是说它只会一行一行进行匹配,不会跨行匹配,添加 flags=re.S 参数后会将 \n 当做一个普通的字符加入到正则表达式中 . 匹配范围内,也就可以把字符串作为一个整体进行匹配。
python
# 导入re模块
import re

'''
换行\n不可见状态:
hello
world!
换行\n可以见状态:
hello\nworld!
以上两者是等价的。
'''
print(re.findall('hello.world', 'hello\nworld!'))        # 输出:[]
print(re.findall('hello.world', 'hello\nworld!', re.S))  # 输出:['hello\nworld']
  • re.I 不区分大小写。
python
# 导入re模块
import re

print(re.findall(r"ABC", "abc"))        # 输出:[]
print(re.findall(r"ABC", "abc", re.I))  # 输出:['abc']
  • re.M 将所有行的尾字母输出。
python
# 导入re模块
import re

print(re.findall(r'\A\d+', '12 34\n56 78\n90', re.M))  # 输出:['12']。注释:配位于字符串开头的数字。
print(re.findall(r'\d+\Z', '12 34\n56 78\n90', re.M))  # 输出:['90']。注释:匹配位于字符串尾的数字。
print(re.findall(r'^\d+', '12 34\n56 78\n90', re.M))   # 输出:['12', '56', '90']。注释:匹配位于行首的数字。
print(re.findall(r'\d+$', '12 34\n56 78\n90', re.M))   # 输出:['34', '78', '90']。注释:匹配位于行尾的数字。
  • re.IGNORECASE 忽略大小写。
python
# 导入re模块
import re

str1 = '你丫是傻逼吗? 我操你大爷的. Fuck you.'
result = re.sub('[操草艹]|fuck|[傻煞沙][比屄逼叉缺吊屌碉雕]', '*', str1, flags=re.IGNORECASE)
print(result)  # 输出:你丫是*吗? 我*你大爷的. * you.

建议

上面的一些方法中没有写 flags 参数,是因为按照位置参数进行传递的,第三个位置默认就是 flags 参数,而 re.sub 方法中第四个参数才是 flags 参数,所以使用了关键字传参,这点要注意。

预加载表达式

现在我们已经可以使用正则来提取字符串了,但有些时候我们会遇到一种情况,就是多次使用同一个正则表达式提取不同字符串中的内容,于是我们就可能这样写:

python
import re

list_str = ['我有100元', '他的身高是165cm', '她的体重有65Kg']
for item in list_str:
    # 使用同一个正则表达式
    res = re.findall(r'\d+', item)
    print(res, end=', ')  # 输出:['100'], ['165'], ['65'],

这样写运行是没有问题,但是程序在内存中会反复加载同一个正则表达式,如果正则表达式比较长,对内存就不太友好。更友好的做法是,通过 re.compile(正则表达式) 方法提前加载正则表达式生成一个正则对象,再通过正则对象调用上面我们学习的提取方法去提取字符串中的内容。整个流程正则表达式只会加载一次,具体代码如下:

python
import re

# 预加载正则
obj = re.compile(r'\d+')
list_str = ['我有100元', '他的身高是165cm', '她的体重有65Kg']
for item in list_str:
    # 使用正则对象调用提取方法
    res = obj.findall(item)
    print(res, end=', ')  # 输出:['100'], ['165'], ['65'],