Python代码统计工具

标签: Python 代码统计


声明

本文将对《Python实现C代码统计工具(一)~(三)》中的C代码统计工具进行扩展,以支持Python脚本自身的行数统计。

一. 问题提出

此前实现的C代码统计工具仅能分析和统计C语言代码文件,但其设计思想也适用于Python代码及其他编码语言。

Python行数统计的难点在于注释行,因为Python有两种注释方式:简单明了的单行注释和复杂含糊的多行注释(块注释)。单行注释以#(pound或hash)符号起始,直至物理行的末尾(但字符串内的#并无注释作用)。多行注释可在每行头部添加#号,也可包入未命名的三引号字符串(triple-quoted strings,即多行字符串)内。除非未命名三引号字符串作为对象的文档字符串(docstring),即模块、类、或函数体的第一条语句为未命名字符串,否则可作为多行注释。

下面以总27_代7_注15_空5.py脚本为例,演示不同的注释方式。注意,该脚本仅作测试数据用,并非真实世界中的脚本文件。

#!/usr/bin/python
# -*- coding: utf-8 -*- #comment3 print 'code1'
'''comment4
print """comment5"""
comment6''' """comment7
'''print 'comment8 and comment9'
"""
print 'code2' def code3():
"""f = open('whatever', 'r')
multiline comment 10,11,12 make up a doc string
"""
print 'code4'
'''
print 'comment13, comment14 and comment15'
'''
return 'code5' help(code3); print 'code6'
print code3.__doc__, 'code7'

运行该脚本后,输出如下:

code1
code2
Help on function code3 in module __main__: code3()
f = open('whatever', 'r')
multiline comment 10,11,12 make up a doc string code6
f = open('whatever', 'r')
multiline comment 10,11,12 make up a doc string
code7

使用未命名三引号字符串做注释时,存在如下缺点:

  1. 未命名字符串本质上并非注释,而是不生成字节码的语句。因此,需要满足缩进要求(常错点)。
  2. 无法注释掉已包含相同三引号字符串的代码。
  3. IDE的语法高亮会将三引号字符串标记为字符串,而不是注释区。

    此外,大多数IDE均支持选择代码片段,并自动使用单行注释符对选区添加注释。以IDLE(Python GUI)为例,快捷键Alt+3可添加注释,Alt+4可删除注释。因此,建议总是使用#号添加多行注释,而三引号字符串仅用于调试过程中临时性地注释代码块。

二. 代码实现

为同时支持统计C和Python代码,需对CalcLines()和CountFileLines()函数稍作修改。其他函数实现参考C代码统计工具前述系列文章。可以看出,绝大部分实现只需少量或无需修改,这表明前期的函数划分和布局得当。

为求直观,将原先的CalcLines()函数重命名为CalcLinesCh()。接着,实现统计Python脚本行信息的CalcLinesPy()函数:

def CalcLinesPy(line, isBlockComment):
#isBlockComment[single quotes, double quotes]
lineType, lineLen = 0, len(line)
line = line + '\n\n' #添加两个字符防止iChar+2时越界
iChar, isLineComment = 0, False
while iChar < lineLen:
#行结束符(Windows:\r\n; MacOS 9:\r; OS X&Unix:\n)
#不可写为"if line[iChar] in os.linesep"(文件可能来自异种系统)
if line[iChar] == '\r' or line[iChar] == '\n':
break
elif line[iChar] == ' ' or line[iChar] == '\t': #空白字符
iChar += 1; continue
elif line[iChar] == '#': #行注释
isLineComment = True
lineType |= 2
elif line[iChar:iChar+3] == "'''": #单引号块注释
if isBlockComment[0] or isBlockComment[1]:
isBlockComment[0] = False
else:
isBlockComment[0] = True
lineType |= 2; iChar += 2
elif line[iChar:iChar+3] == '"""': #双引号块注释
if isBlockComment[0] or isBlockComment[1]:
isBlockComment[1] = False
else:
isBlockComment[1] = True
lineType |= 2; iChar += 2
else:
if isLineComment or isBlockComment[0] or isBlockComment[1]:
lineType |= 2
else:
lineType |= 1
iChar += 1 return lineType #Bitmap:0空行,1代码,2注释,3代码和注释

相应地,CountFileLines()函数作如下修改:

def CountFileLines(filePath, isRawReport=True, isShortName=False):
fileExt = os.path.splitext(filePath)
if fileExt[1] == '.c' or fileExt[1] == '.h':
CalcLinesFunc = CalcLinesCh
elif fileExt[1] == '.py':
CalcLinesFunc = CalcLinesPy
else:
return isBlockComment = [False]*2 #或定义为全局变量,以保存上次值
lineCountInfo = [0]*4 #[代码总行数, 代码行数, 注释行数, 空白行数]
with open(filePath, 'r') as file:
for line in file:
lineType = CalcLinesFunc(line, isBlockComment)
lineCountInfo[0] += 1
if lineType == 0: lineCountInfo[3] += 1
elif lineType == 1: lineCountInfo[1] += 1
elif lineType == 2: lineCountInfo[2] += 1
elif lineType == 3: lineCountInfo[1] += 1; lineCountInfo[2] += 1
else:
assert False, 'Unexpected lineType: %d(0~3)!' %lineType if isRawReport:
global rawCountInfo
rawCountInfo[:-1] = [x+y for x,y in zip(rawCountInfo[:-1], lineCountInfo)]
rawCountInfo[-1] += 1
elif isShortName:
detailCountInfo.append([os.path.basename(filePath), lineCountInfo])
else:
detailCountInfo.append([filePath, lineCountInfo])

CountFileLines()函数根据后缀判断文件类型并调用相应的统计函数,并扩展isBlockComment列表以存储两种Python块注释(三单引号和三双引号)。除此之外,别无其他修改。

三. 效果验证

将本文的统计实现命名为PCLineCounter.py。首先,混合统计C文件和Python脚本:

E:\PyTest>PCLineCounter.py -d lctest
FileLines CodeLines CommentLines EmptyLines CommentPercent FileName
6 3 4 0 0.57 E:\PyTest\lctest\hard.c
33 19 15 4 0.44 E:\PyTest\lctest\line.c
243 162 26 60 0.14 E:\PyTest\lctest\subdir\CLineCounter.py
44 34 3 7 0.08 E:\PyTest\lctest\subdir\test.c
44 34 3 7 0.08 E:\PyTest\lctest\test.c
27 7 15 5 0.68 E:\PyTest\lctest\总27_代7_注15_空5.py
------------------------------------------------------------------------------------------
397 259 66 83 0.20 <Total:6 Code Files>

然后,统计纯Python脚本,并通过cProfile命令分析性能:

E:\PyTest>python -m cProfile -s tottime PCLineCounter.py -d -b C:\Python27\Lib\encodings_trim > out.txt

截取out.txt文件部分内容如下:

FileLines  CodeLines  CommentLines  EmptyLines  CommentPercent  FileName
157 79 50 28 0.39 __init__.py
527 309 116 103 0.27 aliases.py
50 27 8 15 0.23 ascii.py
80 37 22 21 0.37 base64_codec.py
103 55 24 25 0.30 bz2_codec.py
69 38 10 21 0.21 charmap.py
307 285 262 16 0.48 cp1252.py
39 26 5 8 0.16 gb18030.py
39 26 5 8 0.16 gb2312.py
39 26 5 8 0.16 gbk.py
80 37 22 21 0.37 hex_codec.py
307 285 262 16 0.48 iso8859_1.py
50 27 8 15 0.23 latin_1.py
47 24 10 13 0.29 mbcs.py
83 58 36 17 0.38 palmos.py
175 148 127 18 0.46 ptcp154.py
238 183 28 30 0.13 punycode.py
76 45 15 16 0.25 quopri_codec.py
45 24 8 13 0.25 raw_unicode_escape.py
38 24 4 10 0.14 string_escape.py
49 26 9 14 0.26 undefined.py
45 24 8 13 0.25 unicode_escape.py
45 24 8 13 0.25 unicode_internal.py
126 93 10 23 0.10 utf_16.py
42 23 6 13 0.21 utf_16_be.py
42 23 6 13 0.21 utf_16_le.py
150 113 16 21 0.12 utf_32.py
37 23 5 9 0.18 utf_32_be.py
37 23 5 9 0.18 utf_32_le.py
42 23 6 13 0.21 utf_8.py
117 84 14 19 0.14 utf_8_sig.py
130 70 32 28 0.31 uu_codec.py
103 56 23 25 0.29 zlib_codec.py
------------------------------------------------------------------------------------------
3514 2368 1175 635 0.33 <Total:33 Code Files>
10180 function calls (10146 primitive calls) in 0.168 seconds Ordered by: internal time ncalls tottime percall cumtime percall filename:lineno(function)
3514 0.118 0.000 0.122 0.000 PCLineCounter.py:45(CalcLinesPy)
56 0.015 0.000 0.144 0.003 PCLineCounter.py:82(CountFileLines)
33 0.005 0.000 0.005 0.000 {open}
1 0.004 0.004 0.005 0.005 collections.py:1(<module>)
4028/4020 0.004 0.000 0.004 0.000 {len}
57 0.004 0.000 0.004 0.000 {nt._isdir}
259 0.002 0.000 0.003 0.000 ntpath.py:96(splitdrive)
1 0.002 0.002 0.007 0.007 argparse.py:62(<module>)
1 0.002 0.002 0.168 0.168 PCLineCounter.py:6(<module>)

为避免制作单个exe时体积过大,作者拷贝Lib\encodings目录后,删除该目录内不需要的语言文件并重命名为encodings_trim。

最后,需要指出的是,本文实现未区分三引号块注释与参与赋值或计算的字符串(如s='''devil'''money=10 if '''need''' else 0),也未处理单个单引号或双引号括起的未命名字符串(如"I'm a bad comment")。毕竟,这些并非良好的Python编程风格。而且,实际应用中其实并不要求非常准确地统计代码行和注释行。

Python代码统计工具的更多相关文章

  1. Python实现代码统计工具——终极加速篇

    Python实现代码统计工具--终极加速篇 声明 本文对于先前系列文章中实现的C/Python代码统计工具(CPLineCounter),通过C扩展接口重写核心算法加以优化,并与网上常见的统计工具做对 ...

  2. python 练习(一)代码统计工具的实现

    最近部门成立了一个python学习小组,旨在让大家在做项目中开始成长起来,于是老大就给布置了第一个小任务:代码统计工具,具体的需求如下: 需求: . 能够统计指定目录下C++程序的代码行数. . C+ ...

  3. Python实现C代码统计工具(四)

    目录 Python实现C代码统计工具(四) 标签: Python 计时 持久化 声明 运行测试环境 一. 自定义计时函数 1.1 整个程序计时 1.2 代码片段计时 1.3 单条语句计时 二. 性能优 ...

  4. Python实现C代码统计工具(三)

    目录 Python实现C代码统计工具(三) 声明 一. 性能分析 1.1 分析单条语句 1.2 分析代码片段 1.3 分析整个模块 二. 制作exe Python实现C代码统计工具(三) 标签: Py ...

  5. Python实现C代码统计工具(二)

    目录 Python实现C代码统计工具(二) 声明 一. 问题提出 二. 代码实现 三. 效果验证 Python实现C代码统计工具(二) 标签: Python 代码统计 声明 本文将对<Pytho ...

  6. Python实现C代码统计工具(一)

    目录 Python实现C代码统计工具(一) 声明 一. 问题提出 二. 代码实现 三. 效果验证 四. 后记 Python实现C代码统计工具(一) 标签: Python 代码统计 声明 本文将基于Py ...

  7. C++代码统计工具

    自己前几天写的C++代码统计工具. http://pan.baidu.com/s/17SnnH

  8. Python代码分析工具

    Python代码分析工具:PyChecker.Pylint - CSDN博客 https://blog.csdn.net/permike/article/details/51026156

  9. python代码检查工具pylint 让你的python更规范

    1.pylint是什么? Pylint 是一个 Python 代码分析工具,它分析 Python 代码中的错误,查找不符合代码风格标准(Pylint 默认使用的代码风格是 PEP 8,具体信息,请参阅 ...

随机推荐

  1. Tracking Boost Regulator TYPICAL 5V REGULATION WITH BOOST CONVERTER AND LDO

    Cs5171: Tracking Boost Regulator Adding a current mirror circuit to a typical boost circuit allows t ...

  2. 自定义Directive使用ngModel

    我们知道ngModel是AngularJS中默认的一个Directive,用于数据的双向绑定.通常是这样使用的: <input type="text" ng-model=&q ...

  3. vmware vSphere 5.5的14个新功能

    摘录自:http://www.networkworld.com/slideshow/117304/12-terrific-new-updates-in-vmware-vsphere-55.html#s ...

  4. 使用JavaCV播放视频、摄像头、人脸识别

    一.导入Maven依赖包 <dependencies> <!-- https://mvnrepository.com/artifact/org.bytedeco/javacv-pla ...

  5. python2中在sqlite3中插入中文

    # -*- coding: utf-8 -*- import sqlite3 conn = sqlite3.connect('SWC_Perf_Info.db') cur = conn.cursor( ...

  6. aaronyang的百度地图API之LBS云[把数据丰富显示1/3]

    中国的IT 需要无私分享和贡献的人,一起努力 本篇博客来自地址:http://www.cnblogs.com/AaronYang/p/3673933.html,请支持原创,未经允许不许转载 一.第一步 ...

  7. golang ---tcmalloc浅析

    总体结构 在tcmalloc内存管理的体系之中,一共有三个层次:ThreadCache.CentralCache.PageHeap,如上图所示.分配内存和释放内存的时候都是按从前到后的顺序,在各个层次 ...

  8. Easyui的DataGrid 清除所有勾选的行。

    $('#grid').datagrid('clearChecked')='none';//清除所有勾选的行.

  9. Nginx 配置TCP代理

    Nginx 1.9 版本以后增加了stream模块,可以对tcp,udp请求进行代理和负载均衡了,今天来体验一下首先编译安装过程configure的时候增加选项 --with-stream --wit ...

  10. 在 Visual Studio 生成项目时,会发现一些 dll 并没有被复制到输出目录,导致最终程序的执行错误

    发现与解决 检查了一下项目文件,发现是因为这些 dll 文件的引用其中一个叫做 嵌入互操作类型(EmbedInteropTypes)的属性被设为了 True,此时 复制本地 属性会被强制设为 Fals ...