ساخت مجموعه داده تجزیه و تحلیل فایل با پایتون
سال گذشته راه هایی برای تجزیه و تحلیل تاریخچه و ساختار کد به صورت تصویری ابداع کردم، اما چیز زیادی از آن را مستند نکردم تا با جامعه به اشتراک بگذارم. این مقاله فرآیندی را که من برای ساختن یک فایل CSV استفاده کردم که شامل مسیر، پسوند، پروژه و تعداد خط کد در همه فایلهای منبع پروژه است، بررسی میکند.
توجه: اگر ایده دریافت داده از یک مخزن git برای شما جالب است، مقاله من در مورد استفاده از PyDriller برای استخراج اطلاعات git را بررسی کنید.
اگر بخواهید فایل های کدی را که روی هارد دیسک خود دارید به سرعت تجزیه و تحلیل کنید و یک مجموعه داده برای تجزیه و تحلیل بیشتر و تجسم داده ها بسازید، این کد موثر است.
بسیاری از کدهای پایتون که من به اشتراک می گذارم، به شدت از پاسخ های مختلف StackOverflow با ترفندها و تغییرات خودم وام گرفته شده است. من سعی کردم به همه منابعی که استفاده کردم اعتبار بدهم، اما این کد یک سال پیش ساخته شده است و ممکن است یکی دو منبع مفید را فراموش کرده باشم.
وابستگی ها
من این کد را با استفاده از پایتون 3.8 نوشتم که روی نوت بوک Jupyter با پایتون 3.8 اجرا می شود. با این حال، نوت بوک های Jupyter برای عملکرد این کد مورد نیاز نیستند.
کد متکی بر os
کتابخانه، که استاندارد پایتون است، و همچنین pandas
کتابخانه ای برای تجزیه و تحلیل داده های جدولی
import pandas as pd
import os
کد
آنچه در ادامه میآید، تفکیک متوالی توابع مورد نیاز برای انجام وظیفه استخراج دادهها از کد است.
شناسایی فایل ها
تجزیه و تحلیل فایل ها بدون تعیین اینکه چه فایل هایی باید تجزیه و تحلیل شوند انجام نمی شود.
من این کار را با استفاده از os
کتابخانه listdir
متد به همراه چندین روش مسیر و دایرکتوری دیگر برای پیمایش بازگشتی درخت دایرکتوری که از یک دایرکتوری مشخص شروع می شود.
من همچنین عمداً دایرکتوری هایی را نادیده می گیرم که حاوی تعداد زیادی فایل نادیده گرفته می شوند، مانند دایرکتوری های گزارش، دایرکتوری git. و دایرکتوری های مورد استفاده در محیط توسعه.
# Some directories should be ignored as their output is not helpful
ignored_directories = ['.git', '.vs', 'obj', 'ndependout', 'bin', 'debug']
def get_file_list(dir_path, paths=None):
"""
Gets a list of files in this this directory and all contained directories
"""
files = list()
contents = os.listdir(dir_path)
for entry in contents:
path = os.path.join(dir_path, entry)
if os.path.isdir(path):
# Ignore build and reporting directories
if entry.lower() in ignored_directories:
continue
# Maintain an accurate, but separate, hierarchy array
if paths is None:
p = [entry]
else:
p = paths[:]
p.append(entry)
files = files + get_file_list(path, p)
else:
files.append((path, paths))
return files
تعیین اینکه آیا یک فایل حاوی کد منبع است یا خیر
به منظور تعیین اینکه آیا یک فایل یک فایل کد منبع است و باید در نتایج گنجانده شود، رویکرد سادهای را انتخاب کردم که در آن فقط به فایلهایی با پسوندهای خاص توجه میکنم.
این کد می تواند از فهرست جامع تری یا حتی شاید برخی تحلیل های پیشرفته از محتویات فایل ها بهره مند شود. با این حال، نگاه کردن به برنامه افزودنی برای اهداف من خوب بود و باید برای اکثر خوانندگان به خوبی خدمت کند.
# Common source file extensions. This list is very incomplete. Intentionally not including JSON / XML
source_extensions = [
'.cs',
'.vb',
'.java',
'.r',
'.agc',
'.fs',
'.js',
'.cpp',
'.go',
'.aspx',
'.jsp',
'.do',
'.php',
'.ipynb',
'.sh',
'.html',
'.lua',
'.css'
]
def is_source_file(file_label):
"""
Defines what a source file is.
"""
file, _ = file_label
_, ext = os.path.splitext(file)
return ext.lower() in source_extensions
شمارش خطوط کد
پس از آن، باید بتوانم به سرعت طول یک فایل را تعیین کنم.
من از پاسخ Stack Overflow برای تعیین نحوه خواندن موثر تعداد خطوط در یک فایل با استفاده از کد زیر استفاده کردم:
def count_lines(path):
"""
Reads the file at the specified path and returns the number of lines in that file
"""
def _make_gen(reader):
b = reader(2 ** 16)
while b:
yield b
b = reader(2 ** 16)
with open(path, "rb") as f:
count = sum(buf.count(b"\n") for buf in _make_gen(f.raw.read))
return count
این کد از یک خواننده بافر برای خواندن سریع بافر فایل و برگرداندن تعداد کل استفاده می کند.
Geeks for Geeks پیاده سازی آهسته تر اما قابل درک تر را برای علاقه مندان ارائه می دهد:
def count_lines_simple(path):
with open(path, 'r') as fp:
return sum(1 for line in fp)
دریافت معیارهای فایل
پس از این، من تابعی ساختم که می توانست چندین فایل را بگیرد و لیستی از اشیاء جزئیات را برای همه فایل های موجود در لیست ایجاد کند.
هر شی جزئیات فایل شامل:
- دایرکتوری ریشه ای که تجزیه و تحلیل در آن آغاز شد
- مسیر کامل فایل
- پروژه ای که فایل در آن قرار دارد (دایرکتوری پایه در پروژه ای که فایل در داخل آن قرار دارد)
- مسیر فایل نسبت به
- نام فایل
- پسوند فایل
- تعداد خطوط آن فایل
این کار با حلقه زدن روی همه فایلها و پوشهها در پارامتر فایلهای ورودی، و سپس شمارش خطوط فایلها با استفاده از count_lines
تابع، شمارش هر پوشه، و ساخت اطلاعات مسیر در هر فایل.
هنگامی که همه اطلاعات شناخته شدند، یک شی جزئیات فایل ایجاد می شود و به لیست حاصل اضافه می شود که پس از تجزیه و تحلیل همه فایل ها برگردانده می شود.
def get_file_metrics(files, root):
"""
This function gets all metrics for the files and returns them as a list of file detail objects
"""
results = []
for file, folders in files:
lines = count_lines(file) # Slow as it actually reads the file
_, filename = os.path.split(file)
_, ext = os.path.splitext(filename)
fullpath = ''
if folders != None and len(folders) > 0:
project = folders[0]
for folder in folders[1:]:
if len(fullpath) > 0:
fullpath += "https://dev.to/"
fullpath += folder
else:
project = ''
if len(fullpath) <= 0:
fullpath = '.'
id = root + "https://dev.to/" + project + "https://dev.to/" + fullpath + "https://dev.to/" + filename
file_details = {
'fullpath': id,
'root': root,
'project': project,
'path': fullpath,
'filename': filename,
'ext': ext,
'lines': lines,
}
results.append(file_details)
return results
همه اش را بگذار کنار هم
در نهایت، من یک تابع مرکزی برای شروع تجزیه و تحلیل منبع در یک مسیر مشخص ساختم:
def get_source_file_metrics(path):
"""
This function gets all source files and metrics associated with them from a given path
"""
source_files = filter(is_source_file, get_file_list(path))
return get_file_metrics(list(source_files), path)
این تابع به سایر توابع فراخوانی می کند تا لیستی از جزئیات فایل را ایجاد کند.
میتوانم این روش را با اعلام فهرستی از دایرکتوریهایی که به آنها علاقهمندم، و سپس فراخوانی آن در آن فهرستها، فراخوانی کنم.
# Paths should be one or more directories of interest
paths = ['C:/Dev/MachineLearning/src', 'C:/Dev/MachineLearning/test']
# Pull Source file metrics
files = []
for path in paths:
files.extend(get_source_file_metrics(path))
در اینجا من تمام پروژه های موجود در آن را تجزیه و تحلیل می کنم src
و test
دایرکتوری های مخزن ML.NET من انتخاب کردم که اینها را به عنوان مسیرهای مجزا بگنجانم زیرا آنها دو گروه بندی متفاوت از پروژه ها را در این مخزن نشان می دهند.
ذخیره نتایج
از وقتی که files
لیست پر شده است، به راحتی می توان از آن برای ایجاد یک Pandas DataFrame برای تجزیه و تحلیل جدولی استفاده کرد. DataFrame همچنین یک روش آسان برای سریال کردن داده ها به یک فایل CSV مانند شکل زیر ارائه می دهد:
# Grab the file metrics for source files and put them into a data frame
df = pd.DataFrame(files)
df = df.sort_values('lines', ascending=False)
# Write to a file for other analyses processes
df.to_csv('filesizes.csv')
در نهایت، میتوانیم 5 ردیف اول آن مجموعه داده را با استفاده از پیشنمایش نماییم df.head()
روش.
در مجموعه داده نمونه من، این نتایج زیر را ایجاد می کند:
مراحل بعدی
اکنون که داده های سلسله مراتبی را در یک فایل CSV ذخیره کرده اید، و من یک روش جداگانه برای دریافت اطلاعات از یک مخزن git نشان داده ام، مراحل بعدی شامل ادغام این داده ها با یکدیگر برای تجزیه و تحلیل و تجسم است.
منتظر به روز رسانی های آینده در این مجموعه در Visualizing Code باشید.