【Python】72行实现代码行数统计,简单实用!

0. 前言

最近突然想知道自己总共写了多少行代码,于是做了这样一个小工具……

1. 准备工作

先考虑一下希望得到的效果:

Language(语言) Lines(代码行数) Size(代码文件总大小) Files(代码文件总数)
A 12345 300 KB 193
B 2345 165 KB 98
如上,程序输出一个表格,将代码行数作为关键字排序。
代码框架:
# -*- encoding: utf-8 -*-
import ...# 代码行数计数类
class CodeLinesCounter(object):SIZES = [('B', 1), ('KB', 1024), ('MB', 1024**2), ('GB', 1024**3), ('TB', 1024**4)]def __init__(self, languages):self._languages = languages # 语言(dict,{文件后缀名:语言})self._codelines = {suffix: (0, 0, 0) for suffix in languages} # 统计结果,{后缀名:(行数,大小,文件数)}self._successful = self._error = 0 # 记录成功、失败文件个数# @param directory: 要扫描的目录# @param log: 是否打印日志def scan(self, directory, log=False):if log: print('Scanning', directory)passdef report(self): # 输出结果passcounter = CodeLinesCounter(languages={'py': 'Python', 'c': 'C', 'cpp': 'C++', 'java': 'Java', 'js': 'JavaScript', 'html': 'HTML', 'css': 'CSS', 'txt': 'Plain text'}) # 创建CodeLinesCounter实例
counter.scan('E:/') # 扫描E盘(注意不能用'E:')
counter.report() # 输出结果

完成,下面正式进入主要部分

2. 统计

2.1 文件扫描

首先,我们需要获取根目录下的文件列表。这可以用os.walk实现:
os.walk(rootdir)返回一个游走器(可迭代),包含根目录下每个子目录的文件及目录列表。我们来看一个例子:
有一文件夹Folder如下:

Folder
|   file1
|   Folder1
|       file2
|       file3
|   Folder2|   file4|   Folder3

运行如下代码:

import osfor root, dirs, files in os.walk('Folder'):print(root, dirs, files)

则输出如下:

Folder					['Folder1', 'Folder2']	['file1']
Folder\Folder1			[]						['file2', 'file3']
Folder\Folder2			['Folder3']				['file4']
Folder\Folder2\Folder3	[]						[]

其中第一项是当前的根目录,第二项为目录下的目录列表,第三项则为当前的文件列表。
因此,我们可以编写如下代码:

# -*- encoding: utf-8 -*-
from os.path import join, getsize, abspath
from os import walkclass CodeLinesCounter(object):SIZES = [('B', 1), ('KB', 1024), ('MB', 1024**2), ('GB', 1024**3), ('TB', 1024**4)]def __init__(self, languages):self._languages = languagesself._results = {suffix: (0, 0, 0) for suffix in languages}self._successful = self._error = 0def scan(self, directory, log=False):if log: print('Scanning', directory)try:for root, _, files in walk(abspath(directory)):for filename in files:suffix = filename[filename.rfind('.') + 1:]filename = join(root, filename)if suffix in self._results:lines, size, numFiles = self._results[suffix]lines += 1 # 暂不统计,先按一行计算numFiles += 1size += getsize(filename) # getsize返回文件大小(字节)self._results[suffix] = (lines, size, numFiles)if log: print(filename)except KeyboardInterrupt:print('\nUser stopped operation')else:if log: print('Scan finished')def report(self):print('Language\tLines\tSize\tFiles')for suffix, (lines, size, files) in sorted(self._results.items(), key=lambda x: x[1], reverse=True):print(self._languages[suffix], lines, self.__format_size(size), files, sep='\t')# 单位转换def __format_size(self, bytes):for suffix, size in self.SIZES:if bytes < size * 1024:return '%.2f %s' % (bytes / size, suffix)return '%.2f %s' % (bytes / self.SIZES[-1][1], 2, self.SIZES[-1][0])counter = CodeLinesCounter(languages={'py': 'Python', 'c': 'C', 'cpp': 'C++', 'java': 'Java', 'js': 'JavaScript', 'html': 'HTML', 'css': 'CSS', 'txt': 'Plain text'})
counter.scan('E:/')
counter.report()

运行结果应类似于下面这样(手动整理了一下):

Language        Lines   Size    		Files
C++     		667     671.51 KB       667
Python  		317     981.01 KB       317
HTML    		38      466.52 KB       38
Plain text    34      90.69 KB        34
JavaScript    19      1.43 MB			19
CSS     		9       341.04 KB       9
C       		2       20.45 KB        2
Java    		1       676.00 B        1

好,下面来到行数统计部分(表格输出后面会介绍)。

2.2 行数统计

众所周知,空行不应该算在代码行数中。因此,统计时需忽略空行。先写上如下代码(替换掉刚才的23行):

with open(filename, 'r', encoding='utf-8') as f: # utf-8编码打开文件for line in f:if line and not line.isspace(): # 去掉空行lines += 1

但是,正当我们兴致勃勃地运行时——

Traceback (most recent call last):...File "...\lib\codecs.py", line 322, in decode(result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb5 in position 355: invalid start byte

程序报错UnicodeDecodeError,分析后发现原因是部分文件使用了GBK编码,而utf-8编码无法正确打开,因此造成错误。
我们再次改进程序,使其尝试两种编码:

try:ln = 0with open(filename, 'r', encoding='utf-8') as f:for line in f:if line and not line.isspace():ln += 1
except UnicodeDecodeError: # 尝试使用GBK编码打开try:ln = 0with open(filename, 'r', encoding='gbk') as f:for line in f:if line and not line.isspace():ln += 1except:print(filename, '[Error: unknown encoding]')self._error += 1else:lines += ln
except Exception as e:print(filename, '[Unknown error: %s]' % e)self._error += 1continue
lines += ln
if log: print(f'{filename} [{ln}]')
self._successful += 1

这次,我们得到了正确的结果:

Language        Lines   Size    		Files
C++     		35595   671.51 KB       667
JavaScript    24485   1.43 MB 		19
Python  		24130   982.16 KB       317
CSS     		8203    341.04 KB       9
HTML    		6138    466.52 KB       38
Plain text    741     90.69 KB        34
C       		557     20.45 KB        2
Java    		29      676.00 B        1

现在仅剩最后一步了——制表。

3. 制表

python输出表格可以使用PrettyTable库。具体用法如下:

# -*- encoding: utf-8 -*-
from os.path import join, getsize, abspath
from os import walk
from prettytable import PrettyTableclass CodeLinesCounter(object):SIZES = [('B', 1), ('KB', 1024), ('MB', 1024**2), ('GB', 1024**3), ('TB', 1024**4)]def __init__(self, languages):self._languages = languagesself._results = {suffix: (0, 0, 0) for suffix in languages}self._successful = self._error = 0def scan(self, directory, log=False):if log: print('Scanning', directory)try:for root, _, files in walk(abspath(directory)):for filename in files:suffix = filename[filename.rfind('.') + 1:]filename = join(root, filename)if suffix in self._results:lines, size, numFiles = self._results[suffix]numFiles += 1size += getsize(filename)try:ln = 0with open(filename, 'r', encoding='utf-8') as f:for line in f:if line and not line.isspace():ln += 1except UnicodeDecodeError: # Try 'gbk' encodingtry:ln = 0with open(filename, 'r', encoding='gbk') as f:for line in f:if line and not line.isspace():ln += 1except:print(filename, '[Error: unknown encoding]')self._error += 1else:lines += lnexcept Exception as e:print(filename, '[Unknown error: %s]' % e)self._error += 1continuelines += lnif log: print(f'{filename} [{ln}]')self._successful += 1self._results[suffix] = (lines, size, numFiles)elif log:print(filename, '[None]')except KeyboardInterrupt:print('\nUser stopped operation')else:if log: print('Scan finished')def report(self):table = PrettyTable(['Language', 'Lines', 'Size', 'Files'], title=f'Scan result (OK {self._successful}, Error {self._error})') # 创建PrettyTable实例,添加标题for suffix, (lines, size, files) in sorted(self._results.items(), key=lambda x: x[1], reverse=True):table.add_row([self._languages[suffix], lines, self.__format_size(size), files]) # 添加行print(table) # 输出def __format_size(self, bytes):for suffix, size in self.SIZES:if bytes < size * 1024:return '%.2f %s' % (bytes / size, suffix)return '%.2f %s' % (bytes / self.SIZES[-1][1], 2, self.SIZES[-1][0])counter = CodeLinesCounter(languages={'py': 'Python', 'c': 'C', 'cpp': 'C++', 'java': 'Java', 'js': 'JavaScript', 'html': 'HTML', 'css': 'CSS', 'txt': 'Plain text'})
counter.scan('E:/')
counter.report()

运行结果:

+----------------------------------------+
|     Scan result (OK 1087, Error 0)     |
+------------+-------+-----------+-------+
|  Language  | Lines |    Size   | Files |
+------------+-------+-----------+-------+
|    C++     | 35595 | 671.51 KB |  667  |
| JavaScript | 24485 |  1.43 MB  |   19  |
|   Python   | 24130 | 982.16 KB |  317  |
|    CSS     |  8203 | 341.04 KB |   9   |
|    HTML    |  6138 | 466.52 KB |   38  |
| Plain text |  741  |  90.69 KB |   34  |
|     C      |  557  |  20.45 KB |   2   |
|    Java    |   29  |  676.00 B |   1   |
+------------+-------+-----------+-------+

4. 总结

最终代码(无注释):

# -*- encoding: utf-8 -*-
from os.path import join, getsize, abspath
from os import walk
from prettytable import PrettyTableclass CodeLinesCounter(object):SIZES = [('B', 1), ('KB', 1024), ('MB', 1024**2), ('GB', 1024**3), ('TB', 1024**4)]def __init__(self, languages):self._languages = languagesself._results = {suffix: (0, 0, 0) for suffix in languages}self._successful = self._error = 0def scan(self, directory, log=False):if log: print('Scanning', directory)try:for root, _, files in walk(abspath(directory)):for filename in files:suffix = filename[filename.rfind('.') + 1:]filename = join(root, filename)if suffix in self._results:lines, size, numFiles = self._results[suffix]numFiles += 1size += getsize(filename)try:ln = 0with open(filename, 'r', encoding='utf-8') as f:for line in f:if line and not line.isspace():ln += 1except UnicodeDecodeError: # Try 'gbk' encodingtry:ln = 0with open(filename, 'r', encoding='gbk') as f:for line in f:if line and not line.isspace():ln += 1except:print(filename, '[Error: unknown encoding]')self._error += 1else:lines += lnexcept Exception as e:print(filename, '[Unknown error: %s]' % e)self._error += 1continuelines += lnif log: print(f'{filename} [{ln}]')self._successful += 1self._results[suffix] = (lines, size, numFiles)elif log:print(filename, '[None]')except KeyboardInterrupt:print('\nUser stopped operation')else:if log: print('Scan finished')def report(self):table = PrettyTable(['Language', 'Lines', 'Size', 'Files'], title=f'Scan result (OK {self._successful}, Error {self._error})')for suffix, (lines, size, files) in sorted(self._results.items(), key=lambda x: x[1], reverse=True):table.add_row([self._languages[suffix], lines, self.__format_size(size), files])print(table)def __format_size(self, bytes):for suffix, size in self.SIZES:if bytes < size * 1024:return '%.2f %s' % (bytes / size, suffix)return '%.2f %s' % (bytes / self.SIZES[-1][1], 2, self.SIZES[-1][0])counter = CodeLinesCounter(languages={'py': 'Python', 'c': 'C', 'cpp': 'C++', 'java': 'Java', 'js': 'JavaScript', 'html': 'HTML', 'css': 'CSS', 'txt': 'Plain text'})
counter.scan('E:/')
counter.report()

后期改进:

  • 增加正则表达式忽略文件
  • matplotlib绘图
  • PyQt5GUI
  • ……(欢迎提出宝贵的意见!)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/794190.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AtCoder Beginner Contest 205 A~E 题解

A - kcal 题目大意 我们有一种每\(100\)毫升含有\(A\)千卡热量的饮料。\(B\)毫升的这种饮料含有多少千卡热量? \(0\le A, B\le 1000\) 输入格式 \(A~B\) 输出格式 输出\(B\)毫升这种饮料包含的的千卡数。最大允许浮点数精度误差\(10^{-6}\)。 样例\(A\) \(B\) 输出\(45\) \(20…

AtCoder Beginner Contest 196 A~E 题解

A - Difference Max 题目大意 给定四个整数\(a,b,c\)和\(d\)。 我们要选择两个整数\(x\)和\(y\)(\(a\le x\le b\);\(c\le y\le d\))。输出最大的\(x-y\)。 \(-100\le a\le b\le 100\) \(-100\le c\le d\le 100\) 输入格式 \(a~~b\) \(c~~d\) 输出格式 输出最大的\(x-y\)。 样…

AtCoder Beginner Contest 173 A~D 题解

A - Payment 题目大意 如果使用价值\(1000\)元的纸币(假设有)支付\(N\)元,服务员会找多少钱? \(1\le N\le 10000\) 输入格式 \(N\) 输出格式 一行,即服务员找的钱数。 样例输入 输出1900 1003000 0分析 特判: 如果\(N\)除以\(1000\)能整除,那么不需要找钱,输出\(0\); …

AtCoder Beginner Contest 188 A~D 题解

A - Three-Point Shot 题目大意 有两个球队,分别得到\(X\)分和\(Y\)分,问得分较少的球队能否在获得三分后超越对方。 \(0\le X,Y\le 100\) \(X \ne Y\) \(X\)和\(Y\)都是整数。 输入格式 \(X~Y\) 输出格式 如果能,输出Yes;否则,输出No。 样例X Y 输出3 5 Yes分析 这个不用…

Eclipse安装包下载慢解决方法

最近开始学习Java,使用经典的Eclipse IDE,结果发现下载太慢……问题分析Eclipse的下载依赖于其他镜像,(在我这里)默认为朝鲜的镜像(可能在不同电脑中默认不同):点击Select Another Mirror:选择中国的镜像:

程序无法启动,因为您的计算机缺少msvcr71.dll。

背景 打开CrystalTile2这个软件出现此提示。 解决 有很多解决办法,最简单粗暴也是见效最快的就是直接从网上下载dll文件放到对应位置。 下载 https://msvcr71.dll-box.com/zh/自己存了一份,以免网址失效(虽然按道理来说一般不会失效)。 https://www.123pan.com/s/EhW3jv-IW…

Panasonic Programming Contest 2020 C (Sqrt Inequality) 题解

题目大意 输入三个整数\(a\),\(b\),\(c\),如果 \(\sqrt a + \sqrt b < \sqrt c\) 成立,输出Yes,否则输出No。 样例 输入 #1 2 3 9输出 #1 No\(\sqrt 2 + \sqrt 3 < \sqrt 9\) 不成立。 输入 #2 2 3 10输出 #2 Yes\(\sqrt 2 + \sqrt 3 < \sqrt 10\) 成立。 分析 错…

CodeForces Round #621 ABC (1307A+1307B+1307C) 题解

A. Cow and Haybales 题面 The USA Construction Operation (USACO) recently ordered Farmer John to arrange a row of n haybale piles on the farm. The \(i\)-th pile contains \(a_i\) haybales. However, Farmer John has just left for vacation, leaving Bessie all o…

Python函数之*[参数名]和**[参数名]的用处

一、*[参数名] 调用 合法调用 普通调用 *参数名一般写成*args, 如: def func(*args):print(args)可以试着调用func: >>> func(1) (1,) >>> func() () >>> func(1, 2, 3) (1, 2, 3) >>> func(dict(), set(), str(), int()) ({}, set(), ,…

discuz3.4文件包含漏洞

首先查看修复:可以看到新增代码preg_match("/^[\w-]+\.php$/i", $parse[path])) 来验证path是否为php文件,这个应该是修复路径遍历导致的文件读取漏洞。还有require ./.$_ENV[curapp]..php;这里应该是另外一个漏洞,因为$parse[path]和$_ENV[curapp]没有关联。 然后…

河道漂浮物识别检测系统

河道漂浮物识别检测系统依据智能视频分析,视频图像信息内容自动分析和识别,不用人工操纵;检测漂浮物(塑料泡沫、包装袋、堤岸漂浮植物种类等)生物群落等),精确提交检测结果,储存有关信息,便捷查验管理。河道漂浮物识别检测系统实时监控河面状况,对违法行为开展警报、…

厨师帽厨师服口罩穿戴人脸识别-智慧食安监督系统

厨师帽厨师服口罩穿戴人脸识别就是指在监管前提下提早设定查验区域,当规定区域有不戴厨师帽不穿戴厨师服或者口罩,系统自动会警报。厨师帽厨师服口罩穿戴人脸识别-智慧食安监督系统根据视频智能分析商品,在数据分析系统优化计算方法服务中,扩展了非厨房工作人员 进入后厨以…