Python re模块 (正则表达式用法剖析详解)

正则表达式

字符组
字符组 : [字符组]
在同一个位置可能出现的各种字符组成了一个字符组,在正则表达式中用“ [ ] ”表示
字符分为很多类,比如数字、字母、标点等等。
假如要求一个位置"只能出现一个数字",那么这个位置上的字符只能是0、1、2...9这10个数之一

在这里插入图片描述


字符(元字符)

在这里插入图片描述

\w 包括:字母、数字、下划线;如果验证要求只允许输入:“数字和字母” 不能单独 \w 解决,
还要考虑到下划线,所以只能写成[0-9a-zA-Z] 
import re

strd = input("please:")
if re.search("^[0-9a-zA-Z]+$", strd):

    print("输入正确")
else:
    print("错误")

在这里插入图片描述

# [/s/S] 、[/d/D]、[/w/W] 这类的可以匹配任意字符
量词(个数)

在这里插入图片描述
在这里插入图片描述

边界字符(也属于元字符)

在这里插入图片描述

 有一些有特殊意义的元字符进入字符组中会回复它本来的意义如: . | [ ] ( )
^ 匹配字符串的开始 如: ^x ,表示字符串必须以x开头, 注意:[^ ] 意义不同
$ 匹配字符串的结束 如: x$ , 表示字符串必须以x结尾

^,$ : 如果字符有多行, 且在 re.M 模式下, ^,$ 会将每一行当作一个字符串,
	即换行后会重新匹配

\A 匹配字符串的开始,^ 类似,: \Ax ,表示字符串必须以x开头
\Z 匹配字符串的结尾, 与$类似,: x\Z,表示字符串必须以x结尾
# \A ,\Z , 在re.M模式下不会影响多行匹配

\b 用来匹配单词的结尾,: er\b 表示单词是否以 er结束
\B 用来匹配非单词的结尾,: er\B 表示单词中含有er,单独的以er结尾的词不行
# 注意:\b 需要转意 即写成 \\b

import re

res=re.findall("海.","海燕海桥海石")   # ['海燕', '海桥', '海石']
res=re.findall("^海.","海燕海桥海石")  # 海燕
res=re.findall("海.$","海燕海桥海石")  # 海石
res=re.findall("^海.$","海燕海桥海石") # []
res=re.findall("^海.$","海燕") # ['海燕']  这就能明白为什么 . 也属于边界字符了

print(res)

分组()与或 |[^]
身份证号码是一个长度为15或18个字符的字符串,如果是15位则全部由数字组成,首位不能为0;
	如果是18位,则前17位全部是数字,末位可能是数字或x,下面我们尝试用正则来表示:

在这里插入图片描述

# 匹配任意一个邮箱 min@163.com
# x|y 表示匹配 x 或者 y
# (xyz) 加上括号表示将 xyz 看成一个整体
mailPattern = "\w+@\w+\.\w+"
# mailPattern = "(\w+@\w+\.((com)|(cn)))" # 在re.match / re.search 下能找出
# 匹配日期 1800-01-01 --- 2020-12-31
# 1899 1999 -- 2020
# 0-[1-9] 1[0-1-2]
# 0-[1-9] 1 2 - 0-9 3 0,1


result=re.search("^((1[89]\d{2})|(20[01]\d)|(2020))-((0[1-9])|(1[012]))-((0[1-9])|([12]\d)|(3[01]))$","2020-12-31")
# 注意括号分组,或 | 的时候,每一种可能给一个括号,
# 判断的每一项(年、月、日)再给一个括号
print(result)


import re

strd = input("请按格式输入出生年月日:")
if re.search("((1[89]\d{2})|(20[01]\d)|(2020))-((0[1-9])|(1[012]))-((0[1-9])|([12]\d)|(3[01]))", strd):
    print("输入正确")
else:
    print("输入格式有误,或者数值有误")

转义字符

在这里插入图片描述

贪婪模式和非贪婪模式

贪婪匹配:在满足匹配时,匹配尽可能长的字符串,默认情况下,采用贪婪匹配

import re

res = re.findall("a?", "min is a good man")  # 非贪婪模式
# 结果:['', '', '', '', '', '', '', 'a', '', '', '', '', '', '', '', 'a', '', '']
res = re.findall("a?", "min is a good maan")  # 非贪婪模式
# 结果:['', '', '', '', '', '', '', 'a', '', '', '', '', '', '', '', 'a', 'a', '', '']
res = re.findall("a*", "min is a good maan")  # 贪婪模式
# 结果:['', '', '', '', '', '', '', 'a', '', '', '', '', '', '', '', 'aa', '', '']
res = re.findall("a+", "min is a good maan")  # 贪婪模式
# 结果:['a','aa']
res = re.findall("a{3}", "min is a good maaan")  # 贪婪模式
# 结果:['aaa']
res = re.findall("a{3}", "min is a good maaaan")  # 贪婪模式
# 结果:['aaa']
res = re.findall("a{3,5}", "min is a good maaaaan")  # 贪婪模式
# 结果:['aaaaa'] 包后
res = re.findall("a{3,}", "min is a good maaaaaaan")  # 贪婪模式
# 结果:['aaaaaaa']
res = re.findall("a{,5}", "min is a good maaaaaaan")  # 贪婪模式
# 结果:['', '', '', '', '', '', '', 'a', '', '', '', '', '', '', '', 'aaaaa', 'aa', '', '']
print(res)

单独出现 ? 非贪婪模式
出现 的第二个范围量词是“ ?” 就会变成 非贪婪模式

常用的非贪婪匹配Pattern

*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

. * ? 的用法

. 是任意字符
* 是取 0 至 无限长度
? 是非贪婪模式。
合在一起就是:取尽量少的任意字符,一般不会这么单独写,他大多用在:
.*?x

就是取前面任意长度的字符,直到一个 x 出现

re模块下的常用方法

三种模式:
re.match() 是从源字符串的 起始位置开始查找一个匹配项
re.search() 是从源字符串中找到一个匹配项
re.findall() 是从源字符串中找到所有的匹配项

flag:真正的含义
	re.I 	使匹配对大小写不敏感
 	re.M 	多行匹配,是否影响 ^ 和 $  
	re.S 使 . 匹配包含换行符在内的任意字符
# re.M 实例
import re

res = re.findall("^min","min is a good man\nmin is a good man") # ['min']
res = re.findall("^min", "min is a good man\nmin is a good man", re.M)  # ['min', 'min']

# 如果后面,没有加上re.M 表示看做一行字符串(不会进行换行),只能找到前面的一个“min”,加了,加了能找到两个

res = re.findall("\Amin","min is a good man\nmin is a good man")  # ['min']
res = re.findall("\Amin","min is a good man\nmin is a good man",re.M) # ['min', 'min']


# print(res)
re.match()
re.match(pattern,string,flag) # 是从源字符串的 起始位置开始查找一个匹配项

     pattern 要进行匹配的正则表达式
     string 表示的是源字符串
     flag 标记, 可以不写
         re.I 使匹配对大小写不敏感
         re.M 多行匹配,是否影响 ^ 和 $
         re.S 使 . 匹配包含换行符在内的任意字符

# 如果匹配成功会返回一个对象
# 如果匹配失败 会 返回None
# 可以根据 结构是否为 None 来判断是否匹配成功
# 可以通过这个变量的group方法来获取结果;
# 如果没有匹配到,会返回None,此时如果使用group会报错
res = re.match("www","www.baidu.com")
res = re.match("www","aww.baidu.com")
res = re.match("www","awww.baidu.com")
res = re.match("www","wwbaidu.www,com")
res = re.match("www","wwwwbaidu.www,com")
res = re.match("www","WWW.baidu.com")
res = re.match("www","WWW.baidu.com",re.I)
print(res)
print(type(res))


>>> 匹配手机号码
import re
phone_number = input('please input your phone number : ')
if re.match('^(13|14|15|18)[0-9]{9}$',phone_number): # 如果成功则不是None
        print('是合法的手机号码')
else:
        print('不是合法的手机号码')
        
re.search()
re.search(pattern,string,flag) # 是从源字符串中(从左往右)找到第一个匹配项

     pattern 要进行匹配的正则表达式
     string 表示的是源字符串
     flag 标记, 可以不写
         re.I 使匹配对大小写不敏感
         re.M 多行匹配,是否影响 ^ 和 $
         re.S 使 . 匹配包含换行符在内的任意字符
         
     # 多用于表单验证    
res = re.search("www","www.baidu.com")
res = re.search("www","ww.baiduwww.com")
res = re.search("www","www.baiduwww.com")
print(res)

print(res.group)
# 只匹配从左到右的第一个,得到的不是直接的结果,而是一个变量,
# --需要通过这个变量的group方法来获取结果;
# 如果没有匹配到,会返回None,此时如果使用group会报错
re.findall()
re.findall(pattern,string,flag) # 是从源字符串中找到所有的匹配项

     pattern 要进行匹配的正则表达式
     string 表示的是源字符串
     flag 标记, 可以不写
         re.I 使匹配对大小写不敏感
         re.M 多行匹配,是否影响 ^ 和 $
         re.S 使 . 匹配包含换行符在内的任意字符
# findall的结果是列表的形式,会将找到的多个结果放到列表中去
# 注意: 如果找不到,会返回一个空列表
# 注意:不能直接使用 group 方法
res = re.findall("www","www.baidu.com")
res = re.findall("wwwa","www.baiduwww.com") # 结果:[]
print(res)
print(type(res))

findall 的优先级查询:

import re

ret = re.findall('www.(baidu|oldboy).com', 'www.oldboy.com')
print(ret)  # ['oldboy'] 这是因为findall会优先把匹配结果组里内容返回

# 如果想要匹配结果,取消权限即可,格式:在分组内加上 ?:即 (?:正则表达式)

ret = re.findall('www.(?:baidu|oldboy).com', 'www.oldboy.com')
print(ret)  # ['www.oldboy.com']
>>> 三种模式练习
import re

ret = re.findall('a', 'eva egon yuan') # 返回所有满足匹配条件的结果,放在列表里
print(ret) # 结果 : ['a', 'a']

ret = re.search('a', 'eva egon yuan').group()
print(ret) # 结果 : 'a'
# 函数会在字符串内 查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,
# 该对象可以通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。

ret = re.match('a', 'abc').group() # 同search,不过在字符串开始处进行匹配
print(ret)
#结果 : 'a'
re.split()
# re.split()
# 参数:
# pattern,正则表达式, 即以某个正则来拆分
# string, 被拆分的源字符串
# maxsplit=0, 最大拆分次数
# flags=0 标识


# 正则表达式来拆分字符串
strData ="wen1 is2 a3 old4 man5"
lis_re = re.split(" ",strData)
# list_re=re.split("\d",strData)
# list_re=re.split("[0-9]",strData)
# list_re=re.split(" +",strData) #注意,+前面有个空格
# print(listRes) 结果:['wen1', 'is2', 'a3', 'old4', 'man5']
# 结果:['wen', ' is', ' a', ' old', ' man', '']
# 如果想保留用来作切割标准的字符串:只需给它添加分组即可
ret = re.split('\d+','alex83taibai40egon25')
print(ret) # ['alex', 'taibai', 'egon', ''] # 最后一次切割 留下末尾一个空
ret = re.split('(\d+)','alex83taibai40egon25aa')
print(ret) # ['alex', '83', 'taibai', '40', 'egon', '25', 'aa']
# split的优先级查询
ret=re.split("\d+","eva3egon4yuan")
print(ret) #结果 : ['eva', 'egon', 'yuan']

ret=re.split("(\d+)","eva3egon4yuan")
print(ret) # 结果 : ['eva', '3', 'egon', '4', 'yuan']

# 在匹配部分加上()之后所切出的结果是不同的,
# 没有()的没有保留所匹配的项,但是有()的却能够保留了匹配的项,
# 这个在某些需要保留匹配部分的使用过程是非常重要的。
re.sub()
str= "蔡徐篮球 是 xxx\n蔡徐坤歌手 是 yyy\n 肖战 是 帅哥"

# re.sub()
# 参数 
	pattern
	repl, 用来替换的新字符串
	string
	count=0, 替换的次数,默认是全部替换
	flags=0  # 不能修改,默认可以不写
	# 默认是 re.M 模式,会换行匹配所有

res = re.sub("蔡徐.{,2}","肖战",str)
'''
肖战 是 xxx
肖战手 是 yyy
 肖战 是 帅哥
'''

# subn与sub功能一致, 但是 sub是返回替 换后的新字符串,

# subn返回的是元组,元组有2个元素, 元素1代表替换后的新字符串, 元素2代表的替换的次数
res = re.subn("蔡徐.{,2}","肖战",str)
# ('肖战 是 xxx\n肖战手 是 yyy\n 肖战 是 帅哥', 2) 
    
print(res)

提取与分组
正则表达式不仅仅有强大的匹配功能,还有强大提取功能
(xyz) 将xyz看作一个整体
(xyz) 将xyz看成一个小组

re.search() 模式下的分组

import re

s = '<a>wahaha</a>'  # 标签语言 html 网页
ret = re.search('<(\w+)>(\w+)</(\w+)>',s)
print(ret.group(0))  # 所有的结果
print(ret.group(1)) # 数字参数代表的是取对应分组中的内容
print(ret.group(2))
print(ret.group(3))

re.findall() 模式的分组

# 为了findall也可以顺利取到分组中的内容,有一个特殊的语法,就是优先显示分组中的内容
ret = re.findall('(\w+)',s)
print(ret) #['a', 'wahaha', 'a']
ret = re.findall('>(\w+)<',s)
print(ret) #['wahaha']

strData = "010-34545546"
numberPattern = "(\d{3})-(\d{8})" 结果:[('010', '34545546')]
numberPattern = "((\d{3})-(\d{8}))" 结果:[('010-34545546', '010', '34545546')]
# findall会将所有的组作为一个元组的元素,放在列表中,组是靠括号来分的
res = re.findall(numberPattern,strData)
print(res) # print(res[0]) 结果:('010', '34545546')

re.findall模式下,取消分组优先

ret = re.findall('\d+(\.\d+)?','1.234*4.3')
print(ret) # 结果是:['.234', '.3']
# 为什么只显示了 小数位,因为findall优先显示分组中的内容

# 取消分组优先做法:在分组内加上?:(?:正则表达式)
ret = re.findall('\d+(?:\.\d+)?','1.234*4.3')
print(ret)


str_date = "010-34545546"
numberPattern = "\d{3}-\d{8}"
numberPattern = "(\d{3})-(\d{8})"
numberPattern = "((\d{3})-(\d{8}))"
numberPattern = "(?P<First>\d{3})-(?P<Last>\d{8})"

res = re.match(numberPattern,str_date)
print(res)

# group可以提取完整匹配的字符
# () 可以将字符串分成多个组, 分组顺序是由外到里, 由前到后
print(res.group()) # 默认是0
print(res.group(1)) # 第一组
print(res.group(2)) # 第二组
#print(res.group(3)) # 第三组

>>>可以给每一个分组取一个别名, 格式:(?P<别名>正则)
# 获取的时候可以直接根据别名来获取
print(res.group("First"))
print(res.group("Last"))

# groups 将() 包裹的内容进行分组提取, 将所有的分组作为元组的元素,并作为结果返回
     # 它只看括号
res=re.match("\d{3}-\d{8}",strDate) # 这里没括号,输出结果为:()空元祖
print(res.groups())

分组命名的用法

s = '<a>wahaha</b>' # 加入div标签不一致
pattern = '<(\w+)>(\w+)</(\w+)>'
ret = re.search(pattern,s)
# print(ret.group(1) == ret.group(3)) False

# 使用前面的分组 要求使用这个名字的分组和前面同名分组中的内容匹配的必须一致
pattern = '<(?P<tab>\w+)>(\w+)</(?P=tab)>'
ret = re.search(pattern,s)
print(ret) #False

>>>练习
# 2018-12-06  这种时间格式,中间的分隔符必须一致,所以可以用到命名分组
# 2018.12.6
# 2018 12 06
# 12:30:30

总结

import re

ret = re.findall('a', 'eva egon yuan')  # 返回所有满足匹配条件的结果,放在列表里
print(ret) # 结果 : ['a', 'a']

ret = re.search('a', 'eva egon yuan').group()
print(ret) # 结果 : 'a'
# 函数会在字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以
# 通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。

ret = re.match('a', 'abc').group()  # 同search,不过尽在字符串开始处进行匹配
print(ret)
# 结果 : 'a'

ret = re.split('[ab]', 'abcd')  # 先按'a'分割得到''和'bcd',在对''和'bcd'分别按'b'分割
print(ret)  # ['', '', 'cd']

ret = re.sub('\d', 'H', 'eva3egon4yuan4', 1) # 将数字替换成'H',参数1表示只替换1个
print(ret) # evaHegon4yuan4

ret = re.subn('\d', 'H', 'eva3egon4yuan4') # 将数字替换成'H',返回元组(替换的结果,替换了多少次)
print(ret)

obj = re.compile('\d{3}')  # 将正则表达式编译成为一个 正则表达式对象,规则要匹配的是3个数字
ret = obj.search('abc123eeee') # 正则表达式对象调用search,参数为待匹配的字符串
print(ret.group())  # 结果 : 123

import re
ret = re.finditer('\d', 'ds3sy4784a')   # finditer返回一个存放匹配结果的迭代器
print(ret)  # <callable_iterator object at 0x10195f940>
print(next(ret).group())  # 查看第一个结果
print(next(ret).group())  # 查看第二个结果
print([i.group() for i in ret])  # 查看剩余的左右结果

'''
先编译后使用

re模块的进阶 : 时间/空间
compile 节省你使用正则表达式解决问题的时间
编译 正则表达式 编译成 字节码
在多次使用的过程中 不会多次编译
ret = re.compile('\d+')   # 已经完成编译了
print(ret)

res = ret.findall('alex83taibai40egon25')
print(res) # 类型是“list”
res = ret.search('sjkhk172按实际花费928')
print(res.group())



finditer 节省你使用正则表达式解决问题的空间/内存
ret = re.finditer('\d+','alex83taibai40egon25')
#print(ret)这是一个迭代器
for i in ret:
     print(i.group())


findall 返回列表 找所有的匹配项
search  匹配就 返回一个变量,通过group取匹配到的第一个值,不匹配就返回None,group会报错
match   相当于search的正则表达式中加了一个'^'

spilt   返回列表,按照正则规则切割,默认匹配到的内容会被切掉
sub/subn 替换,按照正则规则去寻找要被替换掉的内容,subn返回元组,第二个值是替换的次数

compile  编译一个正则表达式,用这个结果去search match findall finditer 能够节省时间
finditer 返回一个迭代器,所有的结果都在这个迭代器中,需要通过循环+group的形式取值 能够节省内存
'''

应用

匹配标签
import re


ret = re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>","<h1>hello</h1>")
# 还可以在分组中利用?<name>的形式给分组起名字
# 获取的匹配结果可以直接用group('名字')拿到对应的值
print(ret.group('tag_name'))  # 结果 :h1
print(ret.group())  # 结果 :<h1>hello</h1>

ret = re.search(r"<(\w+)>\w+</\1>","<h1>hello</h1>")
# 如果不给组起名字,也可以用\序号来找到对应的组,表示要找的内容和前面的组内容一致
# 获取的匹配结果可以直接用group(序号)拿到对应的值
print(ret.group(1))
print(ret.group())  # 结果 :<h1>hello</h1>
# 正则匹配所有所有tag
<[^>]+> # [^>]+ 指除了“>”外,但是至少有一个字符

# 匹配 open tag 
<[^/][^>]*> 
# 匹配 close tag
</[^>]+>

# 匹配 单标签
<[^>]+/>

在这里插入图片描述

import re
s="<img src='http://somehost/picture'/>"
ret=re.search("<[^>]+>",s).group()
print(ret) # <img src='http://somehost/picture'/>
# 匹配 双/单 引号
"[^"]*"  # 双/单 引号 中可以是没有任何内容

在这里插入图片描述

爬虫实例:爬取豆瓣排名前250的电影

import re
from urllib.request import urlopen

def getPage(url): # 获取网页的字符串
     response = urlopen(url)
     return response.read().decode('utf-8')

def parsePage(s):
     ret = com.finditer(s) # 从s这个网页源码中 找到所有符合com正则表达式规则的内容 并且以迭代器的形式返回
     for i in ret:
         yield {
             "id": i.group("id"),
             "title": i.group("title"),
             "rating_num": i.group("rating_num"),
             "comment_num": i.group("comment_num"),
         }

def main(num): # 0 25 50 #这个函数执行10次,每次爬取一页的内容
     url = 'https://movie.douban.com/top250?start=%s&filter=' % num #每次等于多少页
     response_html = getPage(url) # response_html就是这个url对应的html代码 就是 str
     ret = parsePage(response_html) # ret是一个生成器
     #print(ret)
     f = open("move_info7", "a", encoding="utf8")
     for obj in ret:
         print(obj)
         data = str(obj)
         f.write(data + "\n")
     f.close()


com = re.compile(
         '<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
         '.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>', re.S)

#.*? 的使用,遇到什么停止,加上 re.S模式;把要用的内容分组好


count = 0
for i in range(10):
     main(count)
     count += 25

对照标签

在这里插入图片描述

匹配整数
import re

ret=re.findall(r"\d+","1-2*(60+(-40.35/5)-(-4*3))")
print(ret) # ['1', '2', '60', '40', '35', '5', '4', '3']
ret=re.findall(r"-?\d+\.\d*|(-?\d+)","1-2*(60+(-40.35/5)-(-4*3))")
print(ret) # ['1', '-2', '60', '', '5', '-4', '3']
ret.remove("")
print(ret) # ['1', '-2', '60', '5', '-4', '3']
匹配一个整数:\d+
匹配一个小数:\d+\.\d+
匹配一个整数或者小数:\d+\.\d+ | \d+  或者  \d+(\.\d+)?
数字匹配
>>>匹配一段文本中的每行的邮箱
     # 只允许英文字母、数字、下划线、英文句号、以及中划线组成
     # 例:zhoujielun-001@gmail.com
     ^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$
     
     # 名称允许汉字、字母、数字,域名只允许英文域名
     # 例:周杰伦001Abc@lenovo.com.cn
     ^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$

	# 邮箱规则:
	# @之前必须有内容且只能是字母(大小写)、数字、下划线(_)、减号(-)、点(.)
	# @和最后一个点(.)之间必须有内容且只能是字母(大小写)、数字、点(.)、减号(-),
	# --且两个点不能挨着
	# 最后一个点(.)之后必须有内容且内容只能是字母(大小写)、数字且长度为大于等于2个字
	# --节,小于等于6个字节

	[0-9a-zA-Z][\w\-.]+@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*\.[A-Za-z0-9]{2,6}

     
>>>匹配一段文本中的每行的时间字符串,比如:‘1990-07-12’;

   分别取出1年的12个月(^(0?[1-9]|1[0-2])$)、
   一个月的31天:^((0?[1-9])|((1|2)[0-9])|30|31)$

>>>匹配qq号。(腾讯QQ号从10000开始)1,9[0,9]{4,}

>>>匹配一个浮点数。       ^(-?\d+)(\.\d+)?$   或者  -?\d+\.?\d*

>>>匹配汉字。             ^[\u4e00-\u9fa5]{0,}$ 

>>>匹配出所有整数 


>>> 1-2*((60-30+(-40/5)*(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))
	# 从上面算式中匹配出内层没有其他小括号的表达式
	\([^()]+\)

>>>从类似9-1000/3+7/3*99/4*2998+10*568/14的表达式中匹配出从左到右第一个乘法或除法
	\d+[*/]\d+

模拟一个计算器功能

import re


def atmo_cal(exp):  # 原子计算器,最小单位不可再拆分了,处理乘除法,如:5*0.6;6/2
    if "*" in exp:
        a, b = exp.split("*")  # 最小了,只有两位
        return str(float(a) * float(b))
    elif "/" in exp:
        a, b = exp.split("/")
        return str(float(a) / float(b))


def add_sub(exp):  # 计算加减法
    ret = re.findall("[-+]?\d+(?:\.\d+)?", exp)  # 取消分组优先,得到一个列表
    exp_sum = 0
    for i in ret:
        exp_sum += float(i)
    return exp_sum


def mul_div(exp):  # 计算乘除法
    while True:
        ret = re.search("\d+(?:\.\d+)?[/*]-?\d+(?:\.\d+)?", exp)
        # 用的是search,所以从左往右匹配到第一项就返回结果
        if ret:
            atmo_exp = ret.group()  # 分组取出原子每一项
            res = atmo_cal(atmo_exp)  # 调用原子计算器,打印结果为"-22.0"
            # "2-1*-22+3-4/-5" "-22.0" 替换原式子中的位置即:"2--22.0-3-4/-5"
            exp = exp.replace(atmo_exp, res)  # 参数位置 旧的,新的 将旧的替换成新的
        else:  # 2--22.0-3--0.8 乘除法都运算完毕了
            return exp


def format(exp):
    exp = exp.replace('--', '+')
    exp = exp.replace('+-', '-')
    exp = exp.replace('-+', '-')
    exp = exp.replace('++', '+')
    return exp


def cal(exp):  # 将乘法或者除法匹配出来;"2-1*-22-3-4/-5" 用来计算用的
    exp = mul_div(exp)  # 调用乘除法函数
    exp = format(exp)  # 调用格式化函数后:2+22.0-3+0.8
    exp_sum = add_sub(exp)  # 调用计算加法的函数 21.8
    return exp_sum  # 返回结果


# print(cal("2-1*-22-3-4/-5"))


def main(exp):  # 主函数
    exp = exp.replace(" ", "")  # 去掉用户输入内容的空格
    while True:
        ret = re.search("\([^()]+\)", exp)  # (-40/5)
        if ret:
            inner_bracket = ret.group()  # 这里可能得到各种运算
            res = str(cal(inner_bracket))
            # print(inner_bracket,type(res))
            exp = exp.replace(inner_bracket, res)
            # print(exp) 再格式化下 1-2*((60-30+-8.0*(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))
            exp = format(exp)
            # print(exp)
        else:
            break
    # print(exp,type(exp)) ;1-2*-1388335.8476190479 <class 'str'>
    return cal(exp)


s = '1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )'
ret = main(s)
print(ret, type(ret))
print(eval(s), type(eval(s)))  # 用户可能再用到,所以像eval一样返回原来的格式
# 2776672.6952380957 <class 'float'>
# 2776672.6952380957 <class 'float'>
  • 30
    点赞
  • 148
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值