前言


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.pathsepPATH 里的分隔符':' 或 ';'

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[:] 记住,绝大多数问题都能避开。




Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐