How to do a nested list comprehension without making a product?
In Python, I want to list all files in a set of directories. The best I'd like to get is a list. But at most I managed to make a nested list:
pics = os.path.expanduser('~/Pictures')
all_pics = [(d, os.listdir(d)) for d in os.listdir(pics)]
result:
[('folder1', ['file1', 'file2', ...]), ('folder2', ['file1', ...]), ...]
what I want:
[('folder1' 'file1'), ('folder1', 'file2'), ..., (开发者_运维技巧'folder2', 'file1'), ...]
What I would like to get is a simple plain list, doesn't matter of what (can be of tuples), just so that it hasn't nested things, and I need no nested cycles in the code that parses it.
How can I do this with list comprehensions? Doing this gives me a product of 2 sets (dir names and filenames) which is wrong:
[(d, f) for f in os.listdir(os.path.join(pics, d)) for d in os.listdir(pics)]
You got the order of the for-loops wrong. It should be
all_pics = [(d, f)
for d in os.listdir(pics)
for f in os.listdir(os.path.join(pics, d))]
Outermost loop first, innermost loop last.
I wonder why you didn't get a NameError
for d
.
Using os.walk
is a better idea:
all_pics = [(dir,file)
for dir,sub,files in os.walk(pics)
for file in files]
So why is it a better idea?
- it works with deeper trees, too (
subdir/subdir/pic.jpg
) - it doesn't break if you put a file in
~/Pictures
(If you callos.listdir
on files, you getOSError: [Errno 20] Not a directory
) - it's simpler:
os.walk
does the traversing for you, all that remains is formatting the output
List comprehensions work as a mapping from one list to another. As another poster pointed it you can do it by nesting them, but are you sure you really want to? You should favour readability over everything else, in all most all cases - and I don't consider the nested comprehension to be easily understandable.
What is wrong with? It might be marginally slower - but will you notice?
files = []
for d in os.listdir(pics):
for f in os.listdir(os.path.join(pics, d)):
files.append((d, f))
If you don't want to generate the list until absolutely necessary you could use a generator:
def get_file_mapping():
for d in os.listdir(pics):
for f in os.listdir(os.path.join(pics, d)):
yield (d, f)
files = list(get_file_mapping())
You simply need to flatten your output list:
pics = os.path.expanduser('~/Pictures')
all_pics = reduce(lambda xs,ys: xs+ys, [(d, os.listdir(d)) for d in os.listdir(pics)])
l = [ [(d, f) for f in os.listdir(os.path.join(pics, d))] for d in os.listdir(pics) ]
l = sum(l, []) # flatten list of lists => list
精彩评论