Saturday, April 24, 2010

Path class

I hope one of these stuffs is in the standard library.

>>> p = Path('/path/to/some/text.txt')
>>> p.parent()
Path('/path/to/some')
>>> p.parent().parent()
Path('/path/to')
>>> p.parent().parent().parent()
Path('/path')
>>> p.parent().parent().parent().parent()
Path('/')
>>> p.parent().parent().parent().parent().parent()
Path('/')
>>> p.parent().child('other').child('text.txt')
Path('/path/to/some/other/text.txt')
>>> p[:-1]
Path('/path/to/some')
>>> p[1:-1]
Path('path/to/some')
>>> p[2:-1]
Path('to/some')

You cannot write e.g. p[2:3] = p('aaa') since conceptually the Path class is immutable.

import os

class Path(object):
    def __init__(self, path):
        if isinstance(path, Path):
            path = path.path
        self.path = path

    def parent(self):
        return Path(os.path.dirname(self.path))

    def child(self, name):
        if isinstance(name, Path):
            name = name.path
        childPath = os.path.join(self.path, name)
        return Path(childPath)

    def base(self):
        return Path(os.path.basename(self.path))

    def abspath(self):
        return Path(os.path.abspath(self.path))

    def __str__(self):
        return self.path

    def __repr__(self):
        return self.__class__.__name__ + '(' + repr(self.path) + ')'

    def __add__(self, rhs):
        if isinstance(rhs, Path):
            return self.child(rhs.path)
        else:
            return Path(self.path + rhs)

    def __eq__(self, rhs):
        if isinstance(rhs, Path):
            rpath = rhs.path
        elif isinstance(rhs, unicode):
            rpath = rhs
        else:
            return False
        return os.path.abspath(self.path) == os.path.abspath(rpath)

    def __ne__(self, rhs):
        return not self.__eq__(rhs)

    def __getitem__(self, key):
        this, parent = self, self.parent()
        bases = [this.base()]
        while this != parent:
            bases[:0] = [parent.base()]
            this, parent = parent, parent.parent()
        bases[0] = this
        bases = bases[key]
        retPath = bases[0]
        for base in bases[1:]:
            retPath = retPath.child(base)
        return retPath

    @staticmethod
    def getCurrent():
        return Path(os.getcwd())

To extract the Python string a Path object has, you can pass the object to str() or unicode(). A friend of mine gave me an idea to make the class to be a subclass of str/unicode so that a Path object can be used as a Python string. But the meaning of __eq__, __ne__, and __getitem__ is quite different between str/unicode and my class.

3 comments:

Drake said...

Then, how about making Path a subclass of str/unicode and overriding it's __eq__, __ne__ and __getitem__ ?

hohehohe2 [at] gmail.com said...

I thought about it but if it is a string, it should be expected to behave as a string. For example when a Path object is passed to a function in a third party module, the function may expect the object as a regular str/unicode object and expect __eq__, __ne__, __getitem__ not to be overridden. It'll be more confusing than convenient.

Drake said...

Hmm...

You're right~