import re #使用正则表达式来处理数据需要先调用re库(python默认安装)
var_str_list = ["+5.02 mm", "-5.02 mm", "1.2e6 MPa", "-1.e6 MPa", "100,000.01"]
print(var_str_list)
var_float_list = [float(re.sub("[^0-9-.eE]","",var_str))
for var_str in var_str_list ]
运行结果:
['+5.02 mm', '-5.02 mm', '1.2e6 MPa', '-1.e6 MPa', '100,000.01']
[5.02, -5.02, 1200000.0, -1000000.0, 100000.01] 从结果中我们可以看到,我们成功的取出了我们想要的数字部分 使用正则表达式的核心是利用正则表达式给出期望的数据形式,并完成对原始数据的查找或者替换,因此理解正则表达式的基本逻辑是重中之重,让我们先来分析上述例子中正则表达式的表达逻辑: 首先来分析re.sub的功能:
re.sub("[^0-9-.eE]","",var_str)
是将字符串中所有非数字组成的部分以空字符("")实现替换从而实现变相删除,最后就可以将纯数字部分直接转化为float数值型变量
我们再来看内部表达式的逻辑:
"[^0-9-.eE]"这就是典型的正则表达式: 首先[^]表示:除后续声明的字符类型,其他均被选择,也就是实现了反选功能(这里是反选非数字部分以便删除) 而 0-9-.eE部分则是我们要声明保留的数字部分字符, 具体看来0-9表示数字字符0到9均要保留,-是减号需要保留(是escape,转义用),而 . 是小数点需要保留,eE是常见科学计数法的符号,需要保留 这里有两个问题: (1)为什么减号 - 需要转义符,而小数点 . 不需要呢? 这里留一个悬念,后文揭晓 (2)感兴趣的童鞋可以试着用这里的表达式去处理这个字符串:"Velocity = 5.0m/s" 猜猜看,我们可以正确提取出来5.0的有效信息吗?如果不能是哪里出错了呢?所以这个正则表达式的使用限制条件是什么呢?希望你阅读完本文后可以回答这些问题 3. re库中的基本知识 (Python re库简介) 3.1.1 常见函数 (1) 实际应用中对string的处理包括两个主要操作:查找和替换(替换的本质是查找+直接修改),这里介绍4个常见function
| 函数 | 描述 | 操作实质 |
|---|---|---|
| findall | 返回包含所有匹配的字符段的list | 查找 |
| search | 返回包含匹配的字符段的 Match object(后文介绍对象构成) | 查找 |
| split | 将字符串在符合匹配表达式的位置处分割,以list形式返回 | 替换 |
| sub | 将字符串内匹配一处或者多处表达式的字段替换成自定义的字段 | 替换 |
re.findall("a","a , b , c")
re.search("a","a , b , c")
re.split(",","a , b , c")
re.sub(",","+","a , b , c")
输出结果如下所示:(一一对应)
['a']这几个函数具体的用法比较直观,不再赘述,这里重点说一下match object的有关信息, Match object 是记录本次search结果的有关信息的一个对象,它包含有以下常用方法(method)和特性(property): Methods: .span() 返回一个包含对应字符段在字符串中起止序号的若干元组(tuple) containing the start-, and end positions of the match. .group() 返回字符串中对应本次search的字符段 Property: .string 返回本次search中被用于输入的字符串 下面我们以一段代码为例展示match object的有关使用:['a ', ' b ', ' c'] #格外注意这里的空格,实际中这里是容易出问题的地方 'a + b + c'
thisMatch = re.search("xyz","xyz , abc , xyz")
print(thisMatch.span())
print(thisMatch.string)
print(thisMatch.group())
输出结果:
(0, 3) xyz , abc , xyz xyzMatch object比此处的介绍会稍微复杂一些,尤其3.7版本后还添加了一些新的功能,具体细节还请移步python documentation 3.1.2 常见函数 (2) 与前述函数功能类似,但是在实际使用中略有不同的还有
| 函数 | 描述 | 操作实质 |
|---|---|---|
| finditer | 与findall返回匹配的字符段的list不同,这里返回的是可调用的iterator | 查找 |
| fullmatch | 返回完全符合匹配的字符段的 Match object,注意这里是完全符合 | 查找 |
| subn | 返回一个tuple,包含替换后的新字符串,同时给出替换发生的次数 | 替换 |
x=re.finditer("[a-zA-Z]","a, A")
for s in x:
print(s.span())
输出结果:
((0, 1) (3, 4)fullmatch
print(re.fullmatch("^hel{2}o world$","hello world"))
print(re.fullmatch("^hel{2}o world$","hello world!"))
输出结果:
subnNone
re.subn("d","d","1 2 3")
输出结果:
('d d d', 3)
3.1.3 一些可能有用的函数
re.escape(pattern)
用于转义 pattern 中的特殊字符。主要用于自我测试场景,如果目标文本可能包含正则表达式的元字符,那么想要确认转义的正确格式时,它会比较有用的,举例:
print(re.escape('https://www.xyz.org'))
输出为:
https://www.xyz.org至于其他函数本文就不再分析了,详情请移步python文档 3.2 元字符(metacharacters)
| 元字符 | 描述 | 范例 | 范例解释 |
|---|---|---|---|
| [] | 一类字符的集合 | "[a-m]" | 目标字符为a-m中其中一个字母(包括a,m), 类似还可以写出"[3-9]",目标字符为3-9之间一个数字(包括3,9) |
| 表示开始一个特殊字符段或者用于转义 | "d" | d表示返回字符串中含有表示数字的字符, 这类特殊标记后文会详细介绍 | |
| . | 可以用于表示任意字符,换行(new line)符号例外 | "he..o" | 任何以he开始后面跟两个字符再加上o的组合均满足需求, 比如“hello”, “hexxo”等等 |
| ^ | 目标字符以此开始 | "^hello" | 任何以hello开始的字符串: re.findall("^hello", "hello world")会返回hello, 而re.findall("^hello", "world hello")会返回空 |
| $ | 目标字符以此结束 | "world$" |
任何以world结束的字符段:
re.findall("world$", "hello world")会返回world
,而re.findall("world$", "world hello")会返回空 |
| * | 之前的一个字段出现零次或者更多次 | "he.*o" | 任意字符(.)不出现或者重复出现很多次,“hello”,“helo”, “heo” 均符合要求。注意这里的小陷阱,“heldaffaxo”也是符合要求的, 因为是任意字符出现多次,并非指定字符出现零次或者多次, 换句话说,这里任何以he开口,o结尾的字符串均符合要求, 这在实际使用中及其常用 |
| + | 之前的一个字段出现一次或者更多次 | "he.+o" | 任意字符(.)至少出现一次,“hello”,“helo”均符合要求, “heo”不符合要求。如前分析,“heldaffaxo”也是符合要求的 |
| ? | 之前的一个字段出现零次或者一次 | "he.?o" | 任意字符(.)至多出现一次, “heo”,“helo”均符合要求, “hello”不符合要求。在这里“heldaffaxo”就不再符合要求了 |
| {m,n} {n} | 之前字段重复出现m-n次(包括m,n) 之前字段重复出现n次 | "he.{2}o" | 任意字符(.)必须出现两次,“hello”符合要求, “helo”,“heo”不符合 要求。 与"he.*o"相比,其实实际上"he.{2}o"是在限制开头和结尾的 基础上加上了字符长度限制的硬性规定,这个实际中也较常用到, 比如检索学校数据库中以“A”开头,以“2021”结尾的所有学号 |
| | | 或者 | "hello|world" | 不言自明 |
| () | 群组(group) | ^(world|hello) | re.sub("(world|hello)","XY", "world world hello")返回'XY XY XY', ()内的字符串会作为一个整体参与后续运算, 换句话说()加上()内的东西在运算中等价于一个字符 |
| *?, +?, ?? | 关闭*,+, ?的贪婪(greedy)模式 | <.*?> | 在*,+, ?后面在加上一个?可以关闭默认的贪婪模式, re.findall('<.*>',' b 而re.findall('<.*?>',' b |
在正则表达式中以''开头具有特殊含义的字符组合
| 字符 | 具体描述 | 示例 | 示例解释 |
|---|---|---|---|
| A | 若本字符后的字符段是对应字符串的开头则为对应match | "Ahel" | re.findall("Ahel","hello")返回['hello'], 而re.findall("Ahel","xhello")返回为空 |
| b | 若本字符后的字符段是对应字符串的开头或者结尾则为对应match (此处r是确保字符串被当作raw string处理,缺失则无法正常识别) | r"llob" | re.findall(r"llob","hello")返回['hello'],而 re.findall(r"llob","hellox")则返回为空 |
| B | 若本字符后的字符段出现在字符串中,但是不在开头或者不在结尾则为对应match (此处r是确保字符串被当作raw string处理,缺失则无法正常识别) | r"Blo"(不在开头) r"loB"(不在结尾) | re.findall(r"Blo","hello")返回['lo']而 re.findall(r"Blo","lohel")则返回为空 |
| Z | 若本字符后的字符段是对应字符串的结尾则为对应match | r"lloZ" | re.findall("loZ","hello")返回['lo'] |
| d | 返回字符串中字符为数字的部分 | "d" | re.findall(r"d","hi 2.2")返回['2', '2'] |
| D | 返回字符串中字符不是数字的部分 | "D" | re.findall(r"D","hi 2.2")返回['h', 'i', ' ', '.'] |
| s | 返回字符串中包含空格符的部分 | "s" | re.findall("s","hello world")返回[' '] |
| S | 返回字符串中不包含空格符的部分 | "S" | re.findall("S","hello world")返回 ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'] |
| w | 返回字符串中包含word的部分 (a-Z, 0-9以及下划线_) | "w" | re.findall("w","hi_you % /1.0")返回 ['h', 'i', '_', 'y', 'o', 'u', '1', '0'] |
| W | 返回字符串中不包含word的部分 (除a-Z, 0-9以及下划线_以外的字符) | "W" | re.findall("W","hi_you % /1.0")返回 [' ', '%', ' ', '/', '.'] |
| 集合(set) | 描述 | 示例解释 |
|---|---|---|
| [arn] | 找到字符段中存在 a, r, 或者 n 的一个字符 | |
| [a-n] | 找到字符段中存在 a-n 之间的一个字符 | |
| [^arn] | 找到字符段中除a, r, 或者 n 以外的一个其他字符,[^]操作非常实用 | |
| [012] | 找到字符段中存在 0, 1, 或者 2 的一个数字字符 | |
| [0-9] | 找到字符段中存在 0-9 之间的一个数字字符 | |
| [0-1][0-9] | 找到字符段中两位的数字,第一位在0-1之间,第二位在0-9之间(实际就是00-19) | re.findall("[0-1][0-9]","12")返回['12'] re.findall("[0-1][0-9]","42")返回空 |
| [a-zA-Z] | 找到字符段中存在 a-z, A-Z 之间的一个字符 | re.findall("[a-zA-Z]","aA")返回['a', 'A'] re.findall("[a-zA-Z]","42")返回空 |
| [+] | 在集合(set)的符号([ ])中间,+, *, ., |, (), $,{} 都是没有特殊含义的 |
import re
txt = "+-rain 1.0-"
x = re.findall("[+-0-9]", txt)
print(x)
运行结果不包含1,但多出来一个.
['+', '-', '.', '0', '-']而加上转义字符之后:
txt = "+-rain 1.0-"
x = re.findall("[+-0-9]", txt)
print(x)
运行结果包含 1, 没有不需要的其他字符:
['+', '-', '1', '0', '-']4. 常用的模块常量:
| 模块常量 | 调用方法 | 解释 |
|---|---|---|
| IGNORECASE | re.IGNORECASE 或简写为 re.I | 匹配中忽略大小写 |
| ASCII | re.ASCII 或简写为 re.A | 保证 w, W, b, B, d, D, s 和 S 只匹配ASCII, 而不是Unicode |
| DOTALL | re.DOTALL 或简写为 re.S | 匹配所有字符,包括换行符n。注意默认模式下 . 是不能匹配行符n的 |
| MULTILINE | re.MULTILINE 或简写为 re.M | 多行模式,当某字符串中有换行符n, 默认模式下是不支持换行符特性的,比如:行开头 和 行结尾,而多行模式下是支持匹配行开头的 默认模式下它只能匹配字符串的开头; 而在多行模式下,它还可以匹配 换行符n后面的字符。 单行模式下它两效果一致,多行模式下A不能识别n |
| VERBOSE | re.VERBOSE 或简写为 re.X | 详细模式,可以在正则表达式中加注释, 可以说非常非常有用! |
a = re.compile(r"""d + # the integral part . # the decimal point d * # some fractional digits""", re.X) b = re.compile(r"d+.d*")这里a和b的效果是等价的,都是用于匹配一个带小数点的浮点数 还记得前文提到的空格问题么,在VERBOSE模式下,所有的空格都将被忽略。 5. 实例:如何识别输入文本为有效邮箱地址 最后的最后,让我们应用学习到的知识一起来构建一个识别邮箱地址的正则表达式 (声明:此处表达式是作者自己构建并没有被大量输入验证过,仅作为简单范例,有不周之处还请读者多多批评)
emails = ["Aacb111.111@qq.com", "11D1-1d11@163.com", "111_1d1d1@py.p-y.py.com","d?@sina.com","12d", ".111111@qq.com"] regexPattern = re.compile(r"""^[0-9a-zA-z][0-9a-zA-z_.-]+ # 邮箱名称可以包含数字,字母和下划线_横线-和点.,且必须以字母或数字开始 @ # 标准邮箱符号 (([0-9a-zA-z-]+).)+ # 子域名1 [0-9a-zA-z-]+$ # 高级域名""", re.X) for email in emails: print(re.fullmatch(regexPattern, email))它的输出结果是:
参考资料 [1] Python RegEx (w3schools.com) [2] python documentation re — Regular expression operations — Python 3.10.0 documentation [3] python robustly convert any string with units to float - Stack Overflow [4] Python正则表达式,这一篇就够了! - SegmentFault 思否None None None



