Treating Pathnames as Objects

Credit: David Ascher

Problem

You want to manipulate path objects as if they were sequences of path parts.

Solution

Although it is only available this elegantly in Python 2.2 and later, you can create a subclass of the string type that knows about pathnames:

_translate = { '..': os.pardir }
class path(str):
   def _ _str_ _(self):
       return os.path.normpath(self)
   def _ _div_ _(self, other):
       other = _translate.get(other, other)
       return path(os.path.join(str(self), str(other)))
   def _ _len_ _(self):
       return len(splitall(str(self)))
   def _ _getslice_ _(self, start, stop):
       parts = splitall(str(self))[start:stop]
       return path(os.path.join(*parts))
   def _ _getitem_ _(self, i):
       return path(splitall(str(self))[i])

Note that this solution relies on Recipe 4.16.

Discussion

I designed this class after I had to do a lot of path manipulations. These are typically done with a function such as os.path.join, which does the job well enough, but is somewhat cumbersome to use:

root = sys.prefix
sitepkgs = os.path.join(root, 'lib', 'python', 'site-packages')

To use this recipe, the first path must be created with the path function. After that, divisions are all that we need to append to the path:

root = path(sys.prefix)
sitepkgs = root / 'lib' / 'python' / 'site-packages'

As an additional bonus, you can treat the path as a sequence of path parts:

>>> print sitepkgs
C:\Apps\Python22\lib\python\site-packages
>>> print len(sitepkgs)
6
>>> sitepkgs[0], sitepkgs[1], sitepkgs[-1]
('C:\\', 'Apps', 'site-packages')

This class could be made richer by, for example, adding method wrappers for many of the functions that are defined in the os.path module (isdir, exists, etc.).

The code is fairly straightforward, thanks to the ease with which one can subclass strings in Python 2.2 and later. The call to os.path.normpath is important, since it ensures that casual use of . and .. do not wreak havoc:

>>> root / '..' / 'foo' / "."
'C:\\Apps\\foo\\.'

The overriding of the division operator uses a little trick that is overkill for this recipe but can come in handy in other contexts. The following line:

other = _translate.get(other, other)

does a simple lookup for other in the _translate dictionary and leaves it alone if that key isn’t found in the dictionary.

See Also

Recipe 4.16; documentation for the os.path module in the Library Reference.

Get Python Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.