First of all, I'd make a variable which will keep the current path. For that, I'd use os.getcwd(...)
:
current_path = getcwd()
From the docs:
Return a string representing the current working directory.
After this, I'd normalize the path, so that your script will be able to run on multiple OSs:
current_path = normpath(getcwd())
From the docs:
Normalize a pathname by collapsing redundant separators and up-level
references so that A//B
, A/B/
, A/./B
and A/foo/../B
all become A/B
.
This string manipulation may change the meaning of a path that
contains symbolic links. On Windows, it converts forward slashes to
backward slashes.
Then, I'd get a list of all the files in the directory using listdir(...)
:
only_files = [join(current_path, f) for f in listdir(current_path) if isfile(join(current_path, f))]
After getting the files, I'd wrap everything within a function:
def get_files():
current_path = normpath(getcwd())
return [join(current_path, f) for f in listdir(current_path) if isfile(join(current_path, f))]
Then, I'd make another function which will return a list of hashes for all the files in the list:
def get_hashes():
files = get_files()
list_of_hashes = []
for each_file in files:
hash_md5 = hashlib.md5()
with open(each_file, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
list_of_hashes.append('Filename: {}\tHash: {}\n'.format(basename(each_file), hash_md5.hexdigest()))
return list_of_hashes
After this, I'd just write this list of hashes in the desired file:
def write_hashes():
hashes = get_hashes()
with open('list_of_hashes.txt', 'w') as f:
for md5_hash in hashes:
f.write(md5_hash)
Finally, the code would look like this:
from os import listdir, getcwd
from os.path import isfile, join, normpath, basename
import hashlib
def get_files():
current_path = normpath(getcwd())
return [join(current_path, f) for f in listdir(current_path) if isfile(join(current_path, f))]
def get_hashes():
files = get_files()
list_of_hashes = []
for each_file in files:
hash_md5 = hashlib.md5()
with open(each_file, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
list_of_hashes.append('Filename: {}\tHash: {}\n'.format(basename(each_file), hash_md5.hexdigest()))
return list_of_hashes
def write_hashes():
hashes = get_hashes()
with open('list_of_hashes.txt', 'w') as f:
for md5_hash in hashes:
f.write(md5_hash)
if __name__ == '__main__':
write_hashes()
Now, why I'd do it this way:
- Compare your code with this new one, and tell me which one is more readable.
- You should read PEP8, which is the standard style guide for python.
- You should use
with()
when opening a file as this will make sure the file will be closed when needed.
- I've used
for chunk in iter(lambda: f.read(4096), b"")
which is a better approach for hashing large files (sometimes you won't be able to fit the whole file in memory. In that case, you'll have to read chunks of 4096 bytes sequentially).
- I've split the logic of your main program into multiple functions which I'll let you document using docstrings:
E.g:
def get_files():
"""Returns a list of all files in the current directory"""
# ...
- You can see that I also added
if __name__ == '__main__'
. By doing the main check, you can have that code only execute when you want to run the module as a program and not have it execute when someone just wants to import your module and call your functions themselves.
To be considered:
MD5 is known broken and (IMHO) should come with scary deprecation warnings and removed from the library, so you should actually do the hashing thing using sha256
from the the same module:
Remember:
Each statement that you write is important, and if changing the name of a local variable / method / anything with a descriptive name makes your code a lot clearer and more readable then do it!