How to Create a File If It Does Not Exist in Python
Safely creating files without overwriting existing data is essential for configuration management, logging, and data persistence. Whether you are initializing a default configuration, setting up a log file, or creating a data store, you need to handle the case where the file might already exist with important content.
In this guide, you will learn several approaches to creating files only when they do not already exist, understand the safety trade-offs of each method, and see practical examples for common real-world scenarios.
Using Exclusive Creation Mode ('x')
The 'x' mode in open() creates a file exclusively and raises a FileExistsError if the file already exists:
try:
with open("config.ini", "x") as file:
file.write("setting=default\n")
print("File created successfully")
except FileExistsError:
print("File already exists, skipping creation")
Output (first run):
File created successfully
Output (subsequent runs):
File already exists, skipping creation
This is the safest approach for preventing accidental overwrites because the existence check and file creation happen atomically in a single operating system call. There is no window where another process could interfere.
Using pathlib.Path.touch()
The pathlib module provides a modern, object-oriented way to ensure a file exists, similar to the Unix touch command:
from pathlib import Path
log_file = Path("application.log")
log_file.touch(exist_ok=True)
print(f"File exists: {log_file.exists()}")
Output:
File exists: True
The exist_ok parameter controls what happens when the file already exists:
from pathlib import Path
config = Path("settings.json")
# Raise an error if the file already exists
try:
config.touch(exist_ok=False)
print("File created")
except FileExistsError:
print("Configuration already exists")
# Silently succeed if the file exists (updates modification timestamp)
config.touch(exist_ok=True)
When exist_ok=True, calling touch() on an existing file updates its modification timestamp without altering the contents. This is usually harmless but is worth knowing if your application depends on file timestamps.
Creating Parent Directories Automatically
When the file path includes directories that may not exist yet, you need to create the entire directory tree before creating the file:
from pathlib import Path
file_path = Path("logs/2026/january/app.log")
# Create all parent directories
file_path.parent.mkdir(parents=True, exist_ok=True)
# Then create the file
file_path.touch(exist_ok=True)
print(f"File ready at: {file_path}")
Output:
File ready at: logs/2026/january/app.log
You can wrap this into a reusable helper function that also writes default content:
from pathlib import Path
def ensure_file_exists(filepath, default_content=""):
"""Create a file with default content if it does not already exist."""
path = Path(filepath)
path.parent.mkdir(parents=True, exist_ok=True)
if not path.exists():
path.write_text(default_content, encoding="utf-8")
return True # File was created
return False # File already existed
created = ensure_file_exists("config/app.json", '{"version": 1}')
print(f"File was created: {created}")
Output (first run):
File was created: True
Output (subsequent runs):
File was created: False
The Check-Then-Write Pattern and Its Pitfall
A traditional approach checks for file existence before writing:
import os
filepath = "data.txt"
if not os.path.exists(filepath):
with open(filepath, "w") as file:
file.write("Initial content")
print("File created")
else:
print("File already exists")
Output:
File created
This pattern has a race condition. Another process or thread could create the file between the os.path.exists() check and the open() call, causing your code to overwrite the other process's data without warning.
# Thread A checks: file does not exist
if not os.path.exists("data.txt"): # True
# Thread B creates data.txt with important content here
with open("data.txt", "w") as file: # Thread A overwrites Thread B's data!
file.write("Initial content")
For single-threaded scripts that run in isolation, this pattern is acceptable. For concurrent environments, use the 'x' mode instead.
Opening for Append (Create If Missing)
The 'a' (append) mode creates the file if it does not exist and appends to the end if it does. This makes it ideal for log files:
with open("log.txt", "a") as file:
file.write("New log entry\n")
This never overwrites existing content. Here is a practical logging function:
from datetime import datetime
def log_message(filepath, message):
"""Append a timestamped message to a log file, creating it if needed."""
timestamp = datetime.now().isoformat()
with open(filepath, "a", encoding="utf-8") as file:
file.write(f"[{timestamp}] {message}\n")
log_message("app.log", "Application started")
log_message("app.log", "Processing complete")
After two calls, app.log contains:
[2026-01-15T10:30:00.123456] Application started
[2026-01-15T10:30:00.234567] Processing complete
Creating Files with Specific Permissions
On Unix-like systems, you can set file permissions during creation to restrict access:
from pathlib import Path
filepath = Path("secrets.txt")
if not filepath.exists():
filepath.touch(mode=0o600) # Owner read/write only
filepath.write_text("sensitive data", encoding="utf-8")
print("Secrets file created with restricted permissions")
else:
print("Secrets file already exists")
The 0o600 permission mode means only the file owner can read and write the file. Other users have no access.
Atomic File Creation with tempfile
For maximum safety in concurrent environments where you need to write content atomically, write to a temporary file first and then rename it:
import tempfile
import os
def create_file_atomic(filepath, content):
"""Create a file atomically by writing to a temp file and renaming."""
directory = os.path.dirname(filepath) or "."
fd, temp_path = tempfile.mkstemp(dir=directory)
try:
with os.fdopen(fd, "w") as file:
file.write(content)
os.rename(temp_path, filepath)
except Exception:
os.unlink(temp_path)
raise
create_file_atomic("config.json", '{"setting": "value"}')
This approach ensures that the file either contains the complete content or does not exist at all. If the write fails partway through, the temporary file is cleaned up and the target path remains untouched.
A Common Mistake: Using 'w' Mode Instead of 'x'
A frequent error is using write mode ('w') when you intend to create a file only if it does not exist:
# Dangerous: silently overwrites existing content!
with open("important_data.txt", "w") as file:
file.write("default content")
The 'w' mode truncates the file to zero length before writing, destroying any existing data without warning. If your goal is to avoid overwriting, always use 'x' mode:
# Safe: raises FileExistsError if the file already has content
try:
with open("important_data.txt", "x") as file:
file.write("default content")
except FileExistsError:
pass # File exists, leave it alone
Method Comparison
| Method | Creates Parent Dirs | Thread Safe | Behavior If File Exists |
|---|---|---|---|
open(path, 'x') | No | Yes | Raises FileExistsError |
Path.touch(exist_ok=True) | No | Yes | Updates timestamp, keeps content |
Path.touch(exist_ok=False) | No | Yes | Raises FileExistsError |
open(path, 'a') | No | Yes | Appends to existing content |
os.path.exists() + open('w') | No | No | Race condition risk |
Atomic with tempfile | No | Yes | Overwrites atomically |
Conclusion
Use open(path, 'x') when you must strictly prevent overwrites. It is the safest option because the check and creation happen atomically.
- Use
Path.touch(exist_ok=True)when you simply need to ensure a file exists before further operations, without caring about its content. - Choose append mode (
'a') for log files where you always want to add content without destroying existing entries. - Avoid the check-then-write pattern with
os.path.exists()in concurrent environments due to its race condition vulnerability, and reach for atomic creation withtempfilewhen data integrity is critical and partial writes are unacceptable.