The stat module in Python’s standard library interprets metadata returned by os.stat(), os.fstat(), and os.lstat(). That metadata includes object type, permissions, owner identifiers, size, link count, device, inode, and timestamps. The module is particularly useful when a program needs to evaluate several attributes without issuing repeated system calls.
This guide explains how to recognize regular files, directories, links, and special objects; extract permission bits; display readable modes; understand platform differences; and avoid security problems such as symbolic-link confusion and time-of-check/time-of-use races.
Reading metadata with os.stat
import os
info = os.stat("data.txt")
print(info.st_size)
print(info.st_mtime)
print(info.st_mode)The result behaves like a tuple but provides named attributes. Prefer the named form over historical indexes such as ST_SIZE, because it is easier to read and maintain.
Identifying the object type
The st_mode field combines object type and permission bits. Functions including S_ISREG(), S_ISDIR(), S_ISLNK(), S_ISSOCK(), S_ISFIFO(), S_ISCHR(), and S_ISBLK() test specific types.
import os
import stat
mode = os.lstat("shortcut").st_mode
if stat.S_ISLNK(mode):
print("symbolic link")
elif stat.S_ISDIR(mode):
print("directory")
elif stat.S_ISREG(mode):
print("regular file")Use lstat() when you need information about the link itself. stat() normally follows a symbolic link and describes its target.
Avoiding repeated system calls
Helpers such as os.path.isfile() and os.path.isdir() are convenient, but every test may require another filesystem query. When a program already has a stat result, reuse st_mode.
info = os.stat(path)
mode = info.st_mode
regular = stat.S_ISREG(mode)
directory = stat.S_ISDIR(mode)
permissions = stat.S_IMODE(mode)This pattern is useful in indexers, backup tools, scanners, file browsers, and inventory jobs that process thousands of entries.
Displaying a readable mode
stat.filemode() converts a mode to text similar to ls -l, such as -rw-r--r-- or drwxr-xr-x.
import os
import stat
info = os.stat("data.txt")
print(stat.filemode(info.st_mode))The first character describes the type. The remaining nine represent read, write, and execute bits for owner, group, and others. The string is excellent for logs and interfaces, but access decisions should rely on real APIs and policies.
Extracting configurable permission bits
S_IMODE() removes the file-type portion and retains permissions plus sticky, set-user-ID, and set-group-ID bits where supported.
current = os.stat("script.sh").st_mode
permissions = stat.S_IMODE(current)
print(oct(permissions))When calling os.chmod(), use deliberate masks. Do not blindly copy modes from untrusted files, especially special privilege bits.
Owner, group, and other permissions
The module defines constants such as S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, and S_IXOTH.
mode = os.stat("file.txt").st_mode
if mode & stat.S_IWOTH:
print("world writable")
if mode & stat.S_IXUSR:
print("owner executable")These bits describe the configured mode. They do not guarantee that the current process can access the object. ACLs, privileges, read-only mounts, sandboxing, and security frameworks also matter.
Sticky, setuid, and setgid
S_ISVTX represents the sticky bit. On directories such as /tmp, it restricts deletion and renaming. S_ISUID and S_ISGID have special Unix meanings.
Auditing tools may report these bits, but should not clear or add them automatically without a documented policy. A naive remediation can break software or introduce a vulnerability.
File size is type-dependent
For regular files, st_size is the byte length. For FIFOs and sockets on some Unix systems, it may report bytes waiting to be read. Device semantics vary further.
Before using the value to allocate memory or validate an upload, confirm that S_ISREG(st_mode) is true and enforce an independent maximum.
atime, mtime, and ctime
st_atime records last access, st_mtime records content modification, and st_ctime is platform-dependent. On Unix it generally represents the last metadata change; on Windows it has traditionally represented creation time.
Do not interpret ctime as portable creation time. Filesystems, mount options, and timestamp precision differ. For exact comparisons, prefer nanosecond fields such as st_mtime_ns.
Inodes, devices, and hard links
st_ino and st_dev help identify an object on compatible systems. st_nlink reports the number of hard links.
info = os.stat("data.txt")
identity = (info.st_dev, info.st_ino)
print(identity, info.st_nlink)The pair can help avoid processing the same inode twice during a scan, but it should not be persisted as a permanent global identifier because inode numbers may be reused.
Symbolic links and race conditions
A path can change between validation and use. Another process may replace a checked file, producing a TOCTOU race. For sensitive operations, prefer descriptor-based APIs, dir_fd, follow_symlinks=False, and secure open flags provided by the operating system.
Also validate the approved root and do not rely only on a previous file-type or extension check.
BSD and macOS flags
On compatible platforms, the module exposes flags such as UF_IMMUTABLE, UF_APPEND, UF_HIDDEN, and several SF_* values. Python 3.13 expanded some definitions.
Check availability with hasattr(stat, "UF_IMMUTABLE"). A constant’s presence does not guarantee identical behavior on every filesystem.
Windows file attributes
On Windows, stat results may include st_file_attributes and st_reparse_tag. Constants include FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_REPARSE_POINT, and known tags for links and mount points.
info = os.stat(path, follow_symlinks=False)
attributes = getattr(info, "st_file_attributes", 0)
if attributes & stat.FILE_ATTRIBUTE_HIDDEN:
print("hidden on Windows")Using getattr() keeps code importable on other platforms.
A practical inventory function
from pathlib import Path
import os
import stat
def describe(path: Path):
info = os.lstat(path)
mode = info.st_mode
if stat.S_ISLNK(mode):
kind = "link"
elif stat.S_ISDIR(mode):
kind = "directory"
elif stat.S_ISREG(mode):
kind = "file"
else:
kind = "special"
return {
"name": path.name,
"kind": kind,
"mode": stat.filemode(mode),
"size": info.st_size,
"mtime_ns": info.st_mtime_ns,
}The function uses lstat() so links are not followed. A complete application must still handle permission errors, disappearing files, and traversal limits.
Common mistakes
- Treating
st_ctimeas creation time everywhere. - Calling
stat()when the link itself should be inspected. - Trusting
st_sizewithout checking the object type. - Treating mode bits as a complete authorization answer.
- Repeating filesystem queries unnecessarily.
- Ignoring races between checking and using a path.
- Assuming platform-specific flags are universally available.
Recommended practices
- Reuse one stat result for multiple tests.
- Use
S_IS*helpers for types. - Use
S_IMODE()for permission extraction. - Use
filemode()for presentation only. - Prefer nanosecond timestamps for comparisons.
- Define an explicit symbolic-link policy.
- Test on every supported platform.
Related guides
Continue with Python filecmp, Python mmap, Python platform, Python sysconfig, and Python fnmatch.
See the official stat documentation and the os.stat documentation.
Conclusion
The stat module turns low-level modes and attributes into readable, portable tests. It is valuable for audits, backups, indexing, and system tools, but it must be combined with error handling, safe path policies, and APIs designed to resist race conditions.







