19 Python 正则模块和正则表达式
什么是模块?
常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀。
但其实import加载的模块分为四个通用类别:
1 使用python编写的代码(.py文件)
2 已被编译为共享库或DLL的C或C++扩展
3 包好一组模块的包
4 使用C编写并链接到python解释器的内置模块
为何要使用模块?
如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。
随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用
如何使用模块?
模块可以包含可执行的语句和函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行(import语句是可以在程序中的任意位置使用的,且针对同一个模块很import多次,为了防止你重复导入,python的优化手段是:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载大内存中的模块对象增加了一次引用,不会重新执行模块内的语句)
每个模块都是一个独立的名称空间,定义在这个模块中的函数,把这个模块的名称空间当做全局名称空间,这样我们在编写自己的模块时,就不用担心我们定义在自己模块中全局变量会在被导入时,与使用者的全局变量冲突
正则表达式
正则表达式本身也和python没有什么关系,就是匹配字符串内容的一种规则。
官方定义:正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
在线测试工具 http://tool.chinaz.com/regex/
字符组:同一个位置上可以出现的字符的范围。
在同一个位置可能出现的各种字符组成了一个字符组,
在正则表达式中用[]表示 字符分为很多类,比如数字、字母、标点等等。
假如你现在要求一个位置"只能出现一个数字",那么这个位置上的字符只能是0、1、2...9这10个数之一。
正则 |
待匹配字符 |
匹配 |
说明 |
[0123456789] |
8 |
True |
在一个字符组里枚举合法的所有字符,字符组里的任意一个字符 |
[0123456789] |
a |
False |
由于字符组中没有"a"字符,所以不能匹配 |
[0-9] |
7 |
True |
也可以用-表示范围,[0-9]就和[0123456789]是一个意思 |
[a-z] |
s |
True |
同样的如果要匹配所有的小写字母,直接用[a-z]就可以表示 |
[A-Z] |
B |
True |
[A-Z]就表示所有的大写字母 |
[0-9a-fA-F] |
e |
True |
可以匹配数字,大小写形式的a~f,用来验证十六进制字符 |
字符:
模式字符串使用特殊的语法来表示一个正则表达式:
字母和数字表示他们自身。一个正则表达式模式中的字母和数字匹配同样的字符串。
多数字母和数字前加一个反斜杠时会拥有不同的含义。
标点符号只有被转义时才匹配自身,否则它们表示特殊的含义。
反斜杠本身需要使用反斜杠转义。
由于正则表达式通常都包含反斜杠,所以你最好使用原始字符串来表示它们。模式元素(如 r'\t',等价于 '\\t')匹配相应的特殊字符。
元字符 |
匹配内容 |
| . | 匹配除换行符以外的任意字符 |
| \w | 匹配字母或数字或下划线 |
| \s | 匹配任意的空白符 |
| \d | 匹配数字 |
| \n | 匹配一个换行符 |
| \t | 匹配一个制表符 |
| \b | 匹配一个单词的结尾 |
| ^ | 匹配字符串的开始 |
| $ | 匹配字符串的结尾 |
| \W |
匹配非字母或数字或下划线 |
| \D |
匹配非数字
|
| \S |
匹配非空白符
|
| a|b |
匹配字符a或字符b |
| () |
匹配括号内的表达式,也表示一个组 |
| [...] |
匹配字符组中的字符 |
| [^...] |
匹配除了字符组中字符的所有字符 |
量词:
量词 |
用法说明 |
| * | 重复零次或更多次 |
| + | 重复一次或更多次 |
| ? | 重复零次或一次 |
| {n} | 重复n次 |
| {n,} | 重复n次或更多次 |
| {n,m} | 重复n到m次 |
分组 ()与 或 |[^]
身份证号码是一个长度为15或18个字符的字符串,如果是15位则全部🈶️数字组成,首位不能为0;如果是18位,则前17位全部是数字,末位可能是数字或x,下面我们尝试用正则来表示:
| 正则 | 待匹配字符 | 匹配 结果 |
说明 |
| ^[1-9]\d{13,16}[0-9x]$ | 110101198001017032 |
110101198001017032 |
表示可以匹配一个正确的身份证号 |
| ^[1-9]\d{13,16}[0-9x]$ | 1101011980010170 |
1101011980010170 |
表示也可以匹配这串数字,但这并不是一个正确的身份证号码,它是一个16位的数字 |
| ^[1-9]\d{14}(\d{2}[0-9x])?$ | 1101011980010170 |
False |
现在不会匹配错误的身份证号了 |
| ^([1-9]\d{16}[0-9x]|[1-9]\d{14})$ | 110105199812067023 |
110105199812067023 |
表示先匹配[1-9]\d{16}[0-9x]如果没有匹配上就匹配[1-9]\d{14}
|
转义符 \
在正则表达式中,有很多有特殊意义的是元字符,比如\d和\s等,如果要在正则中匹配正常的"\d"而不是"数字"就需要对"\"进行转义,变成'\\'。
在python中,无论是正则表达式,还是待匹配的内容,都是以字符串的形式出现的,在字符串中\也有特殊的含义,本身还需要转义。所以如果匹配一次"\d",字符串中要写成'\\d',那么正则里就要写成"\\\\d",这样就太麻烦了。这个时候我们就用到了r'\d'这个概念,此时的正则是r'\\d'就可以了。
| 正则 | 待匹配字符 | 匹配 结果 |
说明 |
| \d | \d | False |
因为在正则表达式中\是有特殊意义的字符,所以要匹配\d本身,用表达式\d无法匹配 |
| \\d | \d | True |
转义\之后变成\\,即可匹配 |
| "\\\\d" | '\\d' | True |
如果在python中,字符串中的'\'也需要转义,所以每一个字符串'\'又需要转义一次 |
| r'\\d' | r'\d' | True |
在字符串之前加r,让整个字符串不转义 |
贪婪匹配
贪婪匹配:在满足匹配时,匹配尽可能长的字符串,默认情况下,采用贪婪匹配
| 正则 | 待匹配字符 | 匹配 结果 |
说明 |
| <.*> |
<script>...<script> |
<script>...<script> |
默认为贪婪匹配模式,会匹配尽量长的字符串 |
| <.*?> | r'\d' |
<script> |
加上?为将贪婪匹配模式转为非贪婪匹配模式,会匹配尽量短的字符串 |
几个常用的非贪婪匹配Pattern
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复
.*?的用法
. 是任意字符 * 是取 0 至 无限长度 ? 是非贪婪模式。
何在一起就是 取尽量少的任意字符,一般不会这么单独写,他大多用在: .*?x 就是取前面任意长度的字符,直到一个x出现
re模块的常用方法
正则表达式是对字符串的最简约的规则的表述。python也有专门的正则表达式模块re.
| 正则表达式函数 | 释义 |
| re.match() | 从头开始匹配,匹配失败返回None,匹配成功可通过group(0)返回匹配成功的字符串 |
| re.search() | 扫描整个字符串,并返回第一个匹配的字符串 |
| re.sub() | 对符合要求的所有子串进行替换 |
| re.findall() | 以列表形式返回所有符合条件的子串 |
| re.split() | 以模式作为切分符号切分字符串,并返回列表 |
| re.finditer() | 找到 RE 匹配的所有子串,并把它们作为一个迭代器返回 |
| re.compile() | 把那些经常使用的正则表达式编译成正则表达式对象 |
| re.group() | 返回被 RE 匹配的字符串 |
| re.start() | 返回匹配开始的位置 |
| re.end() | 返回匹配结束的位置 |
| re.span() | 返回一个元组包含匹配 (开始,结束) 的位置 |
1 import re 2 3 ret = re.findall('a', 'eva egon yuan') # 返回所有满足匹配条件的结果,放在列表里 4 print(ret) #结果 : ['a', 'a'] 5 6 ret = re.search('a', 'eva egon yuan').group() 7 print(ret) #结果 : 'a' 8 # 函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以 9 # 通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。 10 11 ret = re.match('a', 'abc').group() # 同search,不过尽在字符串开始处进行匹配 12 print(ret) 13 #结果 : 'a' 14 15 ret = re.split('[ab]', 'abcd') # 先按'a'分割得到''和'bcd',在对''和'bcd'分别按'b'分割 16 print(ret) # ['', '', 'cd'] 17 18 ret = re.sub('\d', 'H', 'eva3egon4yuan4', 1)#将数字替换成'H',参数1表示只替换1个 19 print(ret) #evaHegon4yuan4 20 21 ret = re.subn('\d', 'H', 'eva3egon4yuan4')#将数字替换成'H',返回元组(替换的结果,替换了多少次) 22 print(ret) 23 24 obj = re.compile('\d{3}') #将正则表达式编译成为一个 正则表达式对象,规则要匹配的是3个数字 25 ret = obj.search('abc123eeee') #正则表达式对象调用search,参数为待匹配的字符串 26 print(ret.group()) #结果 : 123 27 28 import re 29 ret = re.finditer('\d', 'ds3sy4784a') #finditer返回一个存放匹配结果的迭代器 30 print(ret) # <callable_iterator object at 0x10195f940> 31 print(next(ret).group()) #查看第一个结果 32 print(next(ret).group()) #查看第二个结果 33 print([i.group() for i in ret]) #查看剩余的左右结果
1 import re 2 3 ret = re.findall('www.(baidu|oldboy).com', 'www.oldboy.com') 4 print(ret) # ['oldboy'] 这是因为findall会优先把匹配结果组里内容返回,如果想要匹配结果,取消权限即可 5 6 ret = re.findall('www.(?:baidu|oldboy).com', 'www.oldboy.com') 7 print(ret) # ['www.oldboy.com']
1 ret=re.split("\d+","eva3egon4yuan") 2 print(ret) #结果 : ['eva', 'egon', 'yuan'] 3 4 ret=re.split("(\d+)","eva3egon4yuan") 5 print(ret) #结果 : ['eva', '3', 'egon', '4', 'yuan'] 6 7 #在匹配部分加上()之后所切出的结果是不同的, 8 #没有()的没有保留所匹配的项,但是有()的却能够保留了匹配的项, 9 #这个在某些需要保留匹配部分的使用过程是非常重要的。
正则表达式修饰符 - 可选标志
正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|) 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志:
| 修饰符 | 描述 |
|---|---|
| re.I | 使匹配对大小写不敏感 |
| re.L | 做本地化识别(locale-aware)匹配 |
| re.M | 多行匹配,影响 ^ 和 $ |
| re.S | 使 . 匹配包括换行在内的所有字符 |
| re.U | 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B. |
| re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
练习
1、匹配标签
1 import re 2 3 4 ret = re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>","<h1>hello</h1>") 5 #还可以在分组中利用?<name>的形式给分组起名字 6 #获取的匹配结果可以直接用group('名字')拿到对应的值 7 print(ret.group('tag_name')) #结果 :h1 8 print(ret.group()) #结果 :<h1>hello</h1> 9 10 ret = re.search(r"<(\w+)>\w+</\1>","<h1>hello</h1>") 11 #如果不给组起名字,也可以用\序号来找到对应的组,表示要找的内容和前面的组内容一致 12 #获取的匹配结果可以直接用group(序号)拿到对应的值 13 print(ret.group(1)) 14 print(ret.group()) #结果 :<h1>hello</h1>
2、匹配整数
1 import re 2 3 ret=re.findall(r"\d+","1-2*(60+(-40.35/5)-(-4*3))") 4 print(ret) #['1', '2', '60', '40', '35', '5', '4', '3'] 5 ret=re.findall(r"-?\d+\.\d*|(-?\d+)","1-2*(60+(-40.35/5)-(-4*3))") 6 print(ret) #['1', '-2', '60', '', '5', '-4', '3'] 7 ret.remove("") 8 print(ret) #['1', '-2', '60', '5', '-4', '3']
3、数字匹配
1 1、 匹配一段文本中的每行的邮箱 2 http://blog.csdn.net/make164492212/article/details/51656638 3 4 2、 匹配一段文本中的每行的时间字符串,比如:‘1990-07-12’; 5 6 分别取出1年的12个月(^(0?[1-9]|1[0-2])$)、 7 一个月的31天:^((0?[1-9])|((1|2)[0-9])|30|31)$ 8 9 3、 匹配qq号。(腾讯QQ号从10000开始) [1,9][0,9]{4,} 10 11 4、 匹配一个浮点数。 ^(-?\d+)(\.\d+)?$ 或者 -?\d+\.?\d* 12 13 5、 匹配汉字。 ^[\u4e00-\u9fa5]{0,}$ 14 15 6、 匹配出所有整数
4、爬虫练习
1 import requests 2 3 import re 4 import json 5 6 def getPage(url): 7 8 response=requests.get(url) 9 return response.text 10 11 def parsePage(s): 12 13 com=re.compile('<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>' 14 '.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>',re.S) 15 16 ret=com.finditer(s) 17 for i in ret: 18 yield { 19 "id":i.group("id"), 20 "title":i.group("title"), 21 "rating_num":i.group("rating_num"), 22 "comment_num":i.group("comment_num"), 23 } 24 25 def main(num): 26 27 url='https://movie.douban.com/top250?start=%s&filter='%num 28 response_html=getPage(url) 29 ret=parsePage(response_html) 30 print(ret) 31 f=open("move_info7","a",encoding="utf8") 32 33 for obj in ret: 34 print(obj) 35 data=json.dumps(obj,ensure_ascii=False) 36 f.write(data+"\n") 37 38 if __name__ == '__main__': 39 count=0 40 for i in range(10): 41 main(count) 42 count+=25
1 import re 2 import json 3 from urllib.request import urlopen 4 5 def getPage(url): 6 response = urlopen(url) 7 return response.read().decode('utf-8') 8 9 def parsePage(s): 10 com = re.compile( 11 '<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>' 12 '.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>', re.S) 13 14 ret = com.finditer(s) 15 for i in ret: 16 yield { 17 "id": i.group("id"), 18 "title": i.group("title"), 19 "rating_num": i.group("rating_num"), 20 "comment_num": i.group("comment_num"), 21 } 22 23 24 def main(num): 25 url = 'https://movie.douban.com/top250?start=%s&filter=' % num 26 response_html = getPage(url) 27 ret = parsePage(response_html) 28 print(ret) 29 f = open("move_info7", "a", encoding="utf8") 30 31 for obj in ret: 32 print(obj) 33 data = str(obj) 34 f.write(data + "\n") 35 36 count = 0 37 for i in range(10): 38 main(count) 39 count += 25
1 flags有很多可选值: 2 3 re.I(IGNORECASE)忽略大小写,括号内是完整的写法 4 re.M(MULTILINE)多行模式,改变^和$的行为 5 re.S(DOTALL)点可以匹配任意字符,包括换行符 6 re.L(LOCALE)做本地化识别的匹配,表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境,不推荐使用 7 re.U(UNICODE) 使用\w \W \s \S \d \D使用取决于unicode定义的字符属性。在python3中默认使用该flag 8 re.X(VERBOSE)冗长模式,该模式下pattern字符串可以是多行的,忽略空白字符,并可以添加注释
作业
实现能计算类似
1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )等类似公式的计算器程序
1 进行乘除法运算 2 # def mul_div(express): 3 # """ 4 # 进行乘除法运算 返回结果 5 # :param express: 字符串表达式 6 # :return: 返回运算结果 7 # """ 8 # if '*' in express: 9 # num1, num2 = express.split('*') 10 # return str(float(num1) * float(num2)) 11 # elif '/' in express: 12 # num1, num2 = express.split('/') 13 # return str(float(num1) / float(num2)) 14 # 15 # 16 # # 提取乘除法进行运算, 在进行加减运算 17 # def simplify_express(express): 18 # """ 19 # 提取乘除进行计算, 在进行加减法计算 20 # :param express: 字符串表达式 (str) 21 # :return: 运算后的结果 22 # """ 23 # while True: 24 # extract_compute = re.search(r'\d+\.?\d*[*/]-?\d+\.?\d*', express) 25 # if extract_compute: 26 # compute = extract_compute.group() 27 # result = mul_div(compute) 28 # express = express.replace(compute, result, 1) 29 # else: break 30 # express = express.replace('--', '+').replace('++', '+').replace('-+', '+').replace('+-', '-') 31 # express_lst = re.findall(r'[+\-]\d+\.?\d*', express) 32 # num = 0 33 # for ex in express_lst: 34 # num += float(ex) 35 # return str(num) 36 # 37 # 38 # # 提取括号里的表达式 39 # def calc(express): 40 # """ 41 # 提取括号里表达式 并进行计算 42 # :param express: 字符串表达式 (str) 43 # :return: 计算的结果 44 # """ 45 # express = re.sub(r'\s', '', express) 46 # while True: 47 # extract_bracket = re.search(r'\([^()]+\)', express) 48 # if extract_bracket: 49 # no_bracket = extract_bracket.group() 50 # result = simplify_express(no_bracket) 51 # express = express.replace(no_bracket, result) 52 # else: break 53 # return simplify_express(express) 54 55 56 args = '1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )' 57 58 print(calc(args))
第二种方法
1 def math(dic): 2 """ 3 计算乘除法 4 :param dic: 一个字典的对象 (dict) 5 :return: 计算后的结果 (str) 6 """ 7 x, y = float(dic['x']), float(dic['y']) 8 if dic['mark'] == '*': return str(x * y) 9 elif dic['mark'] == '/': return str(x / y) 10 11 12 def simplify_express(express): 13 """ 14 提取乘除进行计算, 在进行加减法计算 15 :param express: 字符串表达式 (str) 16 :return: 运算后的结果 17 """ 18 while True: 19 extract_compute = re.search(r'(?P<x>\d+\.?\d*)(?P<mark>[*/])(?P<y>-?\d+\.?\d*)', express) 20 if extract_compute: 21 compute = extract_compute.groupdict() 22 result = math(compute) 23 express = express.replace(extract_compute.group(), result, 1) 24 else: break 25 express = express.replace('--', '+').replace('++', '+').replace('-+', '-').replace('+-', '-') 26 express_lst = re.findall(r'[+\-]\d+\.?\d*', express) 27 num = 0 28 for ex in express_lst: 29 num += float(ex) 30 return str(num) 31 32 33 def calc(express): 34 """ 35 提取括号里表达式 并进行计算 36 :param express: 字符串表达式 (str) 37 :return: 计算的结果 38 """ 39 express = re.sub(r'\s', '', express) 40 while True: 41 extract_bracket = re.search(r'\([^()]+\)', express) 42 if extract_bracket: 43 no_bracket = extract_bracket.group() 44 result = simplify_express(no_bracket) 45 express = express.replace(no_bracket, result, 1) 46 else: break 47 return simplify_express(express) 48 49 50 args = '1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )' 51 52 print(calc(args))
浙公网安备 33010602011771号