os.path.samefile
'a.txt', './a.txt', an absolute path and a hard link are four different strings but one file. samefile() stats both paths and compares device and inode; sameopenfile() does the same for file descriptors, samestat() for stat results you already have. Works on Linux, macOS and Windows.
Common call
os.path.samefile('a.txt', '/abs/path/to/a.txt')
Returns
True or False — raises if either path does not exist
Replaces
Comparing abspath()/realpath() strings
Watch out
Missing path → FileNotFoundError, not False
os.path.samefile(path1, path2, /) / os.path.sameopenfile(fp1, fp2) / os.path.samestat(stat1, stat2, /)
→ bool
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| path1, path2 | str | bytes | os.PathLike | yes | samefile: two paths; both are passed to os.stat(), so symlinks are followed and both must exist. |
| fp1, fp2 | int | yes | sameopenfile: two open file descriptors, e.g. f.fileno(). |
| stat1, stat2 | os.stat_result | yes | samestat: results of os.stat(), os.lstat() or os.fstat(). Only st_ino and st_dev are compared. |
Return value
bool — True when both refer to the same file or directory (same st_dev and st_ino).
Common patterns
Refuse to copy a file onto itself
shutil.copyfile does a similar check and raises SameFileError.
import os, shutil def safe_copy(src, dst): if os.path.exists(dst) and os.path.samefile(src, dst): raise ValueError(f'{src} and {dst} are the same file') shutil.copyfile(src, dst)
Did someone replace the file I opened?
Compare the open handle with what the path points to now (log rotation, atomic replace).
import os def was_replaced(f, path): try: return not os.path.samestat(os.fstat(f.fileno()), os.stat(path)) except FileNotFoundError: return True
Avoid visiting a directory twice
Track (st_dev, st_ino) pairs, the same key samestat compares.
import os seen = set() for entry in os.scandir('.'): st = entry.stat() key = (st.st_dev, st.st_ino) if key in seen: continue seen.add(key)
Examples
1. Different spellings, one file
import os
open('a.txt', 'w').close()
os.mkdir('sub')
os.path.samefile('a.txt', os.path.join('sub', '..', 'a.txt'))
Returns
True2. Relative vs absolute
import os
open('a.txt', 'w').close()
os.path.samefile('a.txt', os.path.abspath('a.txt'))
Returns
True3. Same contents is not the same file
import os
for name in ('a.txt', 'b.txt'):
with open(name, 'w') as f:
f.write('same text')
os.path.samefile('a.txt', 'b.txt')
Returns
False4. A hard link is the same file
import os
open('a.txt', 'w').close()
os.link('a.txt', 'hard.txt')
os.path.samefile('a.txt', 'hard.txt')
Returns
True5. sameopenfile: two handles on one file
import os
open('a.txt', 'w').close()
with open('a.txt') as f1, open('a.txt', 'rb') as f2:
result = os.path.sameopenfile(f1.fileno(), f2.fileno())
result
Returns
True6. sameopenfile: two different files
import os
open('a.txt', 'w').close()
open('b.txt', 'w').close()
with open('a.txt') as f1, open('b.txt') as f2:
result = os.path.sameopenfile(f1.fileno(), f2.fileno())
result
Returns
False7. samestat: fstat of a handle vs stat of a path
import os
open('a.txt', 'w').close()
with open('a.txt') as f:
result = os.path.samestat(os.fstat(f.fileno()), os.stat('a.txt'))
result
Returns
True8. samestat: a file and a directory
import os
open('a.txt', 'w').close()
os.path.samestat(os.stat('a.txt'), os.stat('.'))
Returns
FalsePitfalls
1. Comparing path strings
String comparison only sees spelling. './a.txt' and 'a.txt' are different strings for the same file.
==
open('a.txt', 'w').close() 'a.txt' == './a.txt'
False
samefile
import os open('a.txt', 'w').close() os.path.samefile('a.txt', './a.txt')
True
2. A missing path raises instead of returning False
samefile calls os.stat() on both paths. Check existence first (or catch the error) when one side may not exist yet.
samefile on a missing path
import os open('a.txt', 'w').close() try: os.path.samefile('a.txt', 'missing.txt') except OSError as e: result = type(e).__name__ result
'FileNotFoundError'
exists() first
import os open('a.txt', 'w').close() os.path.exists('missing.txt') and os.path.samefile('a.txt', 'missing.txt')
False
3. Using samefile to compare contents
Two copies with identical bytes are still two files. Compare contents with filecmp.
samefile
import os for name in ('a.txt', 'b.txt'): with open(name, 'w') as f: f.write('same text') os.path.samefile('a.txt', 'b.txt')
False
filecmp.cmp
import filecmp for name in ('a.txt', 'b.txt'): with open(name, 'w') as f: f.write('same text') filecmp.cmp('a.txt', 'b.txt', shallow=False)
True
When to use
Use it
- Refusing to copy, move or overwrite a file onto itself
- Detecting hard links and different spellings of the same path
- Checking whether an open file is still the one at its path (sameopenfile / samestat)
Reach for something else
- Comparing contents → filecmp.cmp(a, b, shallow=False)
- Paths that may not exist → compare os.path.realpath() strings instead
- pathlib code → Path.samefile(other)
Notes
CPython impl
genericpath.samefile = samestat(os.stat(f1), os.stat(f2)); sameopenfile uses os.fstat; samestat compares st_ino and st_dev only
Windows
Supported since 3.2 (samefile, sameopenfile) and 3.4 (samestat); since 3.4 Windows uses the same st_dev / st_ino implementation as other platforms
symlinks
samefile follows symlinks (os.stat). To ask whether a link itself is the same entry, compare os.lstat results with samestat
FAQ
The answer comes from the file system of the machine running the code, which a browser demo does not have. The examples create their own files and show only True/False, which is the same on Linux, macOS and Windows.