Я написал функцию для получения общего размера и общего количества файлов/подкаталогов всего диска или определенной папки. Рекурсивная функция была очень простой:
# Make the python dirSize function a non recursive one:
from pathlib import Path
obj = {}
def dirSize(dir: Path, size = 0, files = 0, dirs = 0):
for sub_dir in dir.glob('*'):
if sub_dir.is_dir():
dirs += 1
res_size, res_files, res_dirs = dirSize(sub_dir)
size += res_size
files += res_files
dirs += res_dirs
elif sub_dir.is_file():
files += 1
size += sub_dir.stat().st_size
obj[dir.as_posix()] = {
'size': size,
'files': files,
'dirs': dirs,
}
return size, files, dirs
dirSize(Path('~/Documents').expanduser())
#Line above is for tests. Final code should run in elevated cmd:
#dirSize(Path('C:/'))
with open(Path('~/Desktop/results.tsv').expanduser().as_posix(), 'w') as file:
file.write('Directory\tBytes\tFiles\tDirs\n')
for key in sorted(obj):
dir = obj[key]
file.write(key)
file.write('\t')
file.write(str(dir['size']))
file.write('\t')
file.write(str(dir['files']))
file.write('\t')
file.write(str(dir['dirs']))
file.write('\n')
Я пытался использовать ИИ, чтобы преобразовать его в нерекурсивную функцию, но ИИ, кажется, игнорирует, что эти строки представляют собой сумму снизу вверх, а не простую сумму:
size += res_size
files += res_files
dirs += res_dirs
Итак, я попробовал стековый цикл, как в алгоритме Дейкстры, но оказалось, что я не смог найти простого решения для вычисления суммы снизу вверх. В качестве альтернативы я мог бы добавить свойства level/parent/child в переменную obj, чтобы получить сумму снизу вверх после обработки очереди. Но это кажется очень громоздким. Есть ли более простой способ добиться этого?
Отвечаем на вопросы, заданные ниже:
также важно отметить, что хвостовая рекурсия не имеет особого статуса в CPython (нет оптимизации хвостового вызова)
В чем проблема с рекурсивной функцией? Если глубина каталогов не превышает 1000 уровней, вам не следует сталкиваться с ограничением рекурсии Python.
«Но не все разработчики умеют работать с рекурсией» << Почему бы и нет?
Я бы ответил, что единственное, что потенциально можно назвать слишком сложным в вашей функции, это то, что она делает две вещи: (1) ходит по дереву каталогов (2) накапливает статистику; а затем я бы предложил разделить его на две функции: одну, которая ходит, и другую, которая принимает статистику. Но тогда функция, которая ходит, будет просто os.walk
и, видимо, вы не хотите ее использовать?
Ваша мысль об использовании стекового подхода была верной — этого можно добиться, создав стек с записями кортежа. Каждый кортеж содержит обрабатываемый подкаталог, а также имя родительского каталога, что позволяет соответствующим образом обновлять статистику родительского каталога. Чтобы обновить статистику родительского элемента после того, как все его дочерние статистические данные были вычислены, мы можем добавить родительскую запись обратно в стек для обработки во второй раз. Вот пример итерационного подхода:
obj = {}
def dirSizeIterative(dir: Path):
dir_stack = [(dir, 'root')]
obj['root'] = {'size': 0, 'files': 0, 'dirs': 0}
while dir_stack:
sub_dir, parent_dir_name = dir_stack.pop(0)
parent_info = obj[parent_dir_name]
if sub_dir.is_dir():
# if this exists, then we are processing this dir for the second time, after children have all been computed
if sub_info := obj.get(sub_dir.as_posix()):
parent_info['size'] += sub_info['size']
parent_info['files'] += sub_info['files']
parent_info['dirs'] += sub_info['dirs']
continue
sub_info = {'size': 0, 'files': 0, 'dirs': 0}
obj[sub_dir.as_posix()] = sub_info
parent_info['dirs'] += 1
# add this dir back to the stack for processing after children have been computed
dir_stack.insert(0, (sub_dir, parent_dir_name))
for child in sub_dir.glob('*'):
dir_stack.insert(0, (child, sub_dir.as_posix()))
elif sub_dir.is_file():
parent_info['files'] += 1
parent_info['size'] += sub_dir.stat().st_size
del obj['root']
dirSizeIterative(Path('~/Documents').expanduser())
Я проверил, ваша функция работает нормально! Это то, что мне очень нужно, вы очень помогли. Хитрость, которую я не смог найти, заключалась в использовании временного «корня» для начала взаимодействия с использованием очереди.
Пожалуйста, проверьте мой ответ ниже. Подход стека, как у Дейкстры, — не самое простое решение, а просто создание структуры путем перемещения по дереву каталогов. Итеративная версия превосходит рекурсивную. Еще раз спасибо!
Но не все разработчики умеют работать с рекурсией.
Контрапункт: возможно, все разработчики умеют работать с рекурсией, но ваша рекурсивная функция слишком сложна, потому что:
Я предлагаю разделить код на две функции:
Что-то вроде этого:
from collections import defaultdict
def walk(dir: Path):
'''
Walk directory recursively and yield (node, parent_name) pairs
'''
for sub_dir in dir.glob('*'):
if sub_dir.is_dir():
yield from walk(sub_dir)
yield (sub_dir, dir.as_posix())
def dirSize(dir: Path):
obj = defaultdict(lambda (): {'size': 0, 'files': 0, 'dirs': 0})
for (node, parent_name) in walk(dir):
size, files = (node.stat().st_size, 1) if node.is_file() else (0, 0)
dirs = 1 if node.is_dir() else 0
node_dict = obj[node.as_posix()]
parent_dict = obj[parent_name]
for d in (node_dict, parent_dict):
d['size'] += size
d['files'] += files
d['dirs'] += dirs
return obj
Это не то, о чем спрашивали, но на самом деле это очень полезно, поскольку значительно упрощает функцию.
Основываясь на ответе @ awarrier99, я придумал эту версию, которая немного упрощает логику. Я надеюсь, что это поможет будущим исследователям:
def dirSizeIterative(dir: Path):
dirs = [(dir, {'size': 0, 'files': 0, 'dirs': 0}, None)]
i = 0
#step 1, read dir/files info and add to array
while i < len(dirs):
dir, info, _ = dirs[i]
i += 1
for sub_dir in dir.glob('*'):
if sub_dir.is_dir():
info['dirs'] += 1
dirs.append((sub_dir, {'size': 0, 'files': 0, 'dirs': 0}, info))
elif sub_dir.is_file():
info['files'] += 1
info['size'] += sub_dir.stat().st_size
#step 2, bottom up sum using the parent
for _, info, parent in dirs[-1:0:-1]:
parent['size'] += info['size']
parent['files'] += info['files']
parent['dirs'] += info['dirs']
return {dir.as_posix(): info for dir, info, _ in dirs}
почему бы тебе просто не использовать
os.walk
?