The path Python module

Download path.py v1.1.2 for Python 2.2 or later.

This module provides a single class that wraps the functionality in the os.path module. You wouldn't think that would be so helpful, but in practice I find it much more pleasant to write and to read.

License: You may use path.py for whatever you wish, at your own risk. (For example, you may modify, relicense, and redistribute it.) It is provided without any guarantee or warranty of any kind, not even for merchantability or fitness for any purpose.

If you do make changes to path.py, please consider sending them along to me at jason@jorendorff.com.

There's a test suite now, although it isn't very thorough. test_path.py requires a matching version of path.py.

Motivation

Why write something like this?

How to use path

Start with from path import path. You can then create path objects by calling path('/usr/local/bin') or path('C:\\Program Files'), etc.

The path object incorporates features from several existing Python standard library modules:

path also has these additional features:

Pure convenience. I find it a joy to use, and I hope you will too.

Examples

Simple scripting - make some files executable

# with os.path
DIR = '/usr/home/guido/bin'
for f in os.listdir(DIR):
    path = os.path.join(DIR, f)
    if os.path.isfile(path) and f.endswith('.py'):
        os.chmod(path, 0755)

# with path
dir = path('/usr/home/guido/bin')
for f in dir.files():
    if f.ext == '.py':
        f.chmod(0755)

Directory walking - delete Emacs backup files

# with os.path.walk
def delete_backups(arg, dirname, names):
    for name in names:
        if name.endswith('~'):
            os.remove(os.path.join(dirname, name))

os.path.walk(os.environ['HOME'], delete_backups, None)

# with os.path, if (like me) you can never remember how os.path.walk works
def walk_tree_delete_backups(dir):
    for name in os.listdir(dir):
        path = os.path.join(dir, name)
        if os.path.isdir(path):
            walk_tree_delete_backups(path)
        elif name.endswith('~'):
            os.remove(path)

walk_tree_delete_backups(os.environ['HOME'])

# with path
dir = path(os.environ['HOME'])
for f in dir.walk():
    if f.isfile() and f.endswith('~'):
        f.remove()

Warts

for c in str means one thing, and for c in path means something completely different. This is intentional, but it breaks compatibility with str a bit. This means you have to be somewhat careful passing path objects to functions that expect strings; if they try to iterate over the characters in the string, they'll malfunction.

Another reason to be careful passing path objects to functions expecting strings is that a function that checks the type of the string, with code like if type(s) is type(''): ... will fail. This may be considered a bug in the function, though. The test should be written if isinstance(s, (str, unicode)), or in Python 2.3, if isinstance(s, basestring), both of which allow for string subclasses.

path.__iter__() and path.__contains__() aren't really on the same wavelength. This means that for child in path and if child in path aren't using the word "in" to mean the same thing, which is mildly annoying in principle (though not in my experience, so far). Of course, this is true of string objects anyway, but the discrepancy isn't as great.

os.path.join doesn't map to path.join(), because there's a string method with that name. Instead it's path.joinpath(). This is a nuisance, but changing the semantics of base class methods is worse. (I know, I tried it.) The same goes for split().

Acknowledgements

Many thanks to Thiébaut Champenier, Gary Herron, John A. Ferguson, Lars Gustäbel, John Bridle, Jesper Hertel, Michael Urman, Oren Tirosh, Garth Kidd, Detlef Lannert, and probably several other people I have missed, for pointing out bugs and possible improvements—and providing bits of code.