I'm working on a python module to randomly choose a wallpaper for my desktop background among thousands of pictures in my photo library.
I though I'd make a general function:
get_random_file(ext, top)
Return the name of a random file in a directory tree with top as root.
Since I'm dealing with a very large directory tree, listing all possible files would be very time consuming (edit: see comments on C. Harley answer). So I went for heuristic method, making sure all files are covered and adding an upper bound in case there are no files with given extension. There are occasionally false negatives, which is not a too big deal, but most importantly, it runs nearly instantly.
Algorithm is simple:
- Get a list of directories, including
.
- Pick a random directory from that list.
- If the picked directory is
.
(either randomly, or only available option), then retrieve a list of files in that directory. - If the list of files is empty, go back to 1.
- Return a random file from that list.
Note that this algorithm can go many levels deep into sub-directories.
So far I'm happy with performance and returned values. It is able to quickly pick up a file in a very large tree.
However, I'm after general comments in terms of performance and also code style.
What I'm not particularly proud of in my code is the use of next(os.walk(top))[1]
to get the list of sub-directories in the current directory.
There are also false negatives, when asking for a file extension with not many occurrences in a tree with many directories. However, I don't want to increase the limit too much to avoid waiting when really there are not such files in the tree.
The code:
# -*- coding: utf-8 -*-
"""
Created on Wed Aug 29 14:05:27 2018
@author: raf
"""
import os
import sys
import glob
import random
def get_random_files(ext, top):
'''Return the name of a random file within a top path.
Works recursivelly in subdirectories.
Note that a full list of files could be got with
glob(top + '/**/*.' + ext, recursive=True)
However that would be extremely slow for large directories.
'''
_top = top
ct, limit = 0, 50000
while True:
if ct > limit:
return 'No file found after %d iterations.' % limit
ct += 1
try:
dirs = next(os.walk(top))[1]
except StopIteration: # access denied and other exceptions
top = _top
continue
i = random.randint(0, len(dirs))
if i == 0: # use .
files = glob.glob(top + '/*.' + ext)
if not files:
top = _top
continue
i = random.randint(0, len(files)-1)
return files[i]
top += '/' + dirs[i-1]
if __name__ == '__main__':
if len(sys.argv) > 1:
print(get_random_files(sys.argv[1], os.getcwd()))
else:
print(get_random_files('*', os.getcwd()))