Handling File Paths, Directories, and File-Related Exceptions in Python
Course: Python Training
Topic: Handling File Paths, Directories, and File-Related Exceptions
Course Link: Python Training Course at JustAcademy
Demo Link: Register for a Course Demo
Website: JustAcademy
1. Introduction to File Paths and Directories
File handling is an important part of Python programming because applications often need to read documents, save reports, process datasets, manage configuration files, and organize user-generated content. To work with files effectively, a program must know where files are located, how directories are organized, and how to respond when a file operation fails.
A file path identifies the location of a file or directory in a file system. A directory is a container that organizes files and other directories. Python provides modules such as pathlib and os for working with paths and directories, along with built-in file operations and exceptions for handling errors.
Learning Objectives
- Understand absolute and relative file paths.
- Work with file and directory paths using the
pathlib module.
- Create, inspect, list, rename, and remove directories.
- Check whether files and directories exist.
- Handle common file-related exceptions.
- Build reliable file-processing programs using validation and exception handling.
2. Understanding File Paths
A file path specifies where a file or directory can be found. Paths may identify locations relative to the current working directory or specify a complete location in the file system.
Absolute Path
An absolute path identifies a location from the root of a file system or drive. It does not depend on the program's current working directory.
# Windows example
C:\Users\Student\Documents\report.txt
# Linux example
/home/student/documents/report.txt
# macOS example
/Users/student/Documents/report.txt
Relative Path
A relative path identifies a location from the current working directory or another established base directory.
report.txt
data/report.csv
documents/notes.txt
../reports/summary.txt
The .. component refers to a parent directory, while . refers to the current directory.
Difference Between Absolute and Relative Paths
| Feature |
Absolute Path |
Relative Path |
| Starting point |
Root directory or drive |
Current or specified base directory |
| Dependency |
Usually independent of the current working directory |
Depends on the base directory used to interpret it |
| Portability |
May be specific to a machine or operating system |
Often more portable within a project |
| Example |
/home/student/report.txt |
data/report.txt |
3. Working with File Paths Using pathlib
The pathlib module provides an object-oriented way to represent and manipulate file system paths. It helps avoid many manual string operations and handles platform-specific path conventions.
Importing Path
from pathlib import Path
file_path = Path("data") / "report.txt"
print(file_path)
The division operator / joins path components. It does not divide numbers in this context; it constructs a new path object.
Getting the Current Working Directory
from pathlib import Path
current_directory = Path.cwd()
print("Current directory:", current_directory)
The current working directory is the location from which relative paths are normally interpreted. It may differ from the directory containing the Python script.
Creating an Absolute Path
from pathlib import Path
path = Path("data/report.txt")
absolute_path = path.resolve()
print("Original path:", path)
print("Resolved path:", absolute_path)
resolve() produces an absolute path and resolves symbolic links where possible. With its default non-strict behavior, the final target does not necessarily have to exist.
Getting a File Name, Suffix, and Parent Directory
from pathlib import Path
path = Path("reports/annual_report.csv")
print("Full path:", path)
print("File name:", path.name)
print("File stem:", path.stem)
print("Extension:", path.suffix)
print("Parent directory:", path.parent)
For this example, the filename is annual_report.csv, the stem is annual_report, the suffix is .csv, and the parent directory is reports.
4. Handling Windows Paths Correctly
Windows paths commonly use backslashes. In ordinary Python strings, a backslash can introduce an escape sequence, so paths should be written carefully.
Using a Raw String
path = r"C:\Users\Student\Documents\report.txt"
print(path)
Escaping Backslashes
path = "C:\\Users\\Student\\Documents\\report.txt"
print(path)
Using pathlib to Build a Path
from pathlib import Path
path = Path("C:/Users/Student/Documents") / "report.txt"
print(path)
For application code, building paths from components with Path is often more maintainable than manually joining path strings.
5. Checking Whether a File or Directory Exists
Before attempting an operation, a program may need to determine whether a path exists and whether it points to a file or a directory.
from pathlib import Path
path = Path("data/report.txt")
print("Exists:", path.exists())
print("Is file:", path.is_file())
print("Is directory:", path.is_dir())
| Method |
Purpose |
exists() |
Checks whether the path refers to an existing file system entry. |
is_file() |
Checks whether the path points to a file. |
is_dir() |
Checks whether the path points to a directory. |
is_symlink() |
Checks whether the path itself is a symbolic link. |
Important: A check such as exists() does not guarantee that a later operation will succeed. A file can disappear or permissions can change between checking and using the path. Handle exceptions around the actual operation as well.
6. Creating Directories
Python supports directory creation through both pathlib and os. With pathlib, use the mkdir() method.
Creating One Directory
from pathlib import Path
folder = Path("reports")
folder.mkdir()
print("Directory created.")
This creates the directory if it does not already exist. If the directory already exists, the operation raises FileExistsError by default.
Creating Nested Directories
from pathlib import Path
folder = Path("project/data/processed")
folder.mkdir(parents=True, exist_ok=True)
print("Directory structure is ready.")
The parents=True option creates missing parent directories. The exist_ok=True option prevents an error when the target directory already exists, provided the existing path is a directory.
Handling Directory Creation Errors
from pathlib import Path
folder = Path("reports/2026")
try:
folder.mkdir(parents=True, exist_ok=True)
print("Directory is ready.")
except PermissionError:
print("Permission denied while creating the directory.")
except OSError as error:
print("Directory creation failed:", error)
7. Listing Files and Directories
Listing a directory helps applications discover files, build inventories, process datasets, and organize project resources.
Listing Direct Children
from pathlib import Path
folder = Path("data")
if folder.is_dir():
for item in folder.iterdir():
print(item.name)
else:
print("The directory does not exist.")
The iterdir() method returns the immediate entries of a directory. It does not recursively list all files in subdirectories.
Listing Only CSV Files
from pathlib import Path
folder = Path("data")
for file_path in folder.glob("*.csv"):
if file_path.is_file():
print(file_path.name)
Searching for Files Recursively
from pathlib import Path
folder = Path("project")
for file_path in folder.rglob("*.py"):
if file_path.is_file():
print(file_path)
The rglob() method searches through subdirectories for matching paths. For very large directory trees, consider the performance and permissions implications of recursive traversal.
8. Using the os Module for Directory Operations
The os module offers functions for working with directories and file system paths. It remains useful in existing code and when operating-system-level functionality is needed.
Get the Current Working Directory
import os
print(os.getcwd())
Change the Current Working Directory
import os
os.chdir("data")
print("New working directory:", os.getcwd())
Changing the current working directory affects how relative paths are interpreted by the process. Avoid changing it unnecessarily in reusable libraries because it introduces global process-level behavior.
Create a Directory with os
import os
os.makedirs("project/reports/2026", exist_ok=True)
print("Directories are ready.")
List Directory Entries with os
import os
for item in os.listdir("data"):
print(item)
os.listdir() returns entry names rather than full path objects. Use pathlib or os.path.join() to construct paths for those entries.
9. Renaming, Moving, and Deleting Files
Python can rename or remove file system entries. These operations should be performed carefully because they may change or permanently remove user data.
Renaming a File
from pathlib import Path
source = Path("old_report.txt")
destination = Path("new_report.txt")
source.rename(destination)
print("File renamed.")
The source path must be accessible and normally must exist. If the destination already exists, replacement behavior may differ by operating system and operation, so do not assume that renaming is always safe for overwriting data.
Deleting a File
from pathlib import Path
file_path = Path("temporary.txt")
try:
file_path.unlink()
print("File deleted.")
except FileNotFoundError:
print("The file does not exist.")
except PermissionError:
print("Permission denied.")
except OSError as error:
print("Unable to delete the file:", error)
Deleting an Empty Directory
from pathlib import Path
folder = Path("empty_folder")
folder.rmdir()
The rmdir() method removes an empty directory. It raises an error if the directory is not empty.
Moving a File with shutil
from pathlib import Path
import shutil
source = Path("reports/report.txt")
destination = Path("archive/report.txt")
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(source), str(destination))
print("File moved.")
The shutil module offers higher-level file operations such as moving, copying, and deleting directory trees. Review destination behavior before using these operations on important files.
10. Common File-Related Exceptions
File system operations may fail for several reasons, including missing paths, insufficient permissions, invalid path components, conflicting file names, or storage errors. Python raises exceptions to communicate these problems.
| Exception |
Typical Cause |
Possible Response |
FileNotFoundError |
A required file or directory does not exist. |
Check the path, create the expected directory, or report the missing resource. |
FileExistsError |
A file or directory already exists where exclusive creation is requested. |
Choose another name or handle the existing resource intentionally. |
PermissionError |
The process lacks required access permissions. |
Check permissions and the intended access location. |
IsADirectoryError |
A file operation was attempted on a directory in an incompatible way. |
Verify whether the path represents a file or directory. |
NotADirectoryError |
A path component expected to be a directory is not a directory. |
Inspect each path component and correct the path. |
OSError |
A broader operating-system file operation failed. |
Inspect the exception details and respond appropriately. |
IsADirectoryError |
A file was expected but the path identifies a directory. |
Use the correct file path or directory operation. |
UnicodeDecodeError |
Bytes cannot be decoded using the selected text encoding. |
Use the correct encoding or handle the data as bytes. |
Several file-related exceptions are subclasses of OSError. When handling multiple exception types, put specific exceptions before broader ones so that the specific handlers can run.
11. Handling FileNotFoundError
FileNotFoundError occurs when an operation requires a path that cannot be found. It commonly appears when reading a file that has not been created or when a directory name is incorrect.
from pathlib import Path
file_path = Path("data/missing.txt")
try:
content = file_path.read_text(encoding="utf-8")
print(content)
except FileNotFoundError:
print("The requested file could not be found.")
This example handles the missing-file situation without terminating the program with an unhandled exception.
12. Handling FileExistsError
FileExistsError is commonly raised when an operation requires a new path but that path already exists.
from pathlib import Path
folder = Path("reports")
try:
folder.mkdir()
print("Directory created.")
except FileExistsError:
print("The directory already exists.")
If the goal is to ensure a directory exists rather than insist on creating it for the first time, use mkdir(parents=True, exist_ok=True).
13. Handling PermissionError
PermissionError occurs when the operating system prevents an operation because the current process does not have the required permissions.
from pathlib import Path
file_path = Path("protected/report.txt")
try:
content = file_path.read_text(encoding="utf-8")
print(content)
except PermissionError:
print("You do not have permission to read this file.")
except FileNotFoundError:
print("The file could not be found.")
except OSError as error:
print("An operating-system error occurred:", error)
Do not assume that changing file permissions is always appropriate. The path may be protected intentionally, or access may be restricted by the operating system or application environment.
14. Handling Multiple Exceptions with try-except
Use try and except blocks to respond to anticipated failures. A program can handle different exceptions separately when each requires a different response.
from pathlib import Path
file_path = Path("data/report.txt")
try:
with file_path.open("r", encoding="utf-8") as file:
print(file.read())
except FileNotFoundError:
print("The report is missing.")
except PermissionError:
print("Access to the report was denied.")
except IsADirectoryError:
print("Expected a file, but received a directory.")
except OSError as error:
print("File operation failed:", error)
This pattern makes errors easier to understand and troubleshoot. Avoid catching every exception without a reason because doing so can hide programming mistakes.
15. Using else and finally with File Operations
The else block runs when the try block completes without raising an exception. The finally block runs whether the operation succeeds or fails.
from pathlib import Path
file_path = Path("data/report.txt")
try:
content = file_path.read_text(encoding="utf-8")
except FileNotFoundError:
print("Report not found.")
except OSError as error:
print("Could not read the report:", error)
else:
print("Report loaded successfully.")
print(content)
finally:
print("File-reading operation completed.")
When using with open() or methods such as read_text(), Python handles resource cleanup for you. A finally block is still useful for other cleanup tasks, but it is not necessary merely to close a file already managed by a context manager.
16. Creating a Reusable File-Reading Function
Reusable functions help centralize file access and error handling. This example returns the file contents when reading succeeds and a clear result when the file cannot be read.
from pathlib import Path
def read_text_file(path):
file_path = Path(path)
try:
return file_path.read_text(encoding="utf-8")
except FileNotFoundError:
print(f"File not found: {file_path}")
except PermissionError:
print(f"Permission denied: {file_path}")
except IsADirectoryError:
print(f"Expected a file, received a directory: {file_path}")
except OSError as error:
print(f"Unable to read file: {error}")
return None
content = read_text_file("data/report.txt")
if content is not None:
print(content)
This is a simple demonstration. In a larger application, consider returning structured error information, logging exceptions, or allowing the caller to decide how errors should be handled.
17. Validating Paths Before Processing
Path validation can help produce clear messages and prevent inappropriate operations. However, validation should not replace exception handling because file system state can change between checking and using a path.
from pathlib import Path
def display_text_file(path):
file_path = Path(path)
if not file_path.exists():
print("The path does not exist.")
return
if not file_path.is_file():
print("The path is not a regular file.")
return
try:
print(file_path.read_text(encoding="utf-8"))
except OSError as error:
print("Unable to access the file:", error)
display_text_file("data/report.txt")
For applications that accept paths from users, additional validation may be needed to restrict access to approved directories.
18. Working with File Extensions
File extensions often indicate the expected content format, such as .txt, .csv, .json, or .py. The pathlib module provides properties and methods for examining these extensions.
from pathlib import Path
file_path = Path("data/sales.csv")
print("Name:", file_path.name)
print("Stem:", file_path.stem)
print("Suffix:", file_path.suffix)
if file_path.suffix.lower() == ".csv":
print("This path has a CSV extension.")
An extension alone does not guarantee that the file actually contains data in that format. When correctness or security matters, validate the file content as well.
19. Reading and Writing Files with pathlib
For small text files, Path.read_text() and Path.write_text() provide convenient alternatives to explicitly opening a file.
Reading Text
from pathlib import Path
file_path = Path("notes.txt")
content = file_path.read_text(encoding="utf-8")
print(content)
Writing Text
from pathlib import Path
file_path = Path("notes.txt")
file_path.write_text("Python file handling practice.", encoding="utf-8")
write_text() replaces the existing file content when the file already exists. Use it carefully if previous content must be preserved. For very large files or incremental processing, use a file object and process the data in manageable chunks.
20. Handling Paths Safely
Applications should treat file paths as input that may be incorrect or untrusted. This is particularly important when a web application or automated service accepts a filename from a user.
- Use
pathlib or os.path instead of manually concatenating path strings.
- Restrict file access to directories the application is intended to use.
- Do not assume that a filename or extension proves that a file is safe.
- Avoid overwriting files unless replacement is intended.
- Handle symbolic links and path traversal risks when working with untrusted paths.
- Use appropriate permissions and avoid running file-processing programs with unnecessary privileges.
- Validate file size and content when processing uploads.
- Handle errors from the actual operation even after checking that a path exists.
Example: Checking That a Path Is Within an Approved Directory
from pathlib import Path
base_directory = Path("uploads").resolve()
candidate = (base_directory / "document.txt").resolve()
try:
candidate.relative_to(base_directory)
except ValueError:
print("The requested path is outside the approved directory.")
else:
print("The path is inside the approved directory.")
This example illustrates a containment check, but production applications need additional care when symbolic links, concurrent file system changes, and user-controlled paths are involved. For sensitive operations, use a design that limits access to the intended directory at the operating-system level wherever possible.
21. Mini Project: File and Directory Organizer
Project Objective: Create a Python utility that checks a source directory, lists files by extension, creates an output directory, and handles file-related exceptions.
Project Requirements
- Accept a source directory path.
- Verify that the source exists and is a directory.
- List the files inside the source directory.
- Count files by extension.
- Create a report directory if it does not exist.
- Save a summary report to a text file.
- Handle missing directories, permission issues, and other operating-system errors.
Sample Implementation
from pathlib import Path
from collections import Counter
def create_directory_report(source_path, report_path):
source = Path(source_path)
report = Path(report_path)
if not source.exists():
print("Source path does not exist.")
return
if not source.is_dir():
print("Source path is not a directory.")
return
try:
extension_counts = Counter()
for item in source.iterdir():
if item.is_file():
extension = item.suffix.lower() or "[no extension]"
extension_counts[extension] += 1
report.parent.mkdir(parents=True, exist_ok=True)
with report.open("w", encoding="utf-8") as file:
file.write(f"Directory report for: {source.resolve()}\n")
file.write("-" * 35 + "\n")
for extension, count in sorted(extension_counts.items()):
file.write(f"{extension}: {count}\n")
print("Report created:", report)
except PermissionError:
print("Permission denied while accessing a path.")
except OSError as error:
print("File system operation failed:", error)
create_directory_report("data", "reports/file_summary.txt")
This project applies path construction, directory inspection, extension handling, directory creation, text-file writing, and exception handling. It can be extended to process subdirectories, produce CSV reports, or move files into categorized folders.
22. Common Mistakes and Troubleshooting
| Problem |
Possible Cause |
Recommended Solution |
| File cannot be found |
Incorrect relative path or working directory. |
Print Path.cwd() and inspect the resolved path. |
| Directory creation fails |
Missing parent directories or insufficient permissions. |
Use parents=True when appropriate and inspect permissions. |
| File already exists |
Exclusive creation was requested. |
Choose a new filename or handle the existing file intentionally. |
| Cannot delete a directory |
The directory is not empty. |
Inspect its contents and use an appropriate, carefully reviewed cleanup strategy. |
| Access is denied |
Insufficient permissions or a protected location. |
Use an approved location and appropriate permissions. |
| Incorrect path on Windows |
Backslashes were interpreted as escape sequences. |
Use raw strings, escaped backslashes, or pathlib. |
| Text decoding fails |
The selected encoding does not match the file's content. |
Determine the correct encoding or process the data as bytes. |
| Program crashes during file operations |
An exception was not handled at a suitable level. |
Catch expected exceptions and provide meaningful feedback. |
23. Best Practices for Professional Python Applications
- Prefer
pathlib.Path for readable, cross-platform path manipulation.
- Use absolute paths for diagnostics when relative path resolution is unclear.
- Use
mkdir(parents=True, exist_ok=True) when an application needs to ensure a directory exists.
- Use context managers for opening files.
- Specify text encoding explicitly for text files.
- Catch specific exceptions before broader exceptions such as
OSError.
- Do not suppress exceptions without logging or otherwise handling the failure.
- Use caution with deletion, renaming, and replacement operations.
- Do not rely solely on
exists() or is_file() to guarantee that a later operation will succeed.
- Test missing paths, empty directories, permission failures, and unexpected file types.
24. Practice Exercises
- Print the current working directory using both
os.getcwd() and Path.cwd().
- Create a nested directory structure using
Path.mkdir().
- Write a program that lists all text files in a directory.
- Check whether a given path is a file, directory, or missing path.
- Write a program that creates a report directory only when required.
- Rename a test file and handle the case where the source does not exist.
- Attempt to read a missing file and handle
FileNotFoundError.
- Count files by extension in a directory and write the results to a report.
- Build a function that accepts a path and returns its filename, extension, and parent directory.
- Extend the directory organizer project to include nested folders and error logging.
25. Interview Questions and Answers
Q1. What is a file path?
A file path identifies the location of a file or directory in the file system.
Q2. What is the difference between an absolute path and a relative path?
An absolute path identifies a location from a file-system root or drive, while a relative path is interpreted from a base directory.
Q3. What is pathlib?
pathlib is a Python module that provides objects and methods for working with file system paths.
Q4. How can you create a directory and its missing parents?
Use Path("parent/child").mkdir(parents=True, exist_ok=True) when existing directories should be accepted.
Q5. What does FileNotFoundError mean?
It indicates that an operation required a file or directory that could not be found.
Q6. What is the purpose of PermissionError?
It indicates that the process does not have the required permission to perform an operation.
Q7. How can you list files in a directory?
Use Path.iterdir() for direct children, Path.glob() for matching entries, or Path.rglob() for recursive matching.
Q8. Why should file operations use exception handling?
Paths, permissions, and file-system state can vary. Exception handling allows a program to report failures and respond without abruptly terminating.
Q9. What is the difference between unlink() and rmdir()?
unlink() removes a file or symbolic link, while rmdir() removes an empty directory.
Q10. Why is checking exists() not enough?
The file system may change after the check, and the subsequent operation may still fail. The actual operation should also handle relevant exceptions.
26. Quick Revision Checklist
- Absolute path: A full location from the file-system root or drive.
- Relative path: A location interpreted from a base directory.
Path.cwd(): Returns the current working directory.
Path.resolve(): Produces a resolved absolute path.
Path.mkdir(): Creates a directory.
Path.iterdir(): Lists direct directory entries.
Path.glob() and Path.rglob(): Find paths matching patterns.
Path.unlink(): Removes a file or symbolic link.
Path.rmdir(): Removes an empty directory.
FileNotFoundError: Handles missing paths.
PermissionError: Handles access restrictions.
OSError: Handles broader operating-system file failures.
- Safe file handling: Use context managers, explicit encodings, validation, and suitable exception handling.
27. Conclusion
Handling file paths, directories, and file-related exceptions is essential for writing dependable Python applications. The pathlib and os modules provide practical tools for locating files, organizing directories, and managing file-system operations. By combining these tools with clear path validation, careful file modification, and appropriate exception handling, developers can build applications that are easier to maintain and more resilient to unexpected conditions.
Continue learning with the Python Training Course at JustAcademy, register for a course demo, or visit the JustAcademy homepage.
Additional Reference: Official Python pathlib Documentation