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 successfullyOutput (subsequent runs):
File already exists, skipping creationThis 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: TrueThe exist_ok parameter controls what happens when the file already exists:
from pathlib import Path
config = Path("settings.json")
try:
config.touch(exist_ok=False)
print("File created")
except FileExistsError:
print("Configuration already exists")
config.touch(exist_ok=True)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")
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.touch(exist_ok=True)
print(f"File ready at: {file_path}")Output:
File ready at: logs/2026/january/app.logYou 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: TrueOutput (subsequent runs):
File was created: FalseThe 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 createdOpening 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 completeCreating 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:
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:
try:
with open("important_data.txt", "x") as file:
file.write("default content")
except FileExistsError:
pass # File exists, leave it aloneMethod 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.