python 练习(一)代码统计工具的实现
最近部门成立了一个python学习小组,旨在让大家在做项目中开始成长起来,于是老大就给布置了第一个小任务:代码统计工具,具体的需求如下:
需求:
. 能够统计指定目录下C++程序的代码行数。
. C++程序文件包括.cpp和.h文件。
. 如果指定目录下有子目录,需要能够递归遍历所有子目录。
. 能够区分注释和代码。
. 不用考虑语句跨行问题。
. 输出.cpp、.h文件个数、代码行数、注释行数及处理时间。
. 基于python3开发。
乍一看,感觉好像有点难度,一下要处理这么多的功能,又是统计特定文件的个数,又是要遍历子目录,还要统计注释行。后面网上百度了一下,万能的python提供的函数简直是方便用到不行,os的walk函数实现了目录的遍历,os.path的splitext能得到文件后缀名,现在最主要的问题来了:怎么识别代码里的注释。
在c++里,注释主要分行注释(//)和块注释(/* */), 起初有2个想法短暂的想法觉得可以实现这个功能
1. 读入的每一行当成一个字符串,判断是否存在行注释符或者块注释符
2. 使用正则表达式
很快这个想法就觉得行不通了,就一个简单的例子,如果一个代码行是这样的:
"// test /* */ ";
就一个简单的字符串,不管是第一种还是第二种方法,对于诸如此类的奇怪写法,都是非常不好处理的,还有以下的几种奇葩的写法:
// int c = 0; \
int b = ;
实际上面的2行都应该算是注释的,单单用方式1,方式2就已经无法做了,因为每行之间不是独立的,行与行之间可能还存在一定的关系,正在百感交集之时,开发哥哥向我抛出了一个关键字:状态机。读行一行的每一个字符,然后根据字符的不同,设置不同的状态,很多的集成开发工具代码行的统计都是采用此类方法,于是,我毅然决定采用此方法来实现注释码的统计。
在实现之处,确实有一种一头雾水的感觉,因为不清楚到底有哪些状态,于是用vs工具,结合开发哥哥提的意见,整了一个特别奇葩的cpp文档,如下所示:
//
/* 123
*/
/* /* */
int g_i;
/* " /*
int g_j;
*/
#include <iostream>
int main(int argc, char *argv[])
{
/* " test */int i; // */ int j; /* test */
int k; // test
/* test */ j = ;
const char *test = "test // ";
int m; /* /*
const char *test2 = "test /*";
const char *test3 = "*/ "test"; // */
/* * /
*
*/
/* // *//* */int n;
const char *test4 = "/*"
"//"
" "
"";
// t1 \
;
cout<<"i am \
loleina";
// "test\"
int a=,\
c;
// \test
b=
cout<<"i am \r loleima";
return ;
}
然后就发现了一些规律:当字符是 “ 或者是 / 或者是 * 或者是 \ 的时候,代码行的状态是有可能发生改变的,假设有3个状态,初始状态,正常状态,字符串状态,最开始打开一个文本,设置行状态为初始状态,读入第一行,如果读入的字符是” ,则表示进入字符串状态,在字符串状态内,读入字符// 或者 /* 这些都是不管的,只有当再次读到”时,状态才会发生改变,此时则变成正常状态。如果读字符是/ , 则有可能进入注释状态了,设定为预注释状态,如果下一个字符读入的是/ ,则表示进入行注释状态,如果读入的是*,则表示进入块注释状态,在行注释里,只需要考虑读入换行符或者读入拼接符\,其他字符的读入都不会引起状态的改变,而在块注释就跟简单,只需要考虑正常退出即可。整理下思维逻辑后,弄了个状态转换图,如下所示:
画完状态图后,感觉状态之间的转换关系理得差不多了,但是还有2个问题没解决,第一个问题:
1. 怎么计算有效代码数?
2. 行与行之间如果有关联关系的时候怎么处理?
第一个问题,可以为每行设置一个状态:is_effetive_code, 默认设置为false,但是只要在正常状态下,读入到一个有效字符(非/)就判断当前行为有效代码行,就设置bool值为true,在每行最后再判断该值,如果是true,则代码行加1;
第二个问题,只有代码行处于拼接状态和块注释状态时,才会影响第二行第一个字符的状态,所以在每行最后,保存当前状态,如果当前状态拼接状态和块注释状态时,设置第二行状态为拼接状态和块注释状态就可以了。
问题基本都解决了,开始实现了,总共分2个部分,一个函数CountFies实现从路径得到有效文件。一个函数ReadEffetiveCodeNumber则是统计有效文件的所有有效代码:
def CountFies(path):
filesList = []
for root, dirs, files in os.walk(path):
for file in files:
if (os.path.splitext(file)[] == '.h' or os.path.splitext(file)[] == '.cpp'):
filename = os.path.join(root, file)
filesList.append(filename)
return filesList
def ReadEffetiveCodeNumber(filesList):
codes_numbers =
for i in range(len(filesList)):
filename=filesList[i]
infp = open(filename,encoding = 'gbk', errors = 'ignore')
lines = infp.readlines()
row_cur_status = Status.Common
for li in lines:
row_pre_status = Status.Init
li = li.strip("\r\t ")
if len(li) == :
continue
is_effective_code = False
for charli in li:
if row_cur_status == Status.Common:
if charli == '/':
row_cur_status = Status.PreComment
continue
if charli == '\"':
row_cur_status = Status.CharString
continue
if charli == '\n':
continue
if charli == ' ':
continue
else:
is_effective_code = True
continue
elif row_cur_status == Status.CharString:
if charli == '\"':
row_cur_status = Status.Common
is_effective_code = True
continue
if charli == '\\':
row_pre_status = row_cur_status
row_cur_status = Status.PreCombination
continue
else:
continue
elif row_cur_status == Status.PreComment:
if charli == '/':
row_cur_status = Status.LineComment
continue
if charli == '*':
row_cur_status = Status.BlockComments
continue
else:
continue
elif row_cur_status == Status.LineComment:
if charli == '\n':
row_cur_status = Status.Common
continue
if charli == '\\':
row_pre_status = row_cur_status
row_cur_status = Status.PreCombination
continue
else:
continue
elif row_cur_status == Status.BlockComments:
if charli == '*':
row_cur_status = Status.PreExitComment
continue
else:
continue
elif row_cur_status == Status.PreExitComment:
if charli == '/':
row_cur_status = Status.Common
continue
else:
row_cur_status = Status.BlockComments
continue
elif row_cur_status == Status.PreCombination:
if charli == '\n':
row_cur_status = Status.Combination
else:
row_cur_status = row_pre_status if is_effective_code == True:
codes_numbers +=
if row_cur_status not in (Status.BlockComments, Status.Combination):
row_cur_status = Status.Common
if row_cur_status == Status.Combination:
row_cur_status = row_pre_status infp.close()
return codes_numbers
if __name__=="__main__":
userdir = input("input the directory :")
while os.path.isdir(userdir) == False:
userdir = input("it is wrong,input the directory again : ") # .表示普通状态 .表示字符串状态 .表示预注释状态 .表示行注释状态 .表示块注释状态 .表示预退出块注释状态 .表示预拼接状态 .表示拼接模式
Status = enum(Init=,Common=, CharString=, PreComment=,LineComment=,BlockComments=,PreExitComment=,PreCombination=,Combination=) # .计算给定路径下存在有效文件(.cpp,.h)的个数,并把有效文件的绝对路径保存在list列表返回
# .读取list下每个文件的有效代码行数,返回总的有效代码行数
# A.读取每个文件的每行
# B.去掉每行的前后空格,以及空行
# C.依次读取每行的每个字符并设置状态
start = time.time()
filesList = CountFies(userdir)
codes_numbers = ReadEffetiveCodeNumber(filesList)
end = time.time()
dealTime = end - start
print("it has %d files(.cpp,.h) and all effective code lines count:%d" %(len(filesList),codes_numbers ))
print ("Code execution consumes %s seconds" %dealTime)
于是用简单的方法就实现了整体的需求,小组内总共有7位童鞋都写了,但是绝大部分用的是方法1查找是否为代码行,有一位童鞋是用方法2正则表达式来实现的,但大家都考虑不够全面,只能统计所谓‘正常人’编写的代码行,而第二种用正则表达式实现的,在性能上还有很大的问题,大家基本都是1s或者1s内计算完成,而正则花了近7s才计算完成。完了,老大点评了下,我这种是最佳实现方式,然后我笑了~~~
这次的练习,实际收获还是挺大的,因为实际编写的过程还遇到一些问题, 比如:写完代码后,把代码放在linux下运行老大指定的特定文件夹,结果程序崩掉了,提示是字符码的问题,但是测试自己放上去的文件夹,没事;放在linux上运行的时候,提示语乱码。
最后,针对2周的相关学习,总结如下:
1. 读批量文件时,应该提供异常捕获机制。可能会由于文件内的编解码格式与读入的格式不一致,会直接导致程序崩溃。
2. 尽量少的使用正则表达式,正则表达式使用多的时候,可能会存在性能上的某些瓶颈。
3. 学习os.walk函数:官网文档:https://docs.python.org/2/library/os.html,属于os模块
os.walk(top, topdown=True, onerror=None, followlinks=False)
Generate the file names in a directory tree by walking the tree either top-down or bottom-up. For each directory in the tree rooted at directory top (including top itself), it yields a -tuple (dirpath, dirnames, filenames). dirpath is a string, the path to the directory. dirnames is a list of the names of the subdirectories in dirpath (excluding '.' and '..'). filenames is a list of the names of the non-directory files in dirpath. Note that the names in the lists contain no path components. To get a full path (which begins with top) to a file or directory in dirpath, do os.path.join(dirpath, name).
4. 学习打开文件函数 open。https://docs.python.org/2/library/io.html,属于IO模块。
io.open(file, mode='r', buffering=-, encoding=None, errors=None, newline=None, closefd=True)
Open file and return a corresponding stream. If the file cannot be opened, an IOError is raised.
5. 学习splitext 函数。https://docs.python.org/2/library/os.path.htm,l属于IO-path模块。
os.path.splitext(path)
Split the pathname path into a pair (root, ext) such that root + ext == path, and ext is empty or begins with a period and contains at most one period. Leading periods on the basename are ignored; splitext('.cshrc') returns ('.cshrc', ''). Changed in version 2.6: Earlier versions could produce an empty root when the only period was the first character.
6. 对python各个模块的官网学习,显得更加迫在眉睫,之前看各种文档和别人的博客,总感觉少了什么,果然每次使用还是得去查官网文档才能正确使用,接下来的一段日子,会静下心去学习python官网的各类文档,尽力每学一篇,再自己总结下~~~
python 练习(一)代码统计工具的实现的更多相关文章
- Python实现C代码统计工具(四)
目录 Python实现C代码统计工具(四) 标签: Python 计时 持久化 声明 运行测试环境 一. 自定义计时函数 1.1 整个程序计时 1.2 代码片段计时 1.3 单条语句计时 二. 性能优 ...
- Python实现C代码统计工具(三)
目录 Python实现C代码统计工具(三) 声明 一. 性能分析 1.1 分析单条语句 1.2 分析代码片段 1.3 分析整个模块 二. 制作exe Python实现C代码统计工具(三) 标签: Py ...
- Python实现C代码统计工具(二)
目录 Python实现C代码统计工具(二) 声明 一. 问题提出 二. 代码实现 三. 效果验证 Python实现C代码统计工具(二) 标签: Python 代码统计 声明 本文将对<Pytho ...
- Python实现C代码统计工具(一)
目录 Python实现C代码统计工具(一) 声明 一. 问题提出 二. 代码实现 三. 效果验证 四. 后记 Python实现C代码统计工具(一) 标签: Python 代码统计 声明 本文将基于Py ...
- Python代码统计工具
目录 Python代码统计工具 声明 一. 问题提出 二. 代码实现 三. 效果验证 Python代码统计工具 标签: Python 代码统计 声明 本文将对<Python实现C代码统计工具(一 ...
- Python实现代码统计工具——终极加速篇
Python实现代码统计工具--终极加速篇 声明 本文对于先前系列文章中实现的C/Python代码统计工具(CPLineCounter),通过C扩展接口重写核心算法加以优化,并与网上常见的统计工具做对 ...
- C++代码统计工具
自己前几天写的C++代码统计工具. http://pan.baidu.com/s/17SnnH
- svn代码统计工具的金额
StatSVN介绍 StatSVN是Java写开源统计程序,从statCVS从移植.从能Subversion版本号来获取信息库,该项目开发的叙述性说明,然后生成各种表格和图表.例:时间线.针对每一个开 ...
- 代码统计工具-cloc
官网地址:http://cloc.sourceforge.net/ https://sourceforge.NET/projects/cloc/files/ 下载得到cloc-1.64.exe Clo ...
随机推荐
- 二 、打开地图《苹果iOS实例编程入门教程》
该app为应用的功能为给你的iPhone打开google地图有效地址连接 现版本 SDK 8.4 Xcode 运行Xcode 选择 Create a new Xcode project ->Si ...
- 1022. D进制的A+B (20)
1022. D进制的A+B (20) 时间限制 100 ms 内存限制 32000 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 输入两个非负10进制整数A和 ...
- js事件冒泡和事件捕获的区别
- ArcGIS JavaScript API异常之onExtentChange事件覆盖onClick事件
利用Esri官方提供的聚合类进行聚合,由于数据较多,为了加快速度,在聚合之前对当期范围进行判断,如果不在当前视图范围的,就不聚合了. 所以,由于Esri官方的类是监听了zoomEnd事件,如下代码 t ...
- HTTP协议 (六) 状态码详解
HTTP协议 (六) 状态码详解 HTTP状态码,我都是现查现用. 我以前记得几个常用的状态码,比如200,302,304,404, 503. 一般来说我也只需要了解这些常用的状态码就可以了. 如果 ...
- vbox下Oracle Enterprise liunx5.4虚拟机安装10G RAC实验(四)
接第3篇 http://www.cnblogs.com/myrunning/p/4003527.html 5.安装配置数据库 5.1安装数据库软件 5.2配置监听 5.3创建ASM磁盘 5.4创建服务 ...
- 配置samba服务一例
问题: 在/data/share目录下建立三个子目录public.training.devel用途如下 public目录用于存放公共数据,如公司的规章制度 training目录用于存放公司的技术培训资 ...
- 微信APP支付(Java后台生成签名具体步骤)
public class PayCommonUtil { //定义签名,微信根据参数字段的ASCII码值进行排序 加密签名,故使用SortMap进行参数排序 public static String ...
- Lambda表达式公共拼接函数(原创)
#region Lambda公共拼接函数 /// <summary> /// LambdaWhere(枚举) /// </summary> public enum Lambda ...
- 怎么启动或停止mysql服务
在linux下, 启动mysql用 service mysql start 停止用 service mysql stop 在windows下, 启动用 net start mysql 停止 ...