33. path — Object oriented filesystem paths.

This module defines the Path class providing object oriented handling of filesystem paths. It has many similarities to the classes in the pathlib module of Python3. But we had some good reasons to not use that module and create our own instead:

  • Python’s pathlib is overly complex, with classes as Path, PosixPath, WindowsPath, PurePath, PurePosixPath, PureWindowsPath. We just have one class (Path) that does it all.

  • Despite nearly all OSes implementing paths as strings, Python’s pathlib.Path is not a string (str), and this creates a lot of problems for transforming a program to the use of pathlib.Path. All file related functions and methods need to be changed to accept str as well as pathlib.Path as arguments.

  • By contrast, our Path class is subclassed from Python’s str. This was set forward as a requirement, as it was developed for pyFormex (https://pyformex.org), which does a lot of string manipulations on file paths, and uses a lot of external libraries using strings for paths. In initially we tried to use pathlib, but the constant switching between Path and str was a real hindrance and we decided to develop our own Path class instead. Being a str, a Path can immediately be used in all places where a str is used, and all str methods can be applied on a Path.

  • We did not require compatibility with other OSes but Linux. Though the class could likely be ported to other OSes with minor adjustments, the author never did consider doing it by lack of knowledge about Windows file systems. (I know it’s case insensitive, but that’s about it).

  • Our Path class offers a lot more functionality than Python’s, as we have concentrated all path related code of pyFormex into this single module. Clearly, this could help a lot in porting pyFormex to other OSes.

Despite the differences, there are also many similarities with the pathlib classes. We have tried as much as possible to use methods with the same name and functionality.

Because we wanted this module to be of general use, the only dependencies are some Python standard library modules.

This version is for Python3 only. See pyFormex 1.0.7 for an older version (with less functionality) that supports both Python2.7 and Python3.x.

33.1. Classes defined in module path

class path.Path(*args)[source]

A filesystem path which also behaves like a str.

A Path instance represents a valid path to a file in the filesystem, existing or not. Path is thus a subclass of str that can only represent strings that are valid as file paths. The constructor will always normalize the path.

Parameters

args (path_like, …) – One or more path components that will be concatenated to form the new Path. Each component can be a str or a Path. It can be relative or absolute. If multiple absolute components are specified, the last one is used.

The following all create the same Path:

>>> Path('/pyformex/gui/menus')
Path('/pyformex/gui/menus')
>>> Path('/pyformex', 'gui', 'menus')
Path('/pyformex/gui/menus')
>>> Path('/pyformex', Path('gui'), 'menus')
Path('/pyformex/gui/menus')

But this is different:

>>> Path('/pyformex', '/gui', 'menus')
Path('/gui/menus')

Spurious slashes and single and double dots are collapsed:

>>> Path('/pyformex//gui///menus')
Path('/pyformex/gui/menus')
>>> Path('/pyformex/./gui/menus')
Path('/pyformex/gui/menus')
>>> Path('/pyformex/../gui/menus')
Path('/gui/menus')

Note

The collapsing of double dots is different from the pathlib behavior. Our Path class follows the os.path.normpath() behavior here.

Empty string and no arguments create a Path to the current directory:

>>> Path('')
Path('.')
>>> Path()
Path('.')

Operators: The slash operator helps create child paths, similarly to os.path.join(). The plus operator can be used to add a trailing part without a slash separator. The equal operator allows comparing paths.

>>> p = Path('/etc') / 'init.d' / 'apache2'
>>> p
Path('/etc/init.d/apache2')
>>> p + '.d'
Path('/etc/init.d/apache2.d')
>>> p1 = Path('/etc') + '/init.d/apache2'
>>> p1 == p
True

Note

Unlike the pathlib, our Path class does not provide the possibility to join a str and a Path with a slash operator: the first component must be a Path.

Properties: The following properties give access to different components of the Path:

  • parts: a tuple with the various parts of the Path,

  • parent: the parent directory of the Path

  • parents: a tuple with the subsequent parent Paths

  • name: a string with the final component of the Path

  • suffix: the file extension of the final component, if any

  • stem: the final component without its suffix

  • lsuffix: the suffix in lower case

  • ftype: the suffix in lower case and without the leading dot

Note

We currently do not have the following properties available with pathlib: drive, root, anchor, suffixes

sortkey

This class attribute is a callable used as the key in sorting file names. The default is set to path.hsortkey(), but it can be changed in case the user want to have another sorting method by default. Setting it to None will use lexical sorting.

Type

callable

Examples

>>> Path('/a/b')
Path('/a/b')
>>> Path('a/b//c')
Path('a/b/c')
>>> Path('a/b/./c')
Path('a/b/c')
>>> Path('a/b/../c')
Path('a/c')
>>> Path('a/b/.../c')
Path('a/b/.../c')
>>> Path('//a/b')
Path('//a/b')
>>> Path('///a/b')
Path('/a/b')
>>> p = Path('/etc/init.d/')
>>> p.parts
('/', 'etc', 'init.d')
>>> p.parent
Path('/etc')
>>> p.parents
(Path('/etc'), Path('/'))
>>> p0 = Path('pyformex/gui/menus')
>>> p0.parts
('pyformex', 'gui', 'menus')
>>> p0.parents
(Path('pyformex/gui'), Path('pyformex'), Path('.'))
>>> Path('../pyformex').parents
(Path('..'), Path('.'))
>>> p.name
'init.d'
>>> p.stem
'init'
>>> p.suffix
'.d'
>>> p1 = Path('Aa.Bb')
>>> p1.suffix, p1.lsuffix, p1.ftype
('.Bb', '.bb', 'bb')
>>> p.exists()
True
>>> p.is_dir()
True
>>> p.is_file()
False
>>> p.is_symlink()
False
>>> p.is_absolute()
True
>>> Path('/var/run').is_symlink()
True

33.2. Functions defined in module path

path.hsortkey(s)[source]

Create a key for human sorting.

When humans sort a list of strings, they tend to interprete the numerical fields as numbers and sort these parts numerically, instead of using the lexicographic sorting as done by a computer.

This splits a string in digits and non-digits parts, and converts the digits parts to int. The resulting list can be used to compare the input strings for use in human sorting. This function can be used in Python’s list.sort method and in the sorted builtin function. This version also splits the string at the last occurring dot, and makes sure that a numeric part before the dot is sorted after the

Parameters

s (str) – The string for which to compute a human sorting key.

Returns

list – A list of items which can be used in sorting methods to compare two strings and sort them in a human way. The items are alternatively of type str and int. There are always an odd number of items. The last item may be an empty string.

See also

hsortkey_ignore

similar, but ignores the case of the alphabetic parts

Examples

>>> hsortkey('abc23def45ghi')
['abc', 23, 'def', 45, 'ghi']
>>> hsortkey('abc23def45')
['abc', 23, 'def', 45, '']
>>> hsortkey('abc.def')
['abc', -1, 'def']
>>> hsortkey('abc23.def')
['abc', 23, '', -1, 'def']
>>> sorted(['.b', '0b', '.0b', '_b', 'Ab', 'ab'], key=hsortkey)
['.0b', '.b', '0b', 'Ab', '_b', 'ab']
>>> sorted(['a', 'ab', 'a.b', 'a1.b', 'a2.b', 'a10.b'])
['a', 'a.b', 'a1.b', 'a10.b', 'a2.b', 'ab']
>>> sorted(['a', 'ab', 'a.b', 'a1.b', 'a2.b', 'a10.b'], key=hsortkey)
['a', 'a.b', 'a1.b', 'a2.b', 'a10.b', 'ab']
path.hsortkey_ignore(s)[source]

Create a key for human sorting while ignoring case.

This is like hsortkey but ignores case.

Examples

>>> hsortkey_ignore('abc23DEF45ghi')
['abc', 23, 'def', 45, 'ghi']
path.matchAny(target, *regexps)[source]

Check whether target matches any of the regular expressions.

Parameters
  • target (str) – String to match with the regular expressions.

  • *regexp (sequence of regular expressions.) – The regular expressions to match the target string.

Returns

bool – True, if target matches at least one of the provided regular expressions. False if no matches.

Examples

>>> matchAny('test.jpg', '.*[.]png', '.*[.]jpg')
True
>>> matchAny('test.jpeg', '.*[.]png', '.*[.]jpg')
False
>>> matchAny('test.jpg')
False