11.1. File Path

11.1.1. Rationale

  • Python works with both relative and absolute path

  • Path separator \ (backslash) is used on Windows

  • Path separator / (slash) is used on *nix operating systems: Linux, macOS, BSD and other POSIX compliant OSes (excluding Windows)

  • In newer Windows versions both \ and / works the same

Absolute path on Windows:

>>> FILE = r'C:\Users\Watney\myfile.txt'

Absolute path on *nix (Linux, macOS, BSD, etc.):

>>> FILE = '/tmp/myfile.txt'

Relative paths works the same on Windows and *nix (Linux, macOS, BSD, etc.):

>>> FILE = 'myfile.txt'
>>> FILE = 'tmp/myfile.txt'
>>> FILE = '../myfile.txt'

11.1.2. Good Engineering Practices

  • Never hardcode paths, use constant as a file name or file path

  • Convention (singular form): FILE, FILENAME, FILEPATH, PATH

  • Convention (plural form): FILES, FILENAMES, FILEPATHS, PATHS

  • Note, that PATH is usually used for other purposes (sys.path or os.getenv('PATH'))

>>> FILE = 'myfile.txt'
>>> FILES = [
...     'myfile.txt',
...     'myfile.csv']

11.1.3. Raw Strings

  • Always use raw-strings (r"...") for paths

  • Escapes does not matters

>>> print(r'C:\Users\Admin\file.txt')
C:\Users\Admin\file.txt
>>> print('C:\\Users\\Admin\\file.txt')
C:\Users\Admin\file.txt
>>> print('C:\Users\Admin\file.txt')
Traceback (most recent call last):
SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 2-3: truncated \UXXXXXXXX escape
  • Problem: \Users

  • after \U... python expects Unicode codepoint in hex i.e. 'U0001F680' which is 🚀 emoticon

  • s is invalid hexadecimal character

  • Only valid characters are 0123456789abcdefABCDEF

>>> import string
>>>
>>>
>>> print(string.hexdigits)
0123456789abcdefABCDEF

11.1.4. Absolute Path

  • Absolute path on Windows starts with drive letter

  • Absolute path on *nix starts with root / dir

  • Absolute path include all entries in the directories hierarchy

>>> FILE = r'C:\Users\Watney\myfile.txt'
>>> FILE = r'/tmp/myfile.txt'

11.1.5. Relative Path

  • Path is relative to currently running script

  • . - Current directory

  • .. - Parent directory

>>> FILE = r'myfile.txt'
>>> FILE = r'./myfile.txt'
>>> FILE = r'tmp/myfile.txt'
>>> FILE = r'./tmp/myfile.txt'
>>> FILE = r'../myfile.txt'
>>> FILE = r'../tmp/myfile.txt'
>>> FILE = r'../../myfile.txt'
>>> FILE = r'../../tmp/myfile.txt'

11.1.6. Escaping Characters in Path

  • "\ " (backslash space) - escapes space

  • Note that in Python escapes in paths are not required

>>> FILE = '/tmp/my file.txt'
>>> FILE = r'/tmp/my file.txt'
>>> FILE = r'C:\Users\Admin\myfile.txt'
>>>
>>>
>>> repr(FILE)
"'C:\\\\Users\\\\Admin\\\\myfile.txt'"
>>>
>>> str(FILE)
'C:\\Users\\Admin\\myfile.txt'
>>>
>>> print(repr(FILE))
'C:\\Users\\Admin\\myfile.txt'
>>>
>>> print(FILE)
C:\Users\Admin\myfile.txt

11.1.7. Exception Handling

>>> try:
...     file = open('/tmp/myfile.txt')
... except FileNotFoundError:
...     print('Sorry, file not found')
... except PermissionError:
...     print('Sorry, not permitted')
Sorry, file not found

11.1.8. Create Directories

>>> from pathlib import Path
>>>
>>>
>>> path = Path('/tmp/a')
>>>
>>> path.mkdir()
>>>
>>> path.mkdir()
Traceback (most recent call last):
FileExistsError: [Errno 17] File exists: '/tmp/a'
>>>
>>> path.mkdir(exist_ok=True)
>>> from pathlib import Path
>>>
>>>
>>> path = Path('/tmp/a/b/c')
>>> path.mkdir(parents=True, exist_ok=True)

11.1.9. Delete directory

Works only with empty directories:

>>> from pathlib import Path
>>>
>>>
>>> path = Path('/tmp/a')
>>> path.rmdir()
Traceback (most recent call last):
OSError: [Errno 66] Directory not empty: '/tmp/a'

Remove directories with files:

>>> from shutil import rmtree
>>>
>>>
>>> path = '/tmp/a'
>>> rmtree(path, ignore_errors=True)

11.1.10. Current Working Directory

  • Returns an absolute path to current working directory

>>> from pathlib import Path
>>>
>>>
>>> path = Path.cwd()
>>> print(path)  # doctest: +SKIP
/home/watney/

11.1.11. Exists and is Directory or File

  • Touch creates a file

>>> from pathlib import Path
>>>
>>>
>>> file = Path('/tmp/myfile.txt')
>>>
>>> file.touch()
>>>
>>> file.exists()
True
>>> file.is_dir()
False
>>> file.is_file()
True

11.1.12. Convert Relative Path to Absolute

>>> from pathlib import Path
>>>
>>>
>>> file = Path('myfile.txt')
>>> file.absolute()  # doctest: +SKIP
/home/watney/myfile.txt

11.1.13. Dirname, Filename

>>> from pathlib import Path
>>>
>>>
>>> file = Path('/home/watney/myfile.py')
>>>
>>> print(file.parent)
/home/watney
>>>
>>> print(file.name)
myfile.py

11.1.14. Script Path

  • __file__ - Returns an absolute path to currently running script

>>> print(__file__)  # doctest: +SKIP
/home/watney/myscript.py
>>> from pathlib import Path
>>>
>>>
>>> file = Path(__file__)  # doctest: +SKIP
>>>
>>> print(file.parent)  # doctest: +SKIP
/home/watney
>>>
>>> print(file.name)  # doctest: +SKIP
myfile.py

11.1.15. Assignments

Code 11.1. Solution
"""
* Assignment: File Path Exception
* Required: yes
* Complexity: easy
* Lines of code: 6 lines
* Time: 3 min

English:
    1. Modify `result` function
    2. If `filename` exists, print 'Ok'
    3. If `filename` does not exist, print 'File not found'
    4. Run doctests - all must succeed

Polish:
    1. Zmodyfikuj funkcję `result`
    2. Jeżeli `filename` istnieje, wypisz 'Ok'
    3. Jeżeli `filename` nie istnieje, wypisz 'File not found'
    4. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert result is not Ellipsis, 'Assign result to variable: `result`'
    >>> assert isfunction(result), 'Variable `result` has invalid type, should be function'

    >>> result(__file__)
    Ok
    >>> result('_notexisting.txt')
    File not found
"""

def result(filename):
    ...


Code 11.2. Solution
"""
* Assignment: File Path Abspath
* Required: yes
* Complexity: easy
* Lines of code: 3 lines
* Time: 5 min

English:
    1. Define `path` with converted `filename` to absolute path
    2. To `result` assgin string:
        a. `file` if path is a file
        b. `directory` if path is a directory
        c. `missing` if path does not exist
    3. Run doctests - all must succeed

Polish:
    1. Zdefiniuj `path` z przekonwertowym `filename` do ścieżki bezwzględnej
    2. Do `result` przypisz ciąg znaków:
        a. `file` jeżeli ścieżka jest plikiem
        b. `directory` jeżeli ścieżka jest katalogiem
        c. `missing` jeżeli ścieżka nie istnieje
    3. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> assert isinstance(result, str), \
    'Result must be a str with: `file`, `directory` or `not exist`'

    >>> assert isinstance(abspath, Path), \
    'Use Path class from pathlib library to create a filepath'

    >>> current_directory = Path.cwd()
    >>> assert str(current_directory) in str(abspath), \
    'File Path must be absolute, check if you have current directory in path'

    >>> result
    'missing'
"""

from pathlib import Path


FILENAME = 'myfile.txt'

abspath = ...  # Path: Absolute path to FILENAME
result = ...  # str: file, directory or missing