
The os.stat function in Python is your go-to tool for gathering metadata about files. Think of it as a detailed inspector report: size, permissions, timestamps, and more. When you call os.stat(path), it returns an object packed with attributes describing the file or directory at path.
Here’s a quick rundown of the most useful attributes from the result:
st_mode # File type and permissions st_ino # Inode number (unique file identifier on the filesystem) st_dev # Device identifier where the file resides st_nlink # Number of hard links st_uid # User ID of the file owner st_gid # Group ID of the file owner st_size # Size of the file in bytes st_atime # Time of last access (seconds since epoch) st_mtime # Time of last modification st_ctime # Time of last metadata change (on Unix) or creation (on Windows)
One subtlety worth remembering: on Unix-like systems, st_ctime is not the creation time but the last status change. On Windows, however, it represents file creation time. This distinction can trip you up when writing cross-platform code.
Here’s a simple example to demonstrate:
import os
import time
file_path = 'example.txt'
stats = os.stat(file_path)
print(f"File size: {stats.st_size} bytes")
print(f"Last modified: {time.ctime(stats.st_mtime)}")
print(f"Permissions: {oct(stats.st_mode)}") # Prints permissions in octal format
Using oct() on st_mode reveals the file permission bits, but it also includes file type info embedded in there. If you want just the permission bits, mask out the type bits with 0o777:
permissions = stats.st_mode & 0o777
print(f"Permissions only: {oct(permissions)}")
This especially important when you’re scripting file permission changes or checks, since the mode includes more than just rwx bits.
Under the hood, os.stat is a wrapper around the system call stat(), which means it’s a direct window into what the operating system knows about a file. This makes it much faster than opening a file just to inspect some metadata.
Also, remember that os.stat will raise a FileNotFoundError if the path doesn’t exist, so catching exceptions or checking existence beforehand is a good idea:
import os
path = 'foo.txt'
try:
info = os.stat(path)
except FileNotFoundError:
print(f"{path} does not exist.")
With these basics down, you have the foundation to start digging into more complex file management tasks, like scanning directories, filtering by size or date, or even syncing files based on modification times. But before that, it’s worth internalizing exactly what each stat field means in your context, since the same attribute can mean different things on different platforms.
Just to wrap the understanding of time stamps: st_atime, st_mtime, and st_ctime are all floats representing seconds since the Unix epoch. You should convert them to human-readable forms with time.ctime() or datetime.fromtimestamp() for anything user-facing.
One last trick before moving on – if you want to quickly check if a path points to a directory or a regular file without parsing st_mode yourself, Python’s stat module offers some helpers:
import os
import stat
info = os.stat('some_path')
if stat.S_ISDIR(info.st_mode):
print("It's a directory")
elif stat.S_ISREG(info.st_mode):
print("It's a regular file")
This saves you from bitmask gymnastics and makes your code cleaner and more readable. These little utilities are lifesavers when filtering files by type during scans or backups.
Knowing how to efficiently pull and interpret file metadata is the first step toward mastering file system operations programmatically. It’s the difference between blindly copying files and intelligently managing them based on real attributes. So get comfortable with os.stat—it’s the backbone of practically every file-related script you’ll write.
Next, we’ll look at using this knowledge in practical, everyday file management tasks, like cleaning up old files, detecting changes, and managing backups. But before that, imagine you want to recursively walk a directory tree and print out the sizes of all files above a certain threshold:
import os
def print_large_files(root_dir, min_size):
for dirpath, dirnames, filenames in os.walk(root_dir):
for filename in filenames:
full_path = os.path.join(dirpath, filename)
try:
size = os.stat(full_path).st_size
if size > min_size:
print(f"{full_path}: {size} bytes")
except FileNotFoundError:
# File might have been deleted between os.walk and os.stat
continue
This snippet highlights the power of combining os.stat with directory traversal to build tools that are both effective and efficient. The key is understanding the data you get back and how to interpret it correctly in your application’s context.
From here, you can extend this pattern to filter files by modification date, ownership, or even permissions. The sky’s the limit – once you know how to read the file’s metadata accurately, you can automate a ton of file system tasks that used to be manual drudgery.
We’re just scratching the surface, but at this point, you should be comfortable with what os.stat returns and how to use it to answer basic questions about files on your system. Now, let’s move on to applying these insights in practical scenarios where they’ll save you time and hassle, like cleaning up stale files or syncing directories based on modification times.
To kick things off, here’s how you might identify and delete files that haven’t been accessed in over 30 days:
import os
import time
def delete_old_files(directory, days=30):
cutoff = time.time() - days * 86400 # 86400 seconds in a day
for root, dirs, files in os.walk(directory):
for f in files:
path = os.path.join(root, f)
try:
if os.stat(path).st_atime < cutoff:
os.remove(path)
print(f"Deleted {path}")
except FileNotFoundError:
continue
This simple script leverages st_atime to make deletion decisions, showing how understanding file timestamps can transform file management into an automated process.
But be cautious – timestamps can be manipulated, and different filesystems behave differently. Always test thoroughly in your environment before running destructive operations like this.
Of course, you can enhance safety by moving files to a “trash” folder instead of deleting immediately, or by logging actions for audit purposes. But at the core is the ability to query file metadata accurately and efficiently, and that’s where os.stat shines.
Now that you’ve got the basics of os.stat down, let’s dive into how you can leverage it for practical file management tasks that go beyond just inspection and into automation and maintenance.
For instance, say you want to synchronize two directories but only copy files that have changed since the last sync. You’d start by comparing modification times:
import os
import shutil
def sync_dirs(src, dst):
for root, dirs, files in os.walk(src):
rel_path = os.path.relpath(root, src)
dest_dir = os.path.join(dst, rel_path)
os.makedirs(dest_dir, exist_ok=True)
for f in files:
src_file = os.path.join(root, f)
dst_file = os.path.join(dest_dir, f)
try:
src_mtime = os.stat(src_file).st_mtime
dst_mtime = os.stat(dst_file).st_mtime
if src_mtime > dst_mtime:
shutil.copy2(src_file, dst_file)
print(f"Updated {dst_file}")
except FileNotFoundError:
shutil.copy2(src_file, dst_file)
print(f"Copied new file {dst_file}")
This snippet shows how os.stat is fundamental in making decisions based on file metadata, enabling smart copying rather than blindly copying everything. The shutil.copy2 preserves the metadata during copy, which helps keep the timestamps intact for future comparisons.
Another common use case is checking file ownership and permissions before performing actions, useful in multi-user environments. Here’s a quick example that skips files not owned by the current user:
import os
current_uid = os.getuid()
def process_owned_files(directory):
for root, dirs, files in os.walk(directory):
for f in files:
path = os.path.join(root, f)
try:
stat_info = os.stat(path)
if stat_info.st_uid == current_uid:
print(f"Processing {path}")
# Do something with the file
except FileNotFoundError:
continue
This approach leverages st_uid to ensure scripts don’t accidentally tamper with files owned by others, which can be a major safety and security concern.
Permissions can be checked similarly if you want to skip files that aren’t writable or executable, using st_mode and the stat module’s helpers:
import stat
def is_writable(path):
try:
mode = os.stat(path).st_mode
return bool(mode & stat.S_IWUSR)
except FileNotFoundError:
return False
With these building blocks, you can write scripts that respect file ownership and permissions, avoiding common pitfalls when running automated maintenance tasks across diverse user environments.
It’s worth noting that os.stat is a snapshot in time. Files can be changed, deleted, or replaced right after you inspect them, so always design your code to handle exceptions gracefully and consider atomic operations where possible.
Up next, we’ll explore more advanced patterns for combining os.stat with other file system tools to build robust, reliable file management solutions that can handle real-world complexity and edge cases. But before moving on, remember that mastering the basics of os.stat means understanding what data it gives you and how to interpret it correctly. That foundation is what makes all the difference when you start automating file system tasks.
Here’s a quick recap of a utility function that extracts all the key information you might want from os.stat and formats it nicely for logging or debugging:
import os
import time
file_path = 'example.txt'
stats = os.stat(file_path)
print(f"File size: {stats.st_size} bytes")
print(f"Last modified: {time.ctime(stats.st_mtime)}")
print(f"Permissions: {oct(stats.st_mode)}") # Prints permissions in octal format
Use this whenever you want a quick human-readable summary of a file’s metadata. It’s a small helper that’ll save you time and keep your debugging info consistent.
One last thing before we pause: if you work on Windows, keep in mind that some fields like st_uid and st_gid might not behave as expected or even be zero, because Windows handles file permissions differently. Always test your code on the target platform to avoid surprises.
Understanding these nuances will save you hours chasing down weird bugs related to file metadata inconsistencies across platforms. And as always, when in doubt, consult the os and stat module docs—they’re packed with details not often covered in tutorials.
With that, you’ve got a solid grasp of what os.stat does, how to interpret its output, and the common pitfalls to watch out for. Next, we’ll dive into applying this knowledge to real-world file management tasks, like pruning old files, detecting changes for synchronization, and more. But first, let’s look at
Ailun 3 Pack Screen Protector for iPhone 17 Pro Max [6.9 inch] with Installation Frame, Tempered Glass, Sensor Protection, Dynamic Island Compatible, Case Friendly
$6.98 (as of June 13, 2026 06:05 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Using os.stat for practical file management tasks
how to efficiently detect file changes by comparing modification times and sizes. That is a common pattern in backup tools, deployment scripts, and file watchers.
Here’s a function that compares two files and determines if the source file is newer or has a different size than the destination file, indicating it needs to be updated:
import os
def file_needs_update(src, dst):
try:
src_stat = os.stat(src)
dst_stat = os.stat(dst)
except FileNotFoundError:
# If the destination file doesn’t exist, definitely update
return True
if src_stat.st_size != dst_stat.st_size:
return True
if src_stat.st_mtime > dst_stat.st_mtime:
return True
return False
This simple check avoids unnecessary copying and can be the basis for efficient synchronization logic. It’s lightweight and relies solely on metadata, so it’s fast even for large files.
Another practical tip: when dealing with many files, calling os.stat repeatedly can be expensive. If you’re scanning a directory, consider using os.scandir() which returns directory entries with stat info cached, reducing system calls:
import os
def list_large_files(directory, size_threshold):
with os.scandir(directory) as entries:
for entry in entries:
if entry.is_file():
if entry.stat().st_size > size_threshold:
print(f"{entry.name}: {entry.stat().st_size} bytes")
This approach is noticeably faster than calling os.stat on each file separately, especially on directories with thousands of entries.
When automating cleanup or archival, combining os.stat with file timestamps lets you filter files by age. For example, to move all files older than 90 days to an archive folder:
import os
import time
import shutil
def archive_old_files(src_dir, archive_dir, days=90):
cutoff = time.time() - days * 86400
os.makedirs(archive_dir, exist_ok=True)
for entry in os.scandir(src_dir):
if entry.is_file():
try:
if entry.stat().st_mtime < cutoff:
dest = os.path.join(archive_dir, entry.name)
shutil.move(entry.path, dest)
print(f"Archived {entry.name}")
except FileNotFoundError:
continue
Using shutil.move preserves the file’s metadata and is more efficient than copying and deleting.
For scripts that need to audit or report on file permissions, ownership, and types, formatting the st_mode bits into a human-readable string is handy. Here’s a compact utility function to do just that:
import stat
def mode_to_string(mode):
is_dir = 'd' if stat.S_ISDIR(mode) else '-'
perm_bits = [
(stat.S_IRUSR, 'r'), (stat.S_IWUSR, 'w'), (stat.S_IXUSR, 'x'),
(stat.S_IRGRP, 'r'), (stat.S_IWGRP, 'w'), (stat.S_IXGRP, 'x'),
(stat.S_IROTH, 'r'), (stat.S_IWOTH, 'w'), (stat.S_IXOTH, 'x'),
]
perms = ''.join([char if mode & bit else '-' for bit, char in perm_bits])
return is_dir + perms
Use it like this:
info = os.stat('somefile')
print(mode_to_string(info.st_mode)) # e.g. '-rw-r--r--' or 'drwxr-xr-x'
This mimics the familiar Unix permission string format and can be invaluable in logging or UI displays.
Finally, when you want to build robust scripts that handle edge cases gracefully, always anticipate potential errors such as permission denied, file not found, or symbolic link issues. Wrapping os.stat calls in try-except blocks and combining with other os and stat utilities will make your file management scripts bulletproof.
