# Directory size checker

So I semi-wrote directory size checker by copying some parts from Stackoverflow.

It works as follows:

1. Select a directory you want to check the size of subdirectories inside.
2. It goes scans all the directories inside the chosen directory. If there are also just files, that's not a problem the recursive function can deal with that as well.
3. It goes through every subdirectory and checks for the file size. If there's a permission problem, it returns -1 (this way we can easily tell which directories caused acces issues).
4. The directory name and it's "mass" gets stored in a dictionary.
5. The whole dictionary gets sorted.
6. Items that are +1GB get the "GB" tag, while the ones between 0 and 1 get the "MB" tag, just for better precision. (I like to change between directories faster, so I added an option to give "r" as an input to scan for a second time. Closing the script and reopening it takes time imo)

Now I can see which folder is the biggest and which ones are empty, I don't know why but in Windows I need to go 1:1 and check each folder size individually, which I dislike a lot. This way I can check folders that contain hundreds of gigabytes in seconds.

I would like to know what I could have done better or what was obnoxious.

import os
from tkinter import *

def Main():
Tk().withdraw()  # use to hide tkinter window
Dir = askdirectory(title="Select Folder")  # shows dialog box and return the path

print("LOADING...")  # Sometimes it takes long to output results, this just indicates that the program is working and not broken
sort_dict = Sorter(Dir)  # Give the directory as an input

print("\n-SORTED-\n")
for i in sort_dict:
if i[1] > 1:  # continues using gigabytes
fixed_float = float("{:.3f}".format(i[1]))
data_type = "GB:   "
elif i[1] < 0:  # no acces
print(f"NO ACCES : {i[0]}")
continue
else:  # convert to megabytes
mega_float = i[1] * 1024
fixed_float = float("{:.3f}".format(mega_float))
data_type = "MB:   "

dflt_white_space = 8
white_space = " " * (dflt_white_space - len(str(fixed_float)))
print(f"{fixed_float}  {white_space + data_type + i[0]}")

if input("\nPress 'r' to retry, press anything else to exit\n") == "r":
Main()

def get_dir_size(Dir):  # Returns the directory size in bytes.
total = 0
try:
for entry in os.scandir(Dir):  # print("[+] Getting the size of", directory)
if entry.is_file():  # if it's a file, use stat() function
total += entry.stat().st_size
elif entry.is_dir():  # if it's a directory, recursively call this function
total += get_dir_size(entry.path)
except NotADirectoryError:  # if directory isn't a directory, get the file size
return os.path.getsize(Dir)
except PermissionError:  # if for whatever reason we can't open the folder, return -1
return -1  # returns a negative size that will be visible (to use conditions) after the conversion

def get_subdirs(Dir):
dir_array = []
for x in os.listdir(Dir):
dir_array.append(x)
return dir_array

def Sorter(Dir):

Dict = {}
dir_array = get_subdirs(Dir)

for x in range(len(dir_array)):
file_size = float(get_dir_size(Dir + "\\" + dir_array[x])) / 1073741824  # Converts the bytes to GB

Dict[dir_array[x]] = file_size

sort_dict = sorted(Dict.items(), key=lambda x: x[1], reverse=True)
return sort_dict

if __name__ == "__main__":
Main()


Function equal to built-in

def get_subdirs(Dir):
dir_array = []
for x in os.listdir(Dir):
dir_array.append(x)
return dir_array


This is exactly the same as os.listdir, so just use it instead of the whole function.

Iterate simpler

for x in range(len(dir_array)):
file_size = float(get_dir_size(Dir + "\\" + dir_array[x])) / 1073741824  # Converts the bytes to GB

Dict[dir_array[x]] = file_size


Can become:

for dir in dir_array:
file_size = float(get_dir_size(Dir + "\\" + dir)) / 1073741824  # Converts the bytes to GB

Dict[dir] = file_size


Constants at the start

Putting constants at the start of the code is better than putting them inside, so at the start:

BYTES_IN_GIGABYTE = 1073741824


User message

Your code works for files so:

Dir = askdirectory(title="Select Folder")


becomes:

Dir = askdirectory(title="Select Folder or File:")


Fancy progressbar

A fancy way to signify that the program is working can be found here

-1 lost in code

You return -1 sometimes from get_dir_size but you never check for it.

One comment on the logic

for i in sort_dict:
if i[1] > 1:  # continues using gigabytes


I would rename i to dir and add a comment saying what i[1] is.

Cross platform compatibility

As suggested by @s.dallapalma avoid concatenating paths with the plus (+) sign, and use os.path.join or pathlib instead. Thus, the code can work on different OS.

I liked the attention to handling of exception and the new clean format strings.

• I felt 2x dumber, when I saw what I did with the "os.listdir".
– user182461
Dec 23 '20 at 11:22
• @Betrax coding is hard ;) Dec 23 '20 at 11:24
• I would suggest to add another section to point out to avoid concatenating paths with the plus (+) sign, and use os.path.join or pathlib instead. Thus, the code can work on different OS. Dec 24 '20 at 9:04

If I understood correctly, you would like to have a straight function that, given a directory to analyze, returns its size and the size of every sub-directory. You want the function to return a dictionary like the following:

{
'dir': int,
'dir/subdir1': int,
'dir/subdir1/subsubdir1': int,
'dir/subdir2': int,
...
}


where the values are the size of the directories in bytes. Afterward, you sort the dictionary to check which folders are the biggest and which the smallest (or empty).

If so, I have written the following code snippet, that can be further improved depending on your needs:

import os

def get_dir_size(path_to_dir: str):
"""Return a dictionary <str, int> where the key is the name of the directory
or subdirectory, and the value is the sum of their files' size in bytes.

Parameters
----------
path_to_dir : str
The path to the directory to analyze

Returns
-------
Dict[str, int]
The dictionary containig the size (in bytes) of each subdirectory

"""

dir_size_map = {}

for root, _, filenames in os.walk(path_to_dir):
for filename in filenames:
path_to_file = os.path.join(root, filename)
size = os.path.getsize(path_to_file)
dir_size_map[root] = dir_size_map.get(root, 0) + size

return dir_size_map


You can call it on the desired directory, say path/to/some/dir:

dir_info = get_dir_size('path/to/some/dir')


And analyze the resulting dictionary:

# Sort items in descending order of size
dir_info = dict(sorted(dir_info.items(), key=lambda item: item[1], reverse=True))

# List the subdirectories size in MB
for name, size in dir_info.items():
print(name, (size/1024/1024), 'MB')

# Calculate the total size
total_bytes = sum([size for _, size in dir_info])
print('Total size:', total_bytes)


Regarding the format size, I recommend you to store the information as single unit, that is, bytes instead of mixing up MB and GB. Then, you can convert them in KB (/1024), MB (/1024^2), GB (/1024^3) when needed, for example, when displaying them.

# Alternative using Counter

As pointed out by Graipher, an alternative would use a Counter, instead of a raw dictionary. In that case, the code can be written as follows:

import os

from collections import Counter

def get_dir_size(path_to_dir: str):

dir_size_counter = Counter()

for root, _, filenames in os.walk(path_to_dir):
for filename in filenames:
path_to_file = os.path.join(root, filename)
dir_size_counter[root] += os.path.getsize(path_to_file)

return dir_size_counter

dir_info = get_dir_size('path/to/some/dir')

for name, size in dir_info.most_common():
print(name, size)


It allows you to simply write dir_size_counter[root] += size and to sort the dictionary using the most_common() method. This is a valid alternative. Personally, I believe the choice depends on your coding style and preferences.

• You can use a collections.defaultdict(int) or even a collections.Counter instead of a dictionary. Both allow you to simply write dir_size_map[root] += size, while with the latter you can (ab)use dir_size_map.most_common() to get a sorted version. Dec 23 '20 at 12:38
• Also, dir is a reserved function name in Python. And doing d = {} instead of d = dict() saves you a bit over typing overhead Dec 23 '20 at 13:06
• Thanks for pointing this out. I modified the answer accordingly! Dec 23 '20 at 13:26