os.stat

st_size is bytes, not characters. Times are float seconds since the epoch (st_mtime) or exact integer nanoseconds (st_mtime_ns). Test the file type with the stat module (stat.S_ISDIR(st.st_mode)). Some fields mean different things per OS — st_ctime is the metadata-change time on Unix.

os functionPython 3.0+ (st_*_ns 3.3+)Live demo
Common call
os.stat('data.csv').st_size
Returns
an os.stat_result
Replaces
Opening a file just to measure it; ls -l
Watch out
st_size counts bytes — UTF-8 text with accents is longer than len(text)
os.stat(pathpath — The file to inspect (an open file descriptor also works, like fstat).type: str | bytes | PathLike | int · required, *, dir_fd=None, follow_symlinksfollow_symlinks — False inspects a symlink itself — the same as lstat.type: bool · default: True=True) → stat_result / os.lstat(path) / os.fstat(fd) / os.utime(path, timestimes — utime only: (atime, mtime) in seconds; None means "now".type: tuple[float, float] | None · default: None=None, *, nsns — utime only: (atime_ns, mtime_ns) in integer nanoseconds. Do not pass both times and ns.type: tuple[int, int] · default: null=…)
→ os.stat_result

Demo

Live evaluation
Write some text as UTF-8 and compare st_size with the number of characters.
Try:
Inputs
textstrfile content
Code
import os
with open('data.txt', 'wb') as f:
    f.write('hello'.encode('utf-8'))
(os.stat('data.txt').st_size, len('hello'))
Result
(5, 5)

'héllo wörld' is 11 characters but 13 bytes, because é and ö take two bytes each in UTF-8, and the emoji takes four. The stat module turns st_mode into answers: S_ISDIR for folders, S_ISREG for regular files; a missing path raises FileNotFoundError. utime stores the time you give it and st_mtime reads it back as a float — use st_mtime_ns when you need exact integers. File systems limit the range: ext4 on Linux, for example, cannot store dates far in the future and clamps them.

Parameters

NameTypeRequiredDescription
pathstr | bytes | PathLike | intyesThe file to inspect (an open file descriptor also works, like fstat).
follow_symlinksboolno (True)False inspects a symlink itself — the same as lstat.
timestuple[float, float] | Noneno (None)utime only: (atime, mtime) in seconds; None means "now".
nstuple[int, int]noutime only: (atime_ns, mtime_ns) in integer nanoseconds. Do not pass both times and ns.

Return value

os.stat_result — st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size, st_atime, st_mtime, st_ctime (also by index 0–9), plus *_ns integer times and platform extras. utime returns None.

Common patterns

Size and modification time
One stat call, several answers.
import os, datetime
st = os.stat(path)
size_kb = st.st_size / 1024
modified = datetime.datetime.fromtimestamp(st.st_mtime, tz=datetime.timezone.utc)
Has the file changed?
Compare (size, mtime_ns) with what you saw last time.
import os
st = os.stat(path)
signature = (st.st_size, st.st_mtime_ns)
if signature != last_signature:
    reload()
touch
Create if missing, then set both times to now.
import os
with open(path, 'a'):
    os.utime(path, None)
Copy the timestamps of another file
ns= keeps full precision.
import os
src = os.stat('original.txt')
os.utime('copy.txt', ns=(src.st_atime_ns, src.st_mtime_ns))

Examples

1. Bytes, not characters
import os with open('data.txt', 'wb') as f: f.write('héllo'.encode('utf-8')) (os.stat('data.txt').st_size, len('héllo'))
Returns
(6, 5)
2. Folder or file?
import os, stat os.mkdir('d') st = os.stat('d') (stat.S_ISDIR(st.st_mode), stat.S_ISREG(st.st_mode))
Returns
(True, False)
3. Set and read back times
import os open('f', 'w').close() os.utime('f', (1000000000, 1700000000)) st = os.stat('f') (st.st_mtime, st.st_mtime_ns, st.st_atime)
Returns
(1700000000.0, 1700000000000000000, 1000000000.0)
4. A tuple of 10 fields too
import os open('f', 'w').close() st = os.stat('f') (type(st).__name__, len(st), st[6] == st.st_size)
Returns
('stat_result', 10, True)
5. fstat on an open file
import os with open('f', 'wb') as fh: fh.write(b'abc') fh.flush() size = os.fstat(fh.fileno()).st_size size
Returns
3
6. Like ls -l
import os, stat open('f', 'w').close() stat.filemode(os.stat('f').st_mode)[0]
Returns
'-'
7. utime(path) means now
import os, time open('f', 'w').close() os.utime('f', (0, 0)) os.utime('f') abs(os.stat('f').st_mtime - time.time()) < 60
Returns
True

Pitfalls

1. Using len() of the text as the file size
Files hold bytes. Any non-ASCII character takes more than one byte in UTF-8 (and Windows text mode turns \n into two bytes).
len(text)
text = 'naïve café'
len(text)
10
st_size
import os
text = 'naïve café'
with open('t.txt', 'wb') as f:
    f.write(text.encode('utf-8'))
os.stat('t.txt').st_size
12
2. Comparing float timestamps for equality
st_mtime is a float and cannot hold every nanosecond value exactly; st_mtime_ns is an exact int.
float seconds
ns = 1700000000123456789
int(ns / 1e9 * 1e9) == ns
False
integer ns
import os
open('f', 'w').close()
os.utime('f', ns=(0, 1700000000000000000))
os.stat('f').st_mtime_ns == 1700000000000000000
True
3. Catching every error as "missing"
stat raises FileNotFoundError for a missing path, but PermissionError and others are real problems. Catch the specific class.
except OSError
import os
try:
    os.stat('ghost.txt')
    state = 'present'
except OSError:
    state = 'missing?'
state
'missing?'
except FileNotFoundError
import os
try:
    os.stat('ghost.txt')
    state = 'present'
except FileNotFoundError:
    state = 'missing'
state
'missing'

When to use

Use it
  • Size, timestamps, type and mode bits of a file in one call
  • Setting timestamps (utime), e.g. after copying or for tests
Reach for something else
  • Only "is it a file / does it exist" → os.path.isfile / exists
  • Only the size → os.path.getsize (a stat underneath)
  • Copying metadata between files → shutil.copystat / copy2

Notes

CPython impl
stat, lstat, fstat and utime are C functions in Modules/posixmodule.c; stat_result is a struct sequence (a named-tuple-like type) whose first 10 items are the classic POSIX fields
st_ctime
Unix: last metadata change. Windows: creation time — deprecated in that meaning since 3.12, which added st_birthtime on Windows
Platform fields
st_blocks, st_blksize, st_rdev, st_flags on some Unix systems such as Linux; st_birthtime where available (on Windows since 3.12); st_file_attributes and st_reparse_tag on Windows
Resolution
Depends on the file system: ext4 keeps nanoseconds, NTFS 100 ns units (ns=1700000000123456789 reads back as 1700000000123456700), and FAT32 only 2 seconds for st_mtime

FAQ

os.stat(path).st_size, or the shortcut os.path.getsize(path) — both in bytes. Path(path).stat().st_size is the pathlib way.