Python实现字符,单词,行,代码行,空行及可视化
Gitee项目地址:https://gitee.com/biubiubiuLYQ/word_and_character_statistics
一.解题思路
一开始拿到该题目,心想最近在学Shell编程,好像写个脚本,用wc命令都可以较轻松的把这些功能实现,但是这好像要得是具体去模拟wc命令,让自己更了解是如何实现的,一想,基本功能都挺好实现的,就是从没实现过带命令参数的程序,也不知道py文件打包成exe文件该如何实现,于是百度了一下,发现实现方法还挺多,那还等什么,于是便开干了......
二.程序设计实现过程
1.基本功能:
写了三个函数,一个函数only_one()是参数只有文件,我默认将字符数,单词数,行数写进与wc.exe文件下的同目录下的result.txt文件里,第二个函数是include_many_minglin(file_path,out_file)判断带命令(-w,-l,-c,-o)参数的,还未写扩展功能时,我也只给定一个参数,首先验证所带参数是否合法(命令重复或命令参数无法识别),验证成功之后继续验证是否带“-o”参数,因为两种情况不一样,如若不带“-o”参数,则所读取的文件为参数的最后一个,而带“-o”参数,则所读取的文件为倒数第三个,最后一个命令参数为结果写进的文件。第三个函数是out_nominglin(file_path)用来接受输出结果指定文件,默认为wc.exe当前目录下的result.txt文件。
2.扩展功能:
扩展功能都是基于基本功能实现的,重写了include_many_minglin(file_path,out_file,stop_txt_file=''),首先添加了判断命令是否带“-a”,若带该参数,则也保存代码行,空行,注释行,然后添加了停止词参数然后判断是否带命令参数“-e”,若有则读取停止词,将所读文件中的单词把停止词排除然后计数,还添加了函数getallfile(path),若命令参数中含“-s”,则遍历当前目录及子目录获取所有文件,另外一个函数是tomgpei(re_file),用于匹配文件名是否满足条件,并提取。
2.高级功能:
这是我单独做的模块,用的GUI,添加了一个类App(),该类主要用来显示可视框并实现点击按钮选择文件并显示文件的字符数,单词数,行数等信息,判断命令参数只有一个且为“-x”时,实列化类并执行。
三.代码说明
1.命令参数只有文件时的函数:
获取文件名,然后读取文件,获取字符数,单词数(用正则模块以“,”和空格分割),行数(以"\n"分割)并写进文件(file_path我配置为与wc.py同目录下的result.txt)。
def only_one():
'''
无命令参数时,写入文件单词,字符,行数
:return:
'''
file_path = sys.argv[-1]
try:
with open(file_path, 'r') as fp:
all_text = fp.read()
zifu = len(all_text)
words = len(re.split('[\s,]', all_text))
hangs = len(all_text.split('\n'))
with open(result_path, 'a', encoding='utf-8') as fp2:
fp2.write(sys.argv[-1] + ',' + '字符数:' + str(zifu) + '\n')
fp2.write(sys.argv[-1] + ',' + '单词数:' + str(words) + '\n')
fp2.write(sys.argv[-1] + ',' + '行数:' + str(hangs) + '\n') fp.close()
except:
print('参数为一个时只能是文件或文件路径!' + '请检查参数是否正确或文件是否正确!')
2.含命令参数('-c','-l','-w','-a'):
首先在主函数验证过后,命令参数合法才调用该函数,out_file为输出结果的文件(默认为result.txt),stop_txt_path代表停止词文件路径,如果命令参数带"-o"且命令格式正确,,若stop_txt_path不为空则读取该停止词文件,以逗号分割成若干个字符串,然后读取要读取的文件,验证是否有单词与停止词文件中字符串相同,若有则全部移除,最后将移除停止词后的文件单词计数。其他"-l",'"-w","-c"判断也是命令合法后然后若命令包含它,则在文件中保存相关信息。
def include_many_minglin(file_path, out_file=result_path, stop_txt_path=''):
'''
包含多个参数时:
file_path:被读取文件的路径
out_file:输出结果输入的文件路径
:param file_path:
:param out_file:
:return:
'''
try:
with open(file_path, 'r') as fp:
all_text = fp.read()
all_minglin = sys.argv[1:-1]
if '-c' in all_minglin:
zifu = len(all_text.strip())
try:
with open(out_file, 'a', encoding='utf-8') as fp2:
fp2.write(file_path + ',字符数:' + str(zifu) + '\n')
fp2.close()
except:
print('输出文件打开或创建失败!')
exit()
# print('字符数:' + str(zifu))
if '-w' in all_minglin:
words = re.split('[\s,,]', all_text)
try:
if stop_txt_path:
try:
with open(stop_txt_path, 'r') as stops:
stop_words = stops.read().split()
with open(out_file, 'a', encoding='utf-8') as fp2:
for word in words:
if word in stop_words:
words.remove(word)
else:
pass
fp2.write(file_path + ',单词数:' + str(len(words)) + '\n')
fp2.close()
stops.close()
except:
print('停止词文件打开失败!!')
else:
with open(out_file, 'a', encoding='utf-8') as fp2:
fp2.write(file_path + ',单词数:' + str(len(words)) + '\n')
fp2.close()
except:
print('输出文件打开或创建失败!')
exit()
if '-l' in all_minglin:
hangs = len(all_text.split('\n'))
try:
with open(out_file, 'a', encoding='utf-8') as fp2:
fp2.write(file_path + ',行数:' + str(hangs) + '\n')
fp2.close()
except:
print('输出文件打开或创建失败!')
exit()
# print('行数:' + str(hangs))
if '-a' in all_minglin:
control_data = ['%', '-', 'm.n', 'l', 'h']
hangss = all_text.split('\n')
null_ = True
code = 0
nulls = 0
zhushi = 0
for hang in hangss:
if len(hang) > 1:
for every_data in hang:
if every_data in control_data:
pass
else:
null_ = False
break
if null_ == True or hang == '{' or hang == '}' or len(hang) == 0:
nulls += 1
else:
if '//' in hang or '/*' in hang:
zhushi += 1
else:
code += 1
with open(out_file, 'a', encoding='utf-8') as fp2:
fp2.write(file_path + ',' + '代码行/空行/注释行:' + str(code) + '/' + str(nulls) + '/' + str(zhushi) + '\n')
fp2.close()
fp.close()
except:
print('你的文件路径或文件格式错误,无法打开该文件,请检查后再次输入!!!')
3.遍历所在目录及子目录获取所有文件并筛选符合条件的:
若命令参数带"-s",首先通过getallfile获取当前目录及子目录所有文件,然后通过tomgpei验证符合条件的文件,若参数本身是一个目录,则默认获取该目录及子目录下的所有文件并返回,否则(我这里写的通配符只写了*),及以*分割参数,然后判断文件名是否符合条件(包含以*分割的所有字符串)。
def getallfile(path):
'''
递归获取目录下所有文件
:param path:
:return:
'''
allfilelist = os.listdir(path)
for file in allfilelist:
filepath = os.path.join(path, file)
# 判断是不是文件夹
if os.path.isdir(filepath):
getallfile(filepath)
else:
allfile.append(filepath)
return allfile def tomgpei(re_file):
'''
通配符匹配
:param re_file:
:return:
'''
if os.path.exists(re_file):
if os.path.isdir(re_file):
allfiles = getallfile(re_file)
return allfiles
elif os.path.isfile(re_file):
all = []
all.append(re_file)
return all
else:
print('不是文件名或目录!')
return None
elif '*' in re_file:
all_txts = []
allfiles = getallfile(Now_Dir)
split_datas = re_file.split('*')
for file in allfiles:
y_n = True
for split_data in split_datas:
if split_data in file:
pass
else:
y_n = False
if y_n == True:
all_txts.append(file)
else:
pass
return all_txts
else:
print('目录或文件不存在!')
return None
4.可视化实现:
这里是GUI的一些简单用法,具体就是添加了两个按钮(一个退出,一个选择文件),并绑定了相应的函数,一个是退出按钮自带的关闭弹出框的函数frame.quit,选择文件按钮绑定的是chose_wenjian函数,用于选择文件并对文件相关信息统计然后动态加入txt文本框中。
class App:
'''
gui实现可视化
''' def __init__(self, master):
# 构造函数里传入一个父组件(master),创建一个Frame组件并显示
frame = Frame(master)
frame.pack()
# 创建两个button,并作为frame的一部分
self.wenjian_path = StringVar()
self.label = Label(text="文件路径: ")
self.label.pack(side=TOP)
self.entry = Entry(textvariable=self.wenjian_path)
self.entry.pack(side=TOP)
self.button = Button(frame, text="退出", fg="red", command=frame.quit, anchor='sw')
self.button.pack(side=RIGHT) # 此处side为LEFT表示将其放置 到frame剩余空间的最左方
self.hi_there = Button(frame, text="选择文件", fg='red', command=self.chose_wenjian, anchor='se')
self.hi_there.pack(side=LEFT)
self.label = Label(text="文件信息显示: ")
self.label.pack(side=TOP)
self.txt = Text(width=55, height=15)
self.txt.pack() def chose_wenjian(self):
paths = askopenfile()
self.wenjian_path.set(paths.name)
if paths:
self.txt.delete(0.0, tkinter.END)
try:
with open(paths.name, 'r') as f:
all_contents = f.read()
zifu = len(all_contents)
words = len(re.split('[\s,]', all_contents))
hangs = len(all_contents.split('\n'))
control_data = ['%', '-', 'm.n', 'l', 'h']
null_ = True
code = 0
nulls = 0
zhushi = 0
hangss = all_contents.split('\n')
for hang in hangss:
if len(hang) > 1:
for every_data in hang:
if every_data in control_data:
pass
else:
null_ = False
break
if null_ == True or hang == '{' or hang == '}' or len(hang) == 0:
nulls += 1
else:
if '//' in hang or '/*' in hang:
zhushi += 1
else:
code += 1
self.txt.insert(END, "字符数:" + str(zifu) + "\n")
self.txt.insert(END, "单词数:" + str(words) + "\n")
self.txt.insert(END, "行数:" + str(hangs) + "\n")
self.txt.insert(END, "代码行数:" + str(code) + "\n")
self.txt.insert(END, "空行数:" + str(nulls) + "\n")
self.txt.insert(END, "注释行数:" + str(zhushi) + "\n") except:
self.txt.insert(END, "文件打开失败,请检查文件格式是否正确!!!")
else:
pass
5.判断命令参数是否合法,并获取相关信息:
判断参数是否存在,是否重复,是否含“-s”,“-o”,"-e",若含有则修改相应状态(False/True),并在后续调用不同的函数。
# 默认无结果输出文件
data_out_txt = False
# 默认为停止词文件
data_stop_txt = False
#是否含需要遍历目录
DIGUI = False
if len(sys.argv) <= 1:
print('命令格式不正确!!!')
elif len(sys.argv) == 2:
if sys.argv[-1] == '-x':
win = Tk()
win.geometry('500x310+500+200')
# 设置窗口标题
win.title('文件检索')
app = App(win)
win.mainloop()
else:
only_one()
else:
minglin = ['-c', '-w', '-l', '-o', '-s', '-a', '-e']
for i in sys.argv[1:-1]:
if i not in minglin and sys.argv[sys.argv.index(i) - 1] == '-o':
pass
elif i not in minglin and sys.argv[sys.argv.index(i) - 1] == '-e':
pass
elif i not in minglin and "-s" in sys.argv[1:sys.argv.index(i)]:
pass
else:
if i in minglin and sys.argv.count(i) == 1 and i != '-o' and i != '-e' and i!='-s':
pass
elif i in minglin and sys.argv.count(i) > 1:
print('命令重复!!请修改!')
# break
exit()
elif sys.argv.count(i) == 1 and i == '-o':
data_out_txt = True
elif sys.argv.count(i) == 1 and i == '-e':
data_stop_txt = True
stop_txt_path = sys.argv[sys.argv.index(i) + 1]
elif sys.argv.count(i) == 1 and i == '-s':
DIGUI = True
else:
print('命令格式错误!!!')
# break
exit()
四.测试设计过程(路径覆盖)
1.基本功能测试:
1.1返回字符数:wc.exe -c G:\main.c
1.2返回单词数:wc.exe -w G:\main.c
1.3返回行数:wc.exe -l G:\main.c
1.4返回字符数和单词数:wc.exe -c -w G:\main.c
1.5返回字符数和行数:wc.exe -c -l G:\main.c
1.6返回单词数和行数:wc.exe -w -l G:\main.c
1.7返回字符数,单词数,行数:wc.exe -c -w -l G:\main.c
所有测试命令
结果
1.8测试输出结果到指定文件(字符数):wc.exe -c G:\main.c -o outfile.txt
1.9测试输出结果到指定文件(单词数):wc.exe -w G:\main.c -o outfile.txt
1.10测试输出结果到指定文件(行数):wc.exe -l G:\main.c -o outfile.txt
1.11测试输出结果到指定文件(字符数和单词数):wc.exe -c -w G:\main.c -o outfile.txt
1.12测试输出结果到指定文件(字符数和行数):wc.exe -c -l G:\main.c -o outfile.txt
1.13测试输出结果到指定文件(单词数和行数):wc.exe -w -l G:\main.c -o outfile.txt
1.14测试输出结果到指定文件(单词数,字符数,行数):wc.exe -c -w -l G:\main.c -o outfile.txt
测试命令
结果
2.扩展功能测试:
2.1遍历目录及子目录符合条件的文件(输出字符数):wc.exe -s -c *.txt
测试命令
结果
2.2遍历目录及子目录符合条件的文件(输出字符数及单词数):wc.exe -s -c -w *.txt
测试命令
结果
2.3遍历目录及子目录符合条件的文件(输出字符数,单词数,行数):wc.exe -s -c -w -l *.txt
测试命令
结果
2.4遍历目录及子目录符合条件的文件(输出字符数,单词数,行数并到指定文件):wc.exe -s -c -w -l *.txt -o G:/all_results.txt
测试用例
结果
2.4处理复杂的代码行(只返回代码行,空行,注释行):
测试用例
结果
2.5返回字符数,单词数,行数,复杂数据:
测试用例
结果
2.6 返回字符数,单词数,行数,复杂数据到指定文件:
测试用例
输出结果
2.7遍历目录及子目录下的满足条件的文件,并输出复杂数据:
测试用例
结果
2.8遍历目录及子目录下的满足条件的文件,并输出复杂数据到指定文件:
测试用例
结果
2.9遍历目录及子目录下的满足条件的文件,并输出复杂数据,字符数,单词数,行数:
测试用例
结果
2.10遍历目录及子目录下的满足条件的文件,并输出复杂数据,字符数,单词数,行数到指定文件:
测试用例
结果
2.11排除停止词输出单词数:
测试命令
结果验证正确
2.12返回当前目录及子目录中所有.txt文件的字符数、单词总数、代码行数、空行数、注释行数,并将结果保存在output.txt中,且统计单词时忽略stop.txt中的单词:
测试命令
结果
3.高级功能测试:
测试命令
弹出框
选择文件后显示信息
选择其他文件会覆盖上一文件信息
五.目前的问题:
1.打包成exe文件后程序运行比Python环境下运行py文件慢很多;
2.打包成exe文件后默认的result.txt文件不会创建在于wc.exe文件下的同目录中,而是创建在当前所在文件夹下,而运行原py文件会创建在于wc.py文件下的同目录中;
3.若在遍历目录下文件时,把输出结果文件创建在当前目录下会阻塞。
六.参考文献
Tkiner的简单使用:http://www.runoob.com/python/python-gui-tkinter.html
Python打包成exe文件:https://blog.csdn.net/u010812071/article/details/78507946