Skip to main content

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)
note

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
Race Condition Vulnerability

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

MethodCreates Parent DirsThread SafeBehavior If File Exists
open(path, 'x')NoYesRaises FileExistsError
Path.touch(exist_ok=True)NoYesUpdates timestamp, keeps content
Path.touch(exist_ok=False)NoYesRaises FileExistsError
open(path, 'a')NoYesAppends to existing content
os.path.exists() + open('w')NoNoRace condition risk
Atomic with tempfileNoYesOverwrites 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 with tempfile when data integrity is critical and partial writes are unacceptable.