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\$
10
\$\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\$
  • \$\begingroup\$ This is a solution I dare not to imagine, surprise :> \$\endgroup\$ – Algebra Jan 2 '18 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\$
  • \$\begingroup\$ How about using os.path.splitext instead of str.endswith? \$\endgroup\$ – Daniel Jan 2 '18 at 12:21
  • 1
    \$\begingroup\$ @Coal_ with .endswith, a user may chose to provide either .pdf or pdf. \$\endgroup\$ – hjpotter92 Jan 2 '18 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 '18 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 '18 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.