7
\$\begingroup\$

I retrieved all the books in pdf from my library with a recursive function.

import os

def walk(dirname):
    for name in os.listdir(dirname):
        path = os.path.join(dirname, name)

        if os.path.isfile(path) and path.endswith('pdf'):
            books.append(path)
        if os.path.isdir(path):
            walk(path)
def main():
    dir = '~/Documents/MyLibrary/'
    global books
    books = []
    walk(dir)
    print(books)

if __name__ == "__main__":
    main()
\$\endgroup\$
0

2 Answers 2

9
\$\begingroup\$

Similarly to a feature in most UNIX shells, Python has a globbing module, called glob.

It even supports the extended globbing (using ** to recursively descend into sub-folders) since Python 3.5, which you can use like this:

import os
from glob import glob

if __name__ == "__main__":
    dir = os.path.expanduser('~/Documents/MyLibrary/')
    books = glob(os.path.join(dir, "**/*.pdf"), recursive=True)
    print(books)

Here I also used the os.path module to correctly join the paths and expand the ~ symbol to the users home directory.


Alternatively, you could use the pathlib.Path object (introduced in Python 3.4), which also supports globbing:

from pathlib import Path

if __name__ == "__main__":
    dir = Path('~/Documents/MyLibrary/').expanduser()
    books = list(dir.glob('**/*.pdf'))
    print(books)

Note the list call, which is necessary, because the Path.glob method returns a generator (in contrast to glob.glob, which returns a list).

\$\endgroup\$
1
  • \$\begingroup\$ This is a solution I dare not to imagine, surprise :> \$\endgroup\$
    – AbstProcDo
    Jan 2, 2018 at 12:41
6
\$\begingroup\$

You are essentially imitating the behaviour of os.walk function, which is already present in python.

Here is a minor implementation of fnmatch to filter out a glob-based file-pattern from the os.walk's result. Since, you do not need an exclude section, your implementation would be easier. Although, personally; I'd go with .endswith check as it is easier to read (and more pythonic).


More importantly, using global variable is a very bad design practice and must be avoided. You can define a new function get_files_of_type, which takes a parameter of type of files to filter out, and another parameter for the source path, and simply returns the list generated after os.walk. A rough implementation would look like (untested):

def get_files_of_types(path='.', file_types=None):
    """
    path: string depicting the path which is to be walked
    file_types: list of extensions to be checked against
    """
    if file_types is None:
        file_types = ['pdf']
    book_list = []
    for root, sub_dirs, files in os.walk(path):
        books = [
            os.path.join(root, f)
            for f in files if f.endswith(tuple(file_types))
        ]
        book_list.append(books)
    return book_list
\$\endgroup\$
4
  • \$\begingroup\$ How about using os.path.splitext instead of str.endswith? \$\endgroup\$
    – Daniel
    Jan 2, 2018 at 12:21
  • 1
    \$\begingroup\$ @Coal_ with .endswith, a user may chose to provide either .pdf or pdf. \$\endgroup\$
    – hjpotter92
    Jan 2, 2018 at 12:24
  • 1
    \$\begingroup\$ A docstring would be really helpful for this method, because get_files_of_type(".", ".pdf") would produce interesting results (since tuple will be called on file_types, it produces ('.', 'p', 'd', 'f')). \$\endgroup\$
    – Graipher
    Jan 2, 2018 at 12:56
  • 2
    \$\begingroup\$ Also, I would call it get_files_of_types to make it clearer that an iterable of types must be supplied. \$\endgroup\$
    – Graipher
    Jan 2, 2018 at 13:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.