🐍 Python - pathlib
pathlib
allows representing file system paths as objects. Contrary to the older os.path
that works with strings, pathlib
returns objects. Full standard library support was added in Python 3.6.
You can still use pathlib
on older Python versions. You just have to install it separately.
pip install pathlib
Path
objects work on all systems. With os.path
you have to deal with /
vs \
and other nonsense.
You can use PurePath
for path handling that doesn't touch the file system. PurePosixPath
to emulate Unix, or PureWindowsPath
to emulate Windows. These implement os.PathLike
interface so can be used in wide standard library contexts. Path
is the concrete subclass of the more abstract PurePath
.
Path
objects are slower than os.path
strings. Not an actual issue if you are not working with tens of thousands of files.
Resolving simple paths.
from pathlib import Path
# resolve makes the path absolute, convertig symlinks and dot components
assert Path('.').resolve() == Path().cwd()
# resolves can also be strict, failing if it doesn't exist
assert Path('..').resolve(strict=True) == Path('.').resolve().parent
# sometimes you need to use other helpers to get where you want
assert Path('~').expanduser() == Path().home()
# __file__ points to current file the line is in
THIS_FILE = Path(__file__)
BASE_DIR = THIS_FILE.parent
TEMPLATES_DIR = BASE_DIR.joinpath('templates')
print(THIS_FILE) # /tmp/pathlib-is-awesome.py
print(BASE_DIR) # /tmp
print(TEMPLATES_DIR) # /tmp/templates
Creating directories and files.
from tempfile import TemporaryDirectory
from pathlib import Path
# tempfile context is unrelated to pathlib, they make these examples auto-cleaning
with TemporaryDirectory() as temp_dir:
# existance, we create this directory above
dir_path = Path(temp_dir)
assert dir_path.exists()
# creating directories
deep_path = dir_path.joinpath('i-want/this/structure')
assert not deep_path.exists()
deep_path.mkdir(parents=True, exist_ok=True)
assert deep_path.exists()
# writing to files
config_path = deep_path.joinpath('.my-config')
config_path.write_text('# config goes here #')
assert config_path.read_text() == '# config goes here #'
# Path objects refer to PATHS, not directories/files (they or might not exist)
new_path = deep_path.joinpath('.new-config')
assert config_path.exists()
assert not new_path.exists()
config_path.rename(new_path)
assert not config_path.exists()
assert new_path.exists()
Context managers work just as well.
from tempfile import TemporaryDirectory
from pathlib import Path
with TemporaryDirectory() as temp_dir:
config_path = Path(temp_dir, '.editorconfig')
with open(config_path, mode='w') as config_file:
config_file.write('# config goes here')
with open(config_path, mode='r') as config_file:
assert config_file.read() == '# config goes here'
Searching for files using name patterns.
from tempfile import TemporaryDirectory
from pathlib import Path
with TemporaryDirectory() as temp_dir:
# creating 3 files in temp dir and 3 files one directory down
for i in range(3):
Path(temp_dir, f'file-{i}.csv').write_text(str(i))
deep_path = Path(temp_dir, 'deeper')
deep_path.mkdir(exist_ok=True)
Path(deep_path, f'file-{i}.csv').write_text(str(i))
assert len([x for x in Path(temp_dir).glob('*.csv')]) == 3
assert len([x for x in Path(temp_dir).glob('**/*.csv')]) == 6
assert len([x for x in Path(temp_dir).rglob('*.csv')]) == 6
assert len([x for x in Path(temp_dir).rglob('doesnt-exist')]) == 0
You can cast between strings and Path
objects when required.
import os
from pathlib import Path
p1 = os.path.join('src', 'my_package')
p2 = Path('src', 'my_package')
assert type(p1) == str
assert isinstance(p2, Path)
assert os.fspath(p1) == os.fspath(p2) # converting to strings
assert Path(p1) == Path(p2) # converting to objects