最近在做个项目,里面经常用到正则表达式,需要不停的调试修改正则表达式,如果直接在程序里跑,是一件很麻烦且缓慢的事,网上有挺多的正则表达式调试网站,奈何这边网络太差,比让人在程序里跑还让人崩溃,所以就自己写了个正则表达式调试小工具。
以下是小工具简单的效果演示:
re库
正则表达式功能强大,用途很广泛,大多数编程语言都可以用,像Python爬虫,对于数据提取阶段,不管网页数据是静态网页还是动态网页,它都可以用,不像Xpath、BeautifulSoup、css、Jsonpath,只能用在特定场景。
在python中,使用正则表达式需要用到re库,所以在制作小工具前,需要先知道re里面有什么方法函数,有什么效果,怎么用。
常用方法
这里讲解几个比较常用的方法,然后挑选合适的方法去打造一个正则表达式调试工具。以下讲解的方法包括:compile、findall、match、fullmatch、search、finditer、sub、subn、split,除了compile方法与其它方法函数的参数pattern的功能重叠,不选用为调试工具的功能,其它的都选用。
findall()
findall
方法匹配所有符合匹配规则的内容,返回的是一个列表,pattern
为匹配规则(正则表达式), string
为要匹配的字符串, flags
为修饰符,默认对于0,修饰符包含:S,L,I,M,U,X,A,这里会挑选几个加入调试工具中。
findall(pattern, string, flags)
示例:
代码语言:javascript复制text = 'https://mm.enterdesk.com/bizhi/63338.html'
print(re.findall('[a-z] ', text))
结果:
compile()
compile
方法为编写一个匹配规则,最终返回的是一个对象。常用于大量构写相同的匹配规则时使用。
compile(pattern, flags)
示例:
代码语言:javascript复制text = 'https://mm.enterdesk.com/bizhi/63338.html'
compiles = re.compile('d ')
print(re.findall(compiles, text))
结果:
match()
match
方法是从要匹配的字符串开头开始匹配,匹配到匹配规则不相同的内容时终止,只匹配一次,匹配成功返回一个对象;当开头匹配不符合匹配规则时则返回None。也就是说,字符串的开头不匹配就终止匹配,字符串的开头匹配成功就继续匹配到匹配不成功;参数同上。
match(pattern, string, flags)
示例:
代码语言:javascript复制text = 'https://mm.enterdesk.com/bizhi/63338.html'
print(re.match('[d] ', text))
print(re.match('[a-z] ', text))
结果:
fullmatch()
fullmatch
方法与match
类似,但fullmatch方法是,匹配的内容从头到尾都要与匹配规则相匹配;参数同上。
fullmatch(pattern, string, flags)
示例:
代码语言:javascript复制text = 'https://mm.enterdesk.com/bizhi/63338.html'
print(re.fullmatch('[a-z] ', text))
print(re.fullmatch('[a-z] ://[^s] ', text))
print(re.fullmatch('[a-z] ://[^s] ', text).group())
结果:
search()
search
方法区别于match
,search不要求字符串前面与匹配规则相匹配,直接从字符串中匹配与匹配规则相匹配的第一个内容,也是只匹配一次;参数同上。
search(pattern, string, flags)
示例:
代码语言:javascript复制text = 'https://mm.enterdesk.com/bizhi/63338.html'
print(re.search('d ', text))
print(re.search('d ', text).group())
结果:
finditer()
finditer
区别于findall
方法,返回的是一个迭代器,用for循环遍历加group()方法可以打印所匹配的值;参数同上。
finditer(pattern, string, flags)
示例:
代码语言:javascript复制text = 'https://mm.enterdesk.com/bizhi/63338.html'
print(re.finditer('([^s] )', text))
for i in re.finditer('([^s] )', text):
print(i.group())
结果:
sub()
sub
方法是替换掉所匹配的内容,repl
是要替换为的字符串,count
为最大替换数,默认为0,输入的数值不得小于0。
sub(pattern, repl, string, count, flags)
示例:
代码语言:javascript复制text = 'https://mm.enterdesk.com/bizhi/63338.html'
print(re.subn('[a-z] ', '*', text))
结果:
subn()
subn
与sub
类似,但subn返回的是元组, 包含替换完成的新字符串和替换内容的次数。
subn(pattern, repl, string, count, flags)
示例:
代码语言:javascript复制text = 'https://mm.enterdesk.com/bizhi/63338.html'
print(re.subn('[d] ', '*', text))
结果:
split()
split
为切割匹配,根据匹配规则,把匹配规则以及匹配规则左右的 内容进行切割,最终形成一个list,maxsplit为最大匹配数,默认为0,不得小于0。
split(pattern, string, maxsplit, flags)
示例:
代码语言:javascript复制text = 'https://mm.enterdesk.com/bizhi/63338.html'
print(re.split('/', text, maxsplit=0))
print(re.split('/', text, maxsplit=2))
结果:
编写工具
可视化界面是用PySimpleGUI
库来进行编写,用re来编写正则表达式底层逻辑。本文只要到这两个库,安装用pip命令即可!
设计逻辑
对于最终的成果,应该是与直接在程序里写正则表达式差不多的,那么,该工具要实现正则表达式的调试,就要从正常写正则表达式的步骤设计。
这里拿findall
方法进行举例说明:
findall(pattern, string, flags)
上面有说到findall
含有三个参数,分别为:
pattern
- 正则表达式string
- 要匹配的字符串flags
- 修饰符
按照findall
方法的用法,我们必须要输入 pattern
和string
参数,而flags
默认等于0,可选可不选。从用法上看,我们必须要从界面中获取 pattern
和string
参数,而flags
可以进行选择性传入,那么我们的GUI界面必须存在传入这三个参数的输入框或者选择框!
pattern
- 正则表达式输入框string
- 要匹配的字符串输入框flags
- 修饰符输入、选择框
flags
因为不只有一个,含有:S,L,I,M,U,X,A,所以我们需要创建一个输入、选择下拉文本框框,方便我们选择,并且在程序上设置它默认为0!
另外,工具中的正则匹配方法有八个,且有几个方法的参数是不同的,所以我们要区分开来,创建不同的传参人口。比如subn
方法,它除了pattern, string, flags参数, 还会有count,repl参数:
subn(pattern, repl, string, count, flags)
而八个方法函数我们也要在匹配内容时声明一下,也就是创建一个输入、选择下拉文本框,方便我们选择正确的方法。
基于上面的逻辑,我创建了对应八个方法的函数,设置了传参变量。后面需要使用相应的方法时,可以直接调用函数:
代码语言:javascript复制def re_findall(patterns, strings, flags):
findall = re.findall(patterns, strings, flags)
print(findall)
def re_match(patterns, strings, flags):
match = re.match(patterns, strings, flags)
print(match)
def re_fullmatch(patterns, strings, flags):
fullmatch = re.fullmatch(patterns, strings, flags)
print(fullmatch)
def re_search(patterns, strings, flags):
search = re.search(patterns, strings, flags)
print(search)
def re_finditer(patterns, strings, flags):
finditer = re.finditer(patterns, strings, flags)
print(finditer)
def re_sub(patterns, repl, strings, count, flags):
sub = re.sub(patterns, repl, strings, count, flags)
print(sub)
def re_subn(patterns, repl, strings, count, flags):
subn = re.subn(patterns, repl, strings, count, flags)
print(subn)
def re_split(patterns, strings, maxsplit, flags):
split = re.split(patterns, strings, maxsplit, flags)
print(split)
通过调用函数,可以直接打印出匹配结果,并通过界面输出文本框进行展示。
GUI界面设计
通过上面简单的逻辑讲解,我们知道可视化界面中需要的几个元素:
- 正则表达式输入文本框 - InputText
- 方法函数输入、选择下拉文本框 - Combo
- 修饰符输入、选择下拉文本框 - Combo
- 要匹配的内容输入文本框(多行)- Multiline
- 开始匹配按钮 - Button
- 结果输出文本框 - Output
基于上面的内容,我设计出以下GUI界面:
除了基本的元素,我另外还在界面中添加了清空匹配内容
和清空匹配结果
按钮。
基于上面的GUI界面设计,我们得出以下代码::
代码语言:javascript复制sg.theme('LightBrown3')
# 方法名称
method = ['findall', 'match', 'fullmatch', 'search', 'finditer', 'sub', 'subn', 'split']
# 修饰符
mode = ['S', 'I', 'M', 'U', 'X']
# 布局设置
layout = [[
sg.Column([
[sg.Text('正则表达式:', font=("微软雅黑", 12)),
# InputText输入文本框,正则表达式输入文本框
sg.InputText(key='keys', size=(49, 1), font=("微软雅黑", 10), enable_events=True),
# Button按钮
sg.Button('开始匹配', font=("微软雅黑", 12))
],
[sg.Text('请选择或输入方法名:', font=("微软雅黑", 12)),
# Combo下拉框
sg.Combo(values=method, tooltip='选择或输入方法', font=("微软雅黑", 10), auto_size_text=True,
size=(14, 10), key='method'),
sg.Text('请选择或输入修饰符:', font=("微软雅黑", 12)),
sg.Combo(values=mode, tooltip='选择或输入修饰符', font=("微软雅黑", 10), auto_size_text=True,
size=(13, 10), key='mode')],
[sg.Text('-' * 37 '匹配内容' '-' * 37, justification='center', text_color='blue')],
# Multiline多行文本框
[sg.Multiline(key='content', s=(70, 20), font=("微软雅黑", 10), text_color='red')],
[sg.Button('清空匹配内容', font=("微软雅黑", 12))]
]),
# 给两个布局加一条线
sg.VSeperator(),
sg.Column([
[sg.Text('-' * 37 '匹配结果' '-' * 37, justification='center', text_color='blue')],
[sg.Output(key='result', size=(70, 24), font=("微软雅黑", 10), text_color='red')],
[sg.Text('', font=("微软雅黑", 12), size=(46, 0)), sg.Button('清空匹配结果', font=("微软雅黑", 12))]
])
]]
# 创建窗口
window = sg.Window('正则表达式调试器', layout, font=("微软雅黑", 12), default_element_size=(80, 1))
while True:
# 退出按钮
event, values = window.read()
if event in (None, 'Exit'):
break
window.close()
用户交互逻辑
对于参数内容的接收和匹配结果的输出,我刚开始是使用exec
函数,因为他简洁,且不繁琐,只需要一句话即可:
exec(r'print(re.{}("{}", "{}", {}))'.format(method, patterns, strings, flags))
但这句话在实际操作中错误有很多,因为他常常在接收参数时发生错误,所以我选择了一种繁琐,但稳定的方式:
代码语言:javascript复制if event == '开始匹配':
# 之所以使用strip(),是因为多行文本框具有多余的换行符
if values['keys'] and values['content'].strip():
a = values['keys'] # 正则表达式
b = values['content'].strip()
if values['method'] == 'findall': # 判断方法,再选择修饰符,不选择时默认为0
if values['mode'] == 'S':
re_findall(patterns=str(a), strings=str(b), flags=re.S)
elif values['mode'] == 'I':
re_findall(patterns=str(a), strings=str(b), flags=re.I)
elif values['mode'] == 'M':
re_findall(patterns=str(a), strings=str(b), flags=re.M)
elif values['mode'] == 'U':
re_findall(patterns=str(a), strings=str(b), flags=re.U)
elif values['mode'] == 'X':
re_findall(patterns=str(a), strings=str(b), flags=re.X)
else:
re_findall(patterns=str(a), strings=str(b), flags=0)
与findall方法相同,match、fullmatch、search、finditer方法都是使用以上逻辑进行调用。其实是可以把一些修饰符删除的,但为了工具变得完美一些,我就不删除了。
其它三个方法中因为参数不相同,在调用这三个方法时,用popup_get_text
方法创建弹窗输入框,从而获取不相同的参数:
sub_count
为最大替换数,是数值类型,而从popup_get_text
方法创建的弹窗输入框得到的是字符串,所以要进行转换成数值类型:
elif values['method'] == 'sub':
sub_repl = sg.popup_get_text('请输入要替换的字符:', title='repl')
if sub_repl:
sub_count = sg.popup_get_text('请输入最大替换数:', title='count')
if sub_count and int(sub_count) >= 0: # 在输入框中输入的是字符串,要进行转换
if values['mode'] == 'S':
re_sub(patterns=str(a), repl=sub_repl, strings=str(b), count=int(sub_count), flags=re.S)
elif values['mode'] == 'I':
re_sub(patterns=str(a), repl=sub_repl, strings=str(b), count=int(sub_count), flags=re.I)
elif values['mode'] == 'M':
re_sub(patterns=str(a), repl=sub_repl, strings=str(b), count=int(sub_count), flags=re.M)
elif values['mode'] == 'U':
re_sub(patterns=str(a), repl=sub_repl, strings=str(b), count=int(sub_count), flags=re.U)
elif values['mode'] == 'X':
re_sub(patterns=str(a), repl=sub_repl, strings=str(b), count=int(sub_count), flags=re.X)
else:
re_sub(patterns=str(a), repl=sub_repl, strings=str(b), count=int(sub_count), flags=0)
else:
sg.popup('count未输入或者输入的数值小于0!')
else:
sg.popup('repl未输入!')
其它几个方法函数的调用逻辑差不多,这里就不再展示了。
清空内容按钮,用的是Update
方法,FindElement
获取的匹配内容
多行文本框和匹配结果输出
文本框的key值:
if event == "清空匹配内容":
window.FindElement("content").Update("")
elif event == "清空匹配结果":
window.FindElement("result").Update("")
打包
打包可以通过pyinstaller
库,安装只需要pip命令即可!安装后在命令行窗口cd到文件所在的文件目录中,最后用下面命令进行打包:
pyinstaller -F -w 名称.py
打包过程没出现什么状况,会得到几个文件,进入dist
文件夹,就可以看见.exe
文件了:
结语
虽然小工具做出来了,但我感觉不够Pythonic,总体上不是很满意,下面我还会尝试进行优化设计的。
完整的源码以及小工具已在公众号存着,感兴趣的小伙伴可以关注“Python与Excel之交”公众号,后台回复"正则"获取本文完整代码!
以上便是今天的全部内容了,如果你喜欢今天的内容,希望你能在下方点个赞和在看支持我,谢谢!