使用Python计算目录中的代码行数

29

我有一个项目,想要统计其中的代码行数。是否可以使用 Python 统计包含该项目的文件目录中的所有代码行数?


1
使用os.walk遍历文件和子目录,使用endswith过滤要计数的文件,打开每个文件并使用sum(1 for line in f)计算行数,汇总所有文件的行数。 - wwii
1
这里可能需要提到内置实用程序wc,尽管它不是Python。使用wc -l filename将比答案中建议的纯Python方法更快地返回filename中的行数。 - John
这里有更多关于代码行数的一般信息。 - djvg
12个回答

36

这是我编写的一个函数,用于统计 Python 包中所有代码行数并打印出相关信息。它将会计算所有 .py 文件中的行数。

import os

def countlines(start, lines=0, header=True, begin_start=None):
    if header:
        print('{:>10} |{:>10} | {:<20}'.format('ADDED', 'TOTAL', 'FILE'))
        print('{:->11}|{:->11}|{:->20}'.format('', '', ''))

    for thing in os.listdir(start):
        thing = os.path.join(start, thing)
        if os.path.isfile(thing):
            if thing.endswith('.py'):
                with open(thing, 'r') as f:
                    newlines = f.readlines()
                    newlines = len(newlines)
                    lines += newlines

                    if begin_start is not None:
                        reldir_of_thing = '.' + thing.replace(begin_start, '')
                    else:
                        reldir_of_thing = '.' + thing.replace(start, '')

                    print('{:>10} |{:>10} | {:<20}'.format(
                            newlines, lines, reldir_of_thing))


    for thing in os.listdir(start):
        thing = os.path.join(start, thing)
        if os.path.isdir(thing):
            lines = countlines(thing, lines, header=False, begin_start=start)

    return lines

要使用它,只需传递您想要开始的目录。例如,要计算某个包foo中的代码行数:

countlines(r'...\foo')

这将输出类似于以下内容:

     ADDED |     TOTAL | FILE               
-----------|-----------|--------------------
        5  |        5  | .\__init__.py       
       539 |       578 | .\bar.py          
       558 |      1136 | .\baz\qux.py         

导入 io,并尝试使用 io.open 而不是 open,如果有人在使用 Python 2 时遇到问题。 - MrMesees
啊,好的,一开始我不太明白...我想知道'ADDED'是什么意思?后来我意识到你的'TOTAL'实际上是'ADDED'的累计计数。 - Jeach
4
但是5+539!=578 - nmz787
你可以在 newlines = f.readlines()newlines = len(newlines) 之间添加 newlines = list(filter(lambda l : l.replace(" ", "") not in ['\n', '\r\n'], newlines)) 来排除空行的计数。 - lumpigerAffe
你也可以添加 newlines = list(filter(lambda l : not l.startswith("#"), newlines)) 来删除单行注释。 - lumpigerAffe
start == '.'时,出现了一个小错误。在打印时,扩展名中的'.'被删除了。可以通过将相应的行替换为reldir_of_thing = '.' + thing.replace(begin_start, '') if begin_start != '.' else thing来修复这个问题。对于另一种情况也是一样。 - undefined

28

pygount会显示文件夹中的所有文件,每个文件都有代码行数的计数(不包括文档)

https://pypi.org/project/pygount/

pip install pygount

要列出当前目录的结果,请运行:

pygount ~/path_to_directory

2
这个程序成功地填满了我的16 GB内存,进而使我的电脑无法使用。 - Philipp
6
通过排除我的虚拟环境并仅考虑后缀为.py的文件,我成功地使它工作:pygount --suffix=py --folders-to-skip=venv,venv2 . - Philipp
1
pygount似乎需要在LINUX上运行才能作为命令行工具使用,正如其pypi网页上所解释的那样。它绝对不会像在Windows上描述的那样运行。 - Jeff Winchell

28
作为对pygount答案的补充,他们刚刚添加了一个选项--format=summary,以获取目录中不同文件类型的总行数。
pygount --format=summary ./your-directory

可以输出类似于以下内容

  Language     Code    %     Comment    %
-------------  ----  ------  -------  ------
XML            1668   48.56       10    0.99
Python          746   21.72      150   14.90
TeX             725   21.11       57    5.66
HTML            191    5.56        0    0.00
markdown         58    1.69        0    0.00
JSON             37    1.08        0    0.00
INI              10    0.29        0    0.00
Text              0    0.00      790   78.45
__duplicate__     0    0.00        0    0.00
-------------  ----  ------  -------  ------
Sum total      3435             1007

11

这种感觉有点像一份家庭作业 :-) -- 尽管如此,它是一项值得做的练习,并且Bryce93的格式很好。我认为许多人不太可能使用Python来完成这个任务,因为可以通过几个Shell命令快速完成,例如:

cat $(find . -name "*.py") | grep -E -v '^\s*$|^\s*#' | wc -l

请注意,这些解决方案都没有考虑多行注释(''')。


足够简短好记的!如果像我这样的人不想包含,可以在第一步中使用“cat *.py”。 - Alex Telon
这个在我的Mac OS上“只是工作”的 - 谢谢! - Sean Azlin

3
这里是另一个使用 pathlib 的示例。列出每个文件的相对路径,包括行数,总文件数和总行数。
import pathlib


class LoC(object):
    suffixes = ['.py']
    skip = ['name of dir or file to skip', ...]

    def count(self, path, init=True):
        path = pathlib.Path(path)
        if path.name in self.skip:
            print(f'skipped: {path.relative_to(self.root)}')
            return
        if init:
            self.root = path
            self.files = 0
            self.lines = 0
        if path.is_dir():
            # recursive case
            for item in path.iterdir():
                self.count(path=item, init=False)
        elif path.is_file() and path.suffix in self.suffixes:
            # base case
            with path.open(mode='r') as f:
                line_count = len(f.readlines())
            print(f'{path.relative_to(self.root)}: {line_count}')
            self.files += 1
            self.lines += line_count
        if init:
            print(f'\n{self.lines} lines in {self.files} files')

为了简单明了,我省略了__init__方法。

使用示例:

loc = LoC()
loc.count('/path/to/your/project/directory')

3
from os import listdir
from os.path import isfile, join

def countLinesInPath(path,directory):
    count=0
    for line in open(join(directory,path), encoding="utf8"):
        count+=1
    return count

def countLines(paths,directory):
    count=0
    for path in paths:
        count=count+countLinesInPath(path,directory)
    return count

def getPaths(directory):
    return [f for f in listdir(directory) if isfile(join(directory, f))]

def countIn(directory):
    return countLines(getPaths(directory),directory)

要计算目录中所有文件的代码行数,请调用"countIn"函数,将目录作为参数传递。


2
Python 已经有 len(file.readlines()) 了吧?这只是我知道的其中一种方法。 - OneCricketeer
是的,我认为那也可以行得通,不过这并不需要更多的代码。 - Daniel

3
这是从丹尼尔的答案中推导出来的(虽然进行了足够的重构,以至于这一点并不明显)。那个不会递归遍历子目录,而我需要这种行为。
from os import listdir
from os.path import isfile, isdir, join

def item_line_count(path):
    if isdir(path):
        return dir_line_count(path)
    elif isfile(path):
        return len(open(path, 'rb').readlines())
    else:
        return 0

def dir_line_count(dir):
    return sum(map(lambda item: item_line_count(join(dir, item)), listdir(dir)))

2

如果你想统计你的项目有多少行代码,可以在项目文件夹中创建一个脚本,并将以下内容粘贴到其中:

import os

directory = "[project_directory]"
directory_depth = 100 # How deep you would like to go
extensions_to_consider = [".py", ".css"]  # Change to ["all"] to include all extensions
exclude_filenames = ["venv", ".idea", "__pycache__", "cache"]
skip_file_error_list = True

this_file_dir = os.path.realpath(__file__)

print("Path to ignore:", this_file_dir)
print("=====================================")
def _walk(path, depth):
    """Recursively list files and directories up to a certain depth"""
    depth -= 1
    with os.scandir(path) as p:
        for entry in p:

            skip_entry = False
            for fName in exclude_filenames:
                if entry.path.endswith(fName):
                    skip_entry = True
                    break

            if skip_entry:
                print("Skipping entry", entry.path)
                continue

            yield entry.path
            if entry.is_dir() and depth > 0:
                yield from _walk(entry.path, depth)

print("Caching entries")
files = list(_walk(directory, directory_depth))
print("=====================================")

print("Counting Lines")
file_err_list = []
line_count = 0
len_files = len(files)
for i, file_dir in enumerate(files):

    if file_dir == this_file_dir:
        print("=[Rejected file directory", file_dir, "]=")
        continue

    if not os.path.isfile(file_dir):
        continue

    skip_File = True
    for ending in extensions_to_consider:
        if file_dir.endswith(ending) or ending == "all":
            skip_File = False

    if not skip_File:
        try:
            file = open(file_dir, "r")
            local_count = 0
            for line in file:
                if line != "\n":
                    local_count += 1
            print("({:.1f}%)".format(100*i/len_files), file_dir, "|", local_count)
            line_count += local_count
            file.close()
        except:
            file_err_list.append(file_dir)
            continue
print("=====================================")
print("File Count Errors:", len(file_err_list))
if not skip_file_error_list:
    for file in file_err_list:
        print(file_err_list)

print("=====================================")
print("Total lines |", line_count)

可能有更快更高效的方法来做这件事,但这是一个不错的开始。

变量信息

directory 是您想要计算的项目目录

directory_depth 是项目基础结构内部的深度,例如深度为3表示它只会扫描以下深度:

  • project_dir
    • sub_dir
      • sub2_dir

extensions_to_consider 是要计算代码的文件扩展名。如果您只想计算 .py 文件,则设置 extensions_to_consider = [".py"]

exclude_filenames 是一个文件名(和目录)数组,您不希望考虑脚本计数代码。

skip_file_error_list 是一个布尔变量。如果您希望查看计数时的所有错误打印输出,则设置为 True。否则设置为 False。

如何运行

使用 Python 编译器运行脚本。 在终端中运行

python path_to_file.py

或者

python3 path_to_file.py


目前你的回答不够清晰,请编辑并添加更多细节以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

1

使用Radon

python3 -mpip install radon

radon raw -s pkg_dir/

** Total **
    LOC: 2994
    LLOC: 1768
    SLOC: 1739
    Comments: 71
    Single comments: 29
    Multi: 818
    Blank: 408
    - Comment Stats
        (C % L): 2%
        (C % S): 4%
        (C + M % L): 30%

它还将计算圈复杂度

a@debian:~/build/clean/scte35-threefive$ radon cc  -a threefive
threefive/base.py
    M 61:4 SCTE35Base.kv_clean - A
    M 85:4 SCTE35Base.load - A
    M 95:4 SCTE35Base._chk_var - A
    C 9:0 SCTE35Base - A
    M 34:4 SCTE35Base.as_hms - A
    M 79:4 SCTE35Base._chk_nbin - A
    M 17:4 SCTE35Base.__repr__ - A
    M 20:4 SCTE35Base.as_90k - A
    M 27:4 SCTE35Base.as_ticks - A
    M 48:4 SCTE35Base.get - A
    M 54:4 SCTE35Base.get_json - A
threefive/bitn.py
    C 9:0 BitBin - A
    M 30:4 BitBin.as_int - A
    M 47:4 BitBin.as_charset - A
    C 99:0 NBin - A
    M 133:4 NBin.add_int - A
    M 170:4 NBin.reserve - A

      ..... 
246 blocks (classes, functions, methods) analyzed.
Average complexity: A (1.9024390243902438)

0

基于Bryce93的答案,使用code_only选项从行计数中排除注释、文档字符串和空行:

import os

def countlines(rootdir, total_lines=0, header=True, begin_start=None,
               code_only=True):
    def _get_new_lines(source):
        total = len(source)
        i = 0
        while i < len(source):
            line = source[i]
            trimline = line.lstrip(" ")

            if trimline.startswith('#') or trimline == '':
                total -= 1
            elif '"""' in trimline:  # docstring begin
                if trimline.count('"""') == 2:  # docstring end on same line
                    total -= 1
                    i += 1
                    continue
                doc_start = i
                i += 1
                while '"""' not in source[i]:  # docstring end
                    i += 1
                doc_end = i
                total -= (doc_end - doc_start + 1)
            i += 1
        return total

    if header:
        print('{:>10} |{:>10} | {:<20}'.format('ADDED', 'TOTAL', 'FILE'))
        print('{:->11}|{:->11}|{:->20}'.format('', '', ''))

    for name in os.listdir(rootdir):
        file = os.path.join(rootdir, name)
        if os.path.isfile(file) and file.endswith('.py'):
            with open(file, 'r') as f:
                source = f.readlines()

            if code_only:
                new_lines = _get_new_lines(source)
            else:
                new_lines = len(source)
            total_lines += new_lines

            if begin_start is not None:
                reldir_of_file = '.' + file.replace(begin_start, '')
            else:
                reldir_of_file = '.' + file.replace(rootdir, '')

            print('{:>10} |{:>10} | {:<20}'.format(
                    new_lines, total_lines, reldir_of_file))

    for file in os.listdir(rootdir):
        file = os.path.join(rootdir, file)
        if os.path.isdir(file):
            total_lines = countlines(file, total_lines, header=False,
                                     begin_start=rootdir, code_only=code_only)
    return total_lines

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接