Python copy without overwriting: create the destination exclusively
By ON Lab · Sources checked November 8, 2026
For a regular-file copy that must reject an existing destination, open the destination in exclusive binary creation mode, "xb", and copy bytes into that open file. Python raises FileExistsError if that path already exists. Checking exists and then calling an overwriting copy function leaves a gap between the check and the write.
A historical reader wanted copying to avoid replacing an existing file as part of a simple versioning workflow. We checked Python's open and shutil documentation and tested an existing-target collision and a fresh-target byte copy. This guide covers individual regular files, not directory merging, a complete versioning system or atomic publication of a fully copied file.
Copy through the exclusively created file handle
import shutil
with open("source.bin", "rb") as source:
with open("copy-new.bin", "xb") as destination:
shutil.copyfileobj(source, destination)
The source is opened first, so a missing source is detected before this example creates the output. Binary mode copies bytes without text encoding or newline conversion. The destination's parent directory must already be available and writable; exclusive creation is not a remedy for a wrong path or missing permission.
The with blocks close the files when copying finishes or an exception propagates. Closing the output is also important before immediately reopening it for verification: copyfileobj alone does not guarantee that the destination stream has been flushed for another reader.
What happened in the local test
| Destination state | Observed result | Byte comparison |
|---|---|---|
| Already contained an original payload | FileExistsError | Original destination remained unchanged |
| Did not exist | Copy completed | New output matched source bytes |
Our fixture included a zero byte and a newline, and compared the bytes after the files were closed. We also verified that the source remained unchanged. We did not test all filesystems, symbolic links, directory permissions, concurrent path replacement or hardware failures.
Why an exists check is not the no-overwrite operation
A preflight existence check can help a user interface explain a likely collision. It does not reserve the filename. Another process can create the destination before a later copy call. The operation that opens or creates the output must enforce the no-clobber rule.
The documented shutil.copyfile behavior replaces an existing destination. Other high-level copying calls have their own semantics and metadata behavior; do not assume they gain exclusive creation just because an earlier check returned false. Handling FileExistsError is the direct collision path for the example above.
Choosing a numbered name also needs a collision policy. If you generate copy-2, copy-3 and so on, attempt exclusive creation for the chosen candidate and handle a collision rather than assuming the name stayed unused. Bound any retry policy and report the final selected name. The small example does not implement that naming system.
No overwrite is different from an all-or-nothing copy
The new output becomes visible when it is created. If reading or writing fails midway, a newly created partial file may remain. The xb mode protects an already existing file from truncation at creation time; it does not guarantee that every observer sees only a completed copy.
For a workflow that requires publishing only complete files, design and test a staging-and-publication operation with guarantees appropriate to the actual filesystem. Do not assume a generic rename both publishes atomically and refuses replacement on every platform. This guide does not provide or claim that stronger guarantee.
Likewise, do not blindly delete the destination on every exception. A collision means the file may belong to someone else. Cleanup must distinguish a file this operation created from a path it never owned, and account for the workflow's concurrency assumptions.
Decide what metadata and consistency matter
copyfileobj copies stream contents; the example is not a metadata-preserving equivalent of every shutil copying function. If timestamps, permissions, ownership, extended attributes or links matter, review those requirements separately. Copying metadata after byte copying can introduce additional failure modes and needs its own verification.
If another process changes the source while it is being read, a byte comparison with a later source read may not represent a consistent snapshot. A reliable versioning system needs a defined source-consistency model as well as unique filenames. Do not infer snapshot isolation from exclusive destination creation.
Completion checks for the limited regular-file workflow
- Confirm the actual source and destination paths and preserve any existing output.
- Open the new output exclusively and handle FileExistsError without overwriting.
- Close the stream before reading the output for comparison.
- Verify the intended bytes, then inspect metadata if the workflow requires it.
- Define what happens to a partial new file and how complete copies are reported.
Checked November 8, 2026 with Python 3.9.6 and local regular files. Existing-target preservation and a fresh byte copy were verified. Directory versioning, concurrent access, partial-failure cleanup, metadata preservation and atomic full-file publication were not tested.