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.

os.path functionPython 3 (Windows support 3.2 / 3.4, path-like 3.6+)
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

NameTypeRequiredDescription
path1, path2str | bytes | os.PathLikeyessamefile: two paths; both are passed to os.stat(), so symlinks are followed and both must exist.
fp1, fp2intyessameopenfile: two open file descriptors, e.g. f.fileno().
stat1, stat2os.stat_resultyessamestat: 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
True
2. Relative vs absolute
import os open('a.txt', 'w').close() os.path.samefile('a.txt', os.path.abspath('a.txt'))
Returns
True
3. 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
False
4. 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
True
5. 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
True
6. 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
False
7. 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
True
8. samestat: a file and a directory
import os open('a.txt', 'w').close() os.path.samestat(os.stat('a.txt'), os.stat('.'))
Returns
False

Pitfalls

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.