Подсчет строк кода в каталоге с использованием Python
У меня есть проект, строки кода которого я хочу посчитать. Можно ли посчитать все строки кода в файловой директории, содержащей проект, используя Python?
12 ответов
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", передав каталог в качестве параметра.
Вот функция, которую я написал для подсчета всех строк кода в пакете 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
В дополнение к 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
https://pypi.org/project/pygount/
pip install pygount
Чтобы отобразить текущий каталог, выполните:
pygount
Это немного напоминает домашнее задание:-) - тем не менее, это полезное упражнение, и форматирование Bryce93 приятно. Я думаю, что многие вряд ли будут использовать Python для этого, учитывая, что это можно сделать быстро с помощью пары команд оболочки, например:
cat $(find . -name "*.py") | grep -E -v '^\s*$|^\s*#' | wc -l
Обратите внимание, что ни одно из этих решений не учитывает многострочность ('''
) Комментарии.
Вот еще один, используя
pathlib
. Перечисляет отдельные (относительные) пути к файлам с указанием количества строк, общего количества файлов и общего количества строк.
import pathlib
class LoC(object):
suffixes = ['.py']
def count(self, path: pathlib.Path, init=True):
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=pathlib.Path('/path/to/your/project/directory'))
Это вытекает из ответа Даниэля (хотя достаточно рефакторинг, чтобы это не было очевидно). Это не повторяется через подкаталоги, что я и хотел.
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)))
If you want to count how many lines are in your project, create a script inside of your project folder and paste the following into it:
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)
There's probably faster and more efficient ways to do this, but this is a nice start.
Используйте радон
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)
Я только что сделал вариант ответа @Bryce93 для проектов python + flask... запустил несколько сводных таблиц для итогового файла .csv и тому подобного (я вручную пометил файлы как "активные" в нисходящем направлении)... ваше здоровье
import os
import pandas as pd
def countlines(start, begin_start=None):
global files
for thing in os.listdir(start):
thing = os.path.join(start, thing)
if os.path.isfile(thing):
if thing.endswith('.py') or thing.endswith('.html'):
with open(thing, 'r') as f:
lines = f.readlines()
count = len([l for l in lines if not l.strip().startswith('#')])
functions, classes, comments = 0, 0, 0
if thing.endswith('.py'):
functions = len([
l for l in lines if l.strip().startswith('def ')
and l.strip().endswith('):')
])
classes = len([
l for l in lines if l.strip().startswith('class ')
and l.strip().endswith('):')
])
comments = len([l for l in lines if l.strip().startswith('#')])
language = 'python'
elif thing.endswith('.html'):
comments = len([l for l in lines if l.strip().startswith('<!--')])
language = 'jinja'
else:
raise Exception(thing)
path = str(thing)
folder = '/'.join(path.split(repo)[-1].split('/')[:-1])
files.append({
'path': path,
'repo': repo,
'language': language,
'filetype': thing.split('.')[-1],
'folder': folder,
'filename': thing.split('/')[-1],
'lines': count,
'functions': functions,
'classes': classes,
'comments': comments,
})
for thing in os.listdir(start):
thing = os.path.join(start, thing)
if os.path.isdir(thing):
countlines(thing, begin_start=start)
files = []
repo = '<repo1>'
countlines('<path>/<repo1>')
master = pd.DataFrame(files)
files = []
repo = '<repo2>'
countlines('<path>/<repo2>')
master = pd.concat([master, pd.DataFrame(files)], ignore_index=False, sort=False)
master['active'] = False
master.sort_values(by=['repo', 'folder', 'language', 'filename'])
master.to_csv('../<blah>.csv')
Я создал простую рекурсивную функцию, которая печатает общий LOC файлов в каждой папке и в конце возвращает общий LOC каталога, который вы указали изначально:
import os
def find_loc(cd = os.curdir):
listdir = os.listdir(cd)
if len(listdir) == 0:
return 0
loc = 0;
files = []
folders = []
next_dirs = []
for x in listdir:
path = os.path.join(cd, x)
if os.path.isfile(path):
files.append(x)
file = open(path, 'r')
for line in file:
if line == '':
continue
loc += 1
elif os.path.isdir(path):
folders.append(x)
next_dirs.append(path)
print(f'cd: {cd}')
print(f'files ({len(files)}): {files}')
print(f'dirs: {folders}')
print(f'loc: {loc}\n')
for next_dir in next_dirs:
loc += find_loc(next_dir)
return loc
find_loc()
cd
параметр вfind_loc
Функция — это каталог, из которого вы хотите начать подсчет LOC.
На основе ответа 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