Python之os模块案例详解
前言
os 模块是 Python 与操作系统打交道的入口:路径、目录、环境变量、进程信息、文件描述符,都从它这里走。它的特点是函数名短、参数少、但语义差异大——os.remove 和 os.rmdir 只差几个字母,一个删文件一个删空目录,用错就是一句报错甚至误删。
几个常见误解:以为 os.listdir() 返回排序后的结果(不保证);以为 os.walk() 的 dirnames 可以直接拿来当完整路径用(不行,需要 os.path.join);以为 os.environ 改了就写回系统了(只改当前进程)。
本文用「路径与环境 → 目录遍历 → 进程与系统信息」三个板块,把 os 模块最常用的部分配上可直接运行的示例讲清楚。示例面向 Python 3.8+。
一、路径、分隔符与环境变量
先说几个常量和函数,它们让代码能跨平台:
| 名称 | 含义 | 典型值 |
|---|
os.name | 操作系统大类 | 'posix'、'nt' |
os.sep | 路径分隔符 | '/' 或 '\\' |
os.pathsep | PATH 里的分隔符 | ':' 或 ';' |
os.linesep | 行分隔符 | '\n' 或 '\r\n' |
os.getcwd() 返回当前工作目录,os.chdir(path) 切换:
# 适用于 Python 3.8+
import os
print(os.getcwd())
os.chdir("..") # 切到上级目录
print(os.getcwd())注意 os.getcwd() 拿到的和脚本文件所在目录不是一回事。从别处执行脚本时,工作目录是「你在哪敲的命令」,不是「脚本放在哪」。要拿脚本自身的目录,得用 __file__ 推算。
环境变量通过 os.environ 这个映射对象访问:
# 适用于 Python 3.8+
import os
home = os.environ.get("HOME") or os.environ.get("USERPROFILE")
print(home)
os.environ["APP_MODE"] = "debug" # 只影响当前进程及其子进程os.environ[key] 在键不存在时抛 KeyError,所以一般用 os.environ.get(key, default) 或 os.getenv(key, default)。两者差别不大:os.getenv 就是 os.environ.get 的薄封装。
二、目录遍历:listdir、scandir、walk
os.listdir(path='.') 返回目录下所有条目名的列表,不含 '.' 和 '..',也不保证顺序。它返回的是名字,不是路径,要拼完整路径得自己 os.path.join:
# 适用于 Python 3.8+
import os
for name in os.listdir("."):
full = os.path.join(".", name)
print(full, os.path.isdir(full))上面这段代码对每个条目都要额外调一次 os.path.isdir,也就是一次系统调用。
os.scandir(path='.') 返回的是 os.DirEntry 迭代器,每个 entry 自带 name、path 属性和 is_file() / is_dir() / stat() 方法,这些信息在扫描目录时往往一并拿到了,所以能少掉大量额外调用。它同时是上下文管理器:
# 适用于 Python 3.8+
import os
with os.scandir(".") as it:
for entry in it:
if entry.is_file() and not entry.name.startswith("."):
print(entry.name)官方文档特别提醒:os.DirEntry 不适合长期保存,因为它的缓存会过期,需要最新信息时应改用 os.stat(entry.path)。
os.walk(top, topdown=True, onerror=None, followlinks=False) 是最常用的递归遍历工具。它对树里每个目录产出一个三元组 (dirpath, dirnames, filenames):
# 适用于 Python 3.8+
import os
from os.path import join, getsize
for root, dirs, files in os.walk("project"):
print(root, "里", len(files), "个文件")
total = sum(getsize(join(root, name)) for name in files)
print(" 合计", total, "字节")
if "__pycache__" in dirs:
dirs.remove("__pycache__") # 原地裁剪,跳过该子树三件事必须记住:dirnames 和 filenames 里都只是名字,不含路径;topdown=True 时原地修改 dirnames 可以剪枝(topdown=False 时改了没用);列表是否有序取决于文件系统,官方明确不保证。
三、进程与系统信息
os 里还有一批读取进程/系统状态的小工具,写脚本时很有用:
# 适用于 Python 3.8+
import os
print("进程号", os.getpid())
print("父进程号", os.getppid())
print("CPU 核心数", os.cpu_count())os.cpu_count() 返回逻辑 CPU 数量,在无法确定的平台上返回 None,拿它做多进程进程池大小时要判空。CPU 密集任务用 multiprocessing 才能真正并行;os.cpu_count() 常常就是进程数的起点。
创建目录用 os.mkdir 和 os.makedirs:
# 适用于 Python 3.8+
import os
os.mkdir("out") # 只建一层,父目录不存在会失败
os.makedirs("a/b/c", exist_ok=True) # 逐级创建,exist_ok=True 时已存在不报错实战:统计目录树里各类文件的数量与体积
下面这段脚本把 walk、路径判断和 stat 组合起来,输出扩展名分布:
# 适用于 Python 3.8+
import os
from collections import defaultdict
def summarize(top):
total = defaultdict(lambda: [0, 0]) # 扩展名 -> [个数, 字节数]
for root, dirs, files in os.walk(top):
dirs[:] = [d for d in dirs if not d.startswith(".")] # 剪掉隐藏目录
for name in files:
full = os.path.join(root, name)
ext = os.path.splitext(name)[1].lower() or "(无扩展名)"
try:
size = os.path.getsize(full)
except OSError:
continue # 断链、权限不足等,跳过
stat = total[ext]
stat[0] += 1
stat[1] += size
return total
if __name__ == "__main__":
for ext, (count, size) in sorted(summarize(".").items(),
key=lambda kv: -kv[1][1]):
print(f"{ext:<12} {count:>6} 个 {size:>12} 字节")这里用 dirs[:] = [...] 做切片赋值,是因为 os.walk 依赖原地修改 dirnames 列表对象来判断是否继续递归;直接 dirs = [...] 只是把局部名字重新绑定,walk 看不到,剪枝就失效了。
常见坑点
1. 以为 listdir 返回有序结果
❌ os.listdir(".")[0] 当「第一个文件」用,不同文件系统结果不同。 ✅ 需要顺序就自己排:sorted(os.listdir("."))。
2. 把 walk 的 filenames 当完整路径
❌ open(name) 直接打开,报文件找不到。 ✅ open(os.path.join(root, name))。
3. 剪枝时重新绑定而不是原地改
❌ dirs = [d for d in dirs if ...],os.walk 根本看不到。 ✅ dirs[:] = [d for d in dirs if ...],切片赋值改的是同一个列表。
4. os.environ[key] 取不存在的键
❌ os.environ["PATHX"] 抛 KeyError。 ✅ os.getenv("PATHX", "") 或 os.environ.get("PATHX", "")。
5. 把 getcwd() 当成脚本目录
❌ 用 os.getcwd() 拼资源路径,换个目录执行就找不到文件。 ✅ 用 os.path.dirname(os.path.abspath(__file__)) 定位脚本自身目录。
6. os.makedirs 不带 exist_ok
❌ 重复执行脚本时 FileExistsError 直接中断。 ✅ os.makedirs(path, exist_ok=True)。
7. os.cpu_count() 返回 None 时直接用
❌ Pool(os.cpu_count()) 在拿不到核心数的平台上会出错。 ✅ os.cpu_count() or 1,给一个安全兜底。
8. 用 os.remove 删目录
❌ os.remove("somedir"),抛 IsADirectoryError(POSIX 上)或 PermissionError(Windows 上)。 ✅ 删空目录用 os.rmdir,删整树用 shutil.rmtree。
总结
| 需求 | 函数 | 关键点 |
|---|
| 当前目录 | os.getcwd() | 是「执行位置」,不是脚本位置 |
| 列目录名 | os.listdir(path='.') | 无序、只有名字 |
| 带类型扫描 | os.scandir(path='.') | DirEntry,信息缓存会过期 |
| 递归遍历 | os.walk(top, ...) | dirnames 原地改可剪枝 |
| 读环境变量 | os.getenv(key, default) | 不存在返回默认值 |
| 建目录 | os.makedirs(name, exist_ok=True) | 逐级创建 |
| 系统信息 | os.cpu_count() | 可能返回 None |
os 模块本身不难,难在「返回的是名字还是路径」「顺序有没有保证」「列表改的是对象还是名字」这三类细节上。把 os.path.join 用熟、把 dirs[:] 记住,绝大多数问题都能避开。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)