• Python代码统计工具


    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 + '
    
    ' #添加两个字符防止iChar+2时越界
        iChar, isLineComment = 0, False
        while iChar < lineLen:
            #行结束符(Windows:
    ; MacOS 9:
    ; OS X&Unix:
    )
            #不可写为"if line[iChar] in os.linesep"(文件可能来自异种系统)
            if line[iChar] == '
    ' or line[iChar] == '
    ':
                break
            elif line[iChar] == ' ' or line[iChar] == '	':   #空白字符
                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:PyTestlctesthard.c
    33         19         15            4           0.44            E:PyTestlctestline.c
    243        162        26            60          0.14            E:PyTestlctestsubdirCLineCounter.py
    44         34         3             7           0.08            E:PyTestlctestsubdir	est.c
    44         34         3             7           0.08            E:PyTestlctest	est.c
    27         7          15            5           0.68            E:PyTestlctest总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:Python27Libencodings_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时体积过大,作者拷贝Libencodings目录后,删除该目录内不需要的语言文件并重命名为encodings_trim。

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

  • 相关阅读:
    架构的上层是系统,是系统要素的组织形式
    计数与方法论、哲学
    网络编程--会话层、表示层、应用层
    面向中间件编程--草稿
    泛型:基于类型组合的算法和结构构建---数据结构与算法
    面向对象:消息机制更适合描述;
    类型的连接:实连接、虚连接
    数据库 = filesystem + transcation + dsl + dslengine
    一文看透浏览器架构
    代码的结合性:继承 扩展 组合 变换--swift暗含的四根主线
  • 原文地址:https://www.cnblogs.com/clover-toeic/p/5535182.html
Copyright © 2020-2023  润新知