Python获取文件大小:四种方法详解与实战选型指南

Python获取文件大小:四种方法详解与实战选型指南 1. 从“文件大小”这个看似简单的需求说起在Python里处理文件获取文件大小几乎是绕不开的基础操作。无论是写一个日志清理脚本还是开发一个文件同步工具又或者仅仅是检查一下下载的文件是否完整你都得先知道这个文件到底有多大。听起来很简单不就是读一个属性吗但真上手写的时候你会发现Python提供了不止一种方法从经典的os.path.getsize到面向对象的pathlib再到能挖出更多信息的os.stat甚至还有处理大文件的seek技巧。选哪个为什么选它每种方法背后有什么坑今天我就结合自己这些年写脚本、做工具的实际经验把这四种主流方法的里里外外、优劣取舍掰开揉碎了讲清楚。无论你是刚入门的新手还是想优化老代码的老鸟这篇文章都能给你带来直接可用的代码和避坑指南。2. 方法一os.path.getsize() – 最直接的“查询员”当我们提到获取文件大小os.path.getsize()通常是第一个映入脑海的函数。它属于os.path模块这个模块是Python标准库中处理路径名的“瑞士军刀”。它的工作方式极其简单你给它一个文件路径字符串它返回这个文件的大小单位是字节。2.1 基础用法与示例使用起来没有任何难度。假设我们有一个名为example.txt的文件。import os file_path example.txt size os.path.getsize(file_path) print(f文件大小是{size} 字节) print(f换算为KB是{size / 1024:.2f} KB) print(f换算为MB是{size / (1024*1024):.2f} MB)这就是全部了。函数返回的是一个整数int类型表示文件的字节数。对于大多数日常文件这个数字完全够用。2.2 工作原理与潜在陷阱os.path.getsize()底层调用的其实就是os.stat()系统调用获取文件的元数据stat结构体然后从中提取st_size字段。所以它的本质是一次系统查询。这里就引出了第一个也是最重要的一个坑它查询的是文件的“逻辑大小”而非“磁盘占用大小”。逻辑大小文件内容实际有多少字节。比如你新建一个文本文档输入“Hello World”保存逻辑大小就是11字节每个英文字母和空格占1字节。磁盘占用大小文件在硬盘上真正占用了多少物理空间。这个值通常大于或等于逻辑大小因为文件系统有“块大小”Block Size/Cluster Size的概念。例如你的硬盘块大小是4KB那么哪怕你只存了1字节的文件它在磁盘上也要独占4KB的空间。os.path.getsize()返回的是前者。如果你需要计算磁盘占用比如做磁盘清理这个方法就不准确了。在Linux下可以用os.statvfs结合块大小来计算Windows下思路类似但更复杂。第二个陷阱是符号链接软链接。如果你传给os.path.getsize()的是一个指向另一个文件的符号链接它返回的是链接目标文件的大小而不是链接文件本身的大小链接文件本身只是存储了一个路径字符串很小。这一点需要根据你的意图来区分。第三个是异常处理。如果文件不存在或者路径是一个目录是的目录也有大小但getsize对目录的行为在不同系统上可能不一致或报错或者你没有读取权限函数会抛出OSError异常。健壮的代码应该处理它import os file_path 可能不存在的文件.txt try: size os.path.getsize(file_path) print(f文件大小{size} 字节) except OSError as e: print(f无法获取文件大小{e})个人经验os.path.getsize()是我在快速脚本和小工具中最常用的方法因为它写起来最快意图最清晰。但只要是正式的项目代码我一定会把它包裹在try-except里并且心里清楚它返回的是“逻辑大小”。对于需要处理符号链接或精确磁盘占用的场景我会直接转向更底层的方法。3. 方法二os.stat() – 获取元数据的“信息库”如果说os.path.getsize()是一个专门查询文件大小的客服那os.stat()就是能调取文件完整档案的数据库管理员。它返回一个os.stat_result对象这个对象包含了文件的所有状态信息大小只是其中之一。3.1 深入stat_result对象调用os.stat(‘file_path’)后你会得到一个包含多个属性的对象。我们来看最常用的几个import os import time file_path example.txt stat_info os.stat(file_path) print(f文件大小 (st_size): {stat_info.st_size} 字节) print(f最后访问时间 (st_atime): {time.ctime(stat_info.st_atime)}) print(f最后修改时间 (st_mtime): {time.ctime(stat_info.st_mtime)}) print(f创建时间 (st_ctime): {time.ctime(stat_info.st_ctime)}) # 注意在Unix上是元数据修改时间在Windows上才是创建时间 print(f文件模式 (st_mode): {oct(stat_info.st_mode)}) # 权限信息 print(f文件inode号 (st_ino): {stat_info.st_ino}) # 仅在Unix系系统有效st_size属性就是我们需要的文件大小和os.path.getsize()的结果完全一样。3.2 为何选择os.stat()场景分析既然os.path.getsize()更简单为什么还要用os.stat()关键在于效率和信息整合。场景一你需要文件的多项属性。比如你要写一个类似ls -l的命令需要同时列出文件大小、修改时间、权限。如果分别用os.path.getsize(),os.path.getmtime(),os.path.getmode()你会对同一个文件发起三次独立的系统调用这在I/O上是低效的。而os.stat()一次调用就获取了所有信息性能更好。# 低效的做法 size os.path.getsize(file_path) mtime os.path.getmtime(file_path) mode os.path.getmode(file_path) # 高效的做法 stat_info os.stat(file_path) size stat_info.st_size mtime stat_info.st_mtime mode stat_info.st_mode场景二你需要处理符号链接本身。os.stat()默认跟随符号链接即查询链接指向的目标文件的信息。但os模块还提供了一个os.lstat()函数。lstat不跟随链接它返回的是符号链接文件自身的信息。如果你需要知道链接文件本身的大小即存储那个路径字符串的大小就必须用os.lstat()。import os # 假设 link_file 是一个符号链接 link_path link_file # 获取链接目标文件的大小 size_of_target os.stat(link_path).st_size print(f目标文件大小{size_of_target}) # 获取符号链接文件本身的大小 size_of_link_itself os.lstat(link_path).st_size print(f符号链接本身大小{size_of_link_itself}) # 通常很小几十到几百字节场景三跨平台兼容性细节。st_ctime在不同系统上的含义不同st_inoinode号在Windows上可能没有意义。当你使用os.stat()时意味着你正在处理更底层的系统接口需要对不同操作系统的行为有更清晰的了解。这既是强大之处也带来了复杂性。个人踩坑记录早期我曾写过一个备份脚本需要根据文件修改时间判断是否更新。我用了os.path.getmtime()后来为了加一个“仅备份大于1MB的文件”的功能又加了os.path.getsize()。脚本在测试时没问题但上线后处理数万个小文件时速度明显变慢。用性能分析工具一看I/O等待时间占了大部分。后来把所有属性查询合并到一个os.stat()调用里性能提升了近两倍。这个教训让我明白对于批量文件操作减少系统调用次数是优化的关键。4. 方法三pathlib.Path – 面向对象的“现代派”从Python 3.4开始pathlib模块被引入标准库它用面向对象的方式处理文件系统路径大大提升了代码的可读性和优雅度。对于获取文件大小它提供了.stat()方法和.stat().st_size属性以及一个便捷属性.stat().st_size的快捷方式.stat().st_size没错就是直接通过stat()结果访问。4.1 Path对象的基本操作首先你需要创建一个Path对象来表示你的文件。from pathlib import Path # 创建Path对象 file_path Path(example.txt) # 方法1: 通过 .stat() 方法 size file_path.stat().st_size print(f文件大小 (通过stat): {size} 字节) # 方法2: 直接使用 .stat().st_size (更常见的写法) size file_path.stat().st_size # 注意虽然 .stat().st_size 是常见写法但pathlib并没有一个单独的 .size 属性。 # 你需要调用 .stat() 方法。看起来和os.stat()差不多别急它的优势在于链式调用和更好的路径处理。4.2 pathlib的优势优雅与安全优势一清晰的链式操作与路径拼接。pathlib让复杂的路径操作变得直观。比如你想获取用户家目录下“文档”文件夹里某个子文件夹中文件的大小from pathlib import Path home Path.home() # 获取用户家目录跨平台 doc_path home / Documents / my_project / data.csv # 使用 / 运算符拼接路径 if doc_path.exists(): # 判断路径是否存在 size doc_path.stat().st_size print(f文件 {doc_path} 的大小是 {size} 字节) else: print(f文件 {doc_path} 不存在)这种写法比用os.path.join拼接字符串要清晰得多尤其是路径层次深的时候。优势二更好的异常处理集成。pathlib的许多方法在失败时会返回更友好的默认值或抛出更具体的异常。虽然.stat()在文件不存在时同样会抛出OSError或其子类FileNotFoundError但结合Path对象的其他方法可以写出更健壮的代码。from pathlib import Path file_path Path(some_file.txt) try: size file_path.stat().st_size except FileNotFoundError: print(f错误文件 {file_path} 未找到。) except PermissionError: print(f错误没有权限读取文件 {file_path}。) except OSError as e: print(f操作系统错误{e})优势三天然支持通配和遍历。pathlib的glob和rglob方法非常强大可以方便地获取目录下所有文件的大小总和。from pathlib import Path folder Path(./log_dir) total_size 0 # 使用 rglob(*) 递归遍历所有文件和子目录 # 注意这里会包含目录本身目录的 stat().st_size 可能不是你想要的值 for item in folder.rglob(*): if item.is_file(): # 只统计文件 total_size item.stat().st_size print(f目录 {folder} 下所有文件总大小{total_size} 字节 ({total_size / (1024**2):.2f} MB))个人迁移建议如果你是新启动的Python 3项目尤其是需要大量文件系统操作的我强烈推荐从pathlib开始。它的学习曲线平缓写出来的代码更像是在描述“做什么”而不是“怎么做”可维护性更高。对于老项目如果只是偶尔获取文件大小用os.path.getsize()也无妨但如果是重构或新增模块尝试引入pathlib会是一个不错的开始。我自己的工具脚本已经全面转向pathlib了那种流畅的路径操作体验用惯了就回不去。5. 方法四file.seek() 与 file.tell() – 手动测量的“工匠”前面三种方法都是通过查询文件系统的元数据来获取大小。但还有一种非常原始却绝对准确的方法打开文件把读写指针seek pointer直接挪到文件末尾然后看指针的位置。这个位置就是文件的字节长度。5.1 原理与基础代码这个方法利用的是文件对象file object的.seek()和.tell()方法。.seek(offset, whence)移动文件指针。whence2表示从文件末尾开始计算偏移。.tell()返回当前文件指针的位置从文件开头开始的字节偏移。def get_size_by_seek(filepath): with open(filepath, rb) as f: # 必须以二进制模式(rb)打开 # 将指针移动到文件末尾 (0字节偏移相对于末尾) f.seek(0, 2) # 获取当前指针位置即文件总大小 size f.tell() return size file_path example.zip size get_size_by_seek(file_path) print(f文件大小 (seek/tell): {size} 字节)5.2 为何需要这种方法特定场景剖析在绝大多数情况下你都不应该用这种方法。它需要打开文件产生I/O操作性能远不如直接查询元数据的os.stat()。那它存在的意义是什么场景一处理“类文件对象”file-like object。这是它最大的用武之地。有时候你拿到的不一定是一个磁盘文件路径而是一个已经在内存中的字节流BytesIO、一个网络请求返回的流式响应、或者一个由其他库生成的临时文件对象。这些对象可能有.seek()和.tell()方法但没有文件路径因此无法使用os.stat或pathlib。import io # 模拟一个来自网络或内存的字节流 data bThis is some binary data... * 1000 byte_stream io.BytesIO(data) # 我们想知道这个流里有多少数据 byte_stream.seek(0, 2) # 跳到末尾 size byte_stream.tell() # 获取大小 print(f字节流大小{size}) # 输出 27000 byte_stream.seek(0) # 记得将指针移回开头以便后续读取场景二极端情况下的兼容性或验证。虽然极其罕见但在某些特殊的文件系统或存储介质上文件系统的元数据可能不可靠或损坏。通过实际读取文件末尾来测量大小可以作为最后一道验证手段。另一种情况是当你以追加模式’a’打开一个文件并写入后想立刻知道文件的新大小此时文件对象内部的指针已经在末尾直接调用f.tell()即可获得写入后的大小这比重新stat一次更直接。场景三理解文件操作的底层原理。对于学习者来说这个方法直观地展示了文件“指针”和“字节偏移”的概念有助于理解更高级的文件操作比如随机访问、分块读取等。重要限制与坑点必须使用二进制模式’rb’如果以文本模式’r’打开由于编码如UTF-8的存在字节和字符的对应关系可能不一致seek和tell的行为在文本模式下是未定义的或者与编码相关绝对不能用于计算大小。性能开销对于大文件seek(0, 2)虽然不读取内容但仍然是一个系统调用并且打开文件本身就有开销。对于需要获取大量文件大小的场景这是最慢的方法。文件锁与权限打开文件可能需要相应的读写权限并且在某些系统上可能会对文件加锁影响其他进程。个人使用心得在我的日常开发中seek/tell方法的使用频率可能不到5%。它是我工具箱里的一个特殊工具专门用于处理“流”或“类文件对象”。99%的获取磁盘文件大小的需求请优先使用前三种方法。但当你遇到一个没有fileno()方法os.stat需要文件描述符的类文件对象时你会庆幸还知道有seek和tell这个组合技。6. 四种方法横向对比与选型指南现在我们把四位“选手”请到台前从不同维度做个全面对比这样你以后选型时就能一目了然。特性维度os.path.getsize()os.stat()pathlib.Pathfile.seek()/tell()导入模块import osimport osfrom pathlib import Path(内置无需额外导入)核心原理查询文件系统元数据 (st_size)查询文件系统元数据 (完整stat结构)查询文件系统元数据 (通过Path对象)打开文件并移动指针返回类型整数 (int)os.stat_result对象pathlib.Path.stat()返回os.stat_result整数 (int)信息量仅文件大小完整文件状态大小、时间、权限等完整文件状态通过.stat()仅文件大小符号链接跟随链接返回目标大小os.stat(): 跟随链接os.lstat(): 不跟随Path.stat(): 跟随链接Path.lstat(): 不跟随跟随链接操作的是目标文件性能高(一次系统调用)高(一次系统调用获取多项数据时效率最优)高(同os.stat但对象创建有微量开销)低(需要打开/关闭文件)可读性好函数名即用途中等需访问.st_size属性优秀面向对象链式调用较差需理解文件指针适用场景快速脚本只需文件大小需要文件多项属性批量操作优化现代Python项目复杂路径操作处理类文件对象如BytesIO无路径的流异常OSError(如FileNotFoundError)OSError(如FileNotFoundError)OSError(如FileNotFoundError)OSError(如FileNotFoundError,PermissionError)6.1 如何选择我的决策流程图面对一个具体需求我会这样思考你要操作的对象是什么如果是磁盘上的一个文件路径字符串进入第2步。如果是一个已经在内存或网络中的类文件对象如BytesIO,StringIO或某些库返回的流对象直接选择方法四seek/tell。这是唯一的选择。你只需要文件大小还是需要更多信息修改时间、权限等如果只需要大小并且代码是简单的脚本或一次性任务选择方法一os.path.getsize()。它最简洁。如果需要多项属性或者是在一个循环中批量处理文件即使只需要大小但后续可能扩展选择方法二os.stat()或方法三pathlib.Path。你的项目风格和Python版本是什么如果是新项目Python 3.4或者你希望代码更现代、更清晰特别是涉及路径拼接、遍历时强烈推荐方法三pathlib.Path。pathlib.stat().st_size的写法并不比os.stat()麻烦但整体代码风格更优。如果是维护旧代码Python 2/早期Python 3或者项目大量使用os.path为了保持一致性可以继续用方法一或方法二。一个综合示例假设我们要写一个函数计算一个目录下所有.log文件的总大小并列出其中最大的三个文件。from pathlib import Path import os def analyze_log_dir(directory): dir_path Path(directory) if not dir_path.is_dir(): print(f错误{directory} 不是一个有效的目录。) return file_sizes [] total_size 0 # 使用 glob 匹配 .log 文件 for log_file in dir_path.glob(*.log): try: # 使用 pathlib 获取大小 size log_file.stat().st_size file_sizes.append((log_file, size)) total_size size except OSError as e: print(f警告无法读取文件 {log_file}: {e}) print(f\n目录 {directory} 中 .log 文件分析结果) print(f文件总数{len(file_sizes)}) print(f总大小{total_size} 字节 ({total_size / (1024**2):.2f} MB)) if file_sizes: # 按文件大小排序从大到小 file_sizes.sort(keylambda x: x[1], reverseTrue) print(\n最大的三个文件) for i, (file_path, size) in enumerate(file_sizes[:3], 1): print(f {i}. {file_path.name}: {size} 字节 ({size / 1024:.2f} KB)) else: print(未找到 .log 文件。) # 使用示例 analyze_log_dir(./logs)在这个例子中我选择了pathlib因为它让路径匹配glob(‘*.log’)和文件检查is_dir()变得非常优雅并且.stat()一次性获取了所有需要的信息。代码清晰且易于扩展例如可以很容易地添加按修改时间过滤的功能。7. 进阶话题与常见问题排查掌握了基本方法后我们来看看一些更深入的问题和实际开发中容易遇到的坑。7.1 获取目录大小一个递归的挑战获取单个文件大小很简单但获取一个目录包含所有子目录和文件的总大小就需要递归遍历。上面第6节的示例已经展示了基本思路。这里再强调几个关键点区分os.path.getsize对目录的行为在大多数Unix系统上对目录使用os.path.getsize()可能返回一个很小的、不代表其内容总大小的值如4096即目录项本身的大小。在Windows上行为可能不同甚至报错。因此绝对不要用getsize来获取目录大小。正确的递归方法使用os.walk()或pathlib的rglob(‘*’)/glob(‘**/*’)进行递归遍历对每一个遍历到的路径判断它是否是文件os.path.isfile()或Path.is_file()如果是再累加其st_size。注意符号链接默认的遍历如os.walk和pathlib.glob不会跟随符号链接进入目录循环这是安全的。如果你需要跟随符号链接要特别小心并使用os.walk的followlinksTrue参数但要警惕可能导致的无限循环。7.2 处理“文件未找到”与权限错误无论用哪种方法文件不存在或没有读取权限都会导致异常。生产代码必须处理。import os from pathlib import Path def safe_get_size(path_str): 安全获取文件大小处理常见异常。 # 方法1: 使用 os.path.getsize try: size os.path.getsize(path_str) return size except FileNotFoundError: return f错误文件 {path_str} 不存在。 except PermissionError: return f错误没有权限读取文件 {path_str}。 except OSError as e: return f操作系统错误{e} # 方法2: 使用 pathlib (更推荐) # path Path(path_str) # if not path.exists(): # return f错误路径 {path_str} 不存在。 # if not path.is_file(): # return f错误{path_str} 不是一个文件。 # try: # return path.stat().st_size # except PermissionError: # return f错误没有权限读取文件 {path_str}。 # 测试 print(safe_get_size(./existing_file.txt)) print(safe_get_size(./non_existent_file.txt)) print(safe_get_size(/root/.bashrc)) # 普通用户无权限读取使用pathlib时可以先通过.exists()和.is_file()做初步检查但最终获取大小时仍需try-except因为权限错误发生在stat()调用时。7.3 大文件与整数溢出Python的应对在32位系统上C语言的long类型可能限制文件大小最大为2GB。但Python的int类型是任意精度的从Python 3开始所以os.stat().st_size返回的Pythonint对象可以表示任意大的整数不会溢出。这意味着即使在32位Python上你也能正确获取超过2GB、甚至TB级别文件的大小。这是Python语言本身提供的一个便利。7.4 性能考量批量操作的优化当你需要获取成千上万个文件的大小时性能变得重要。核心优化原则是减少系统调用次数和避免不必要的I/O。使用os.scandir()替代os.listdir()os.stat()os.scandir()在遍历目录时会在一次系统调用中尽可能多地获取文件信息包括大小如果系统支持。它返回的DirEntry对象可能有.stat()方法不触发额外系统调用或直接的.st_size属性效率远高于先列目录再逐个stat。import os def get_dir_size_fast(directory): total_size 0 with os.scandir(directory) as it: for entry in it: if entry.is_file(): # 如果系统支持entry有stat信息避免额外调用 # 否则entry.stat() 会触发一次系统调用但依然比 os.path.getsize 好 total_size entry.stat().st_size elif entry.is_dir(): # 递归处理子目录 sub_dir os.path.join(directory, entry.name) total_size get_dir_size_fast(sub_dir) return total_size并行处理对于海量文件可以考虑使用多进程如multiprocessing.Pool将目录树划分成多个部分并行统计。但要注意磁盘I/O本身可能成为瓶颈并行不一定总能提速甚至可能因磁盘寻道变慢。通常先尝试单线程优化如用scandir再考虑并行。最后一点经验之谈在Web应用或高频调用的后台服务中获取远程存储如S3、NFS上的文件大小时频繁的stat调用可能成为性能瓶颈和成本来源对象存储API调用可能收费。在这种情况下常见的优化策略是使用缓存缓存文件大小和mtime或者在设计协议/API时让列表操作直接返回文件大小等元数据避免为每个文件单独发起一次查询。这已经超出了单纯Python方法的范畴而是系统设计层面的考虑了。