在 Python 开发中,目录操作既是基础也是高频场景。无论是要读取配置、遍历日志,还是管理项目文件,都绕不开获取当前目录、列出目录内容、递归遍历等操作。Python 标准库提供了 os 和 pathlib 两个核心模块,前者是传统函数式接口,后者是 Python 3.4+ 引入的面向对象路径库。对于新项目,pathlib 更直观、更 Pythonic,但 os 模块在处理系统级交互时依然不可替代。本文从实际开发角度出发,系统梳理这两个模块在目录获取与操作中的典型用法。
获取当前工作目录是很多文件操作的第一步。os.getcwd() 返回字符串,而 pathlib.Path.cwd() 返回 Path 对象,后者可以直接调用 .parent、.joinpath() 等方法。示例代码如下:
- import os
- current_dir = os.getcwd()
- print(f"当前工作目录: {current_dir}")
- from pathlib import Path
- current_path = Path.cwd()
- print(f"当前工作目录: {current_path}")
- print(f"上级目录: {current_path.parent}")
复制代码
列出目录内容时,os.listdir(path) 仅返回文件名组成的字符串列表,不会包含路径信息。若要判断条目是文件还是目录,还需要配合 os.path.isdir() 或 os.path.isfile()。pathlib.Path.iterdir() 则返回 Path 对象生成器,每个条目自带完整路径,可以直接调用 .is_file()、.is_dir()、.name 等属性,更适合做进一步处理。
- import os
- path = "./data"
- try:
- entries = os.listdir(path)
- for entry in entries:
- print(entry)
- except FileNotFoundError:
- print(f"目录 '{path}' 不存在")
- from pathlib import Path
- path_obj = Path("./data")
- if path_obj.exists() and path_obj.is_dir():
- for entry in path_obj.iterdir():
- print(f"{entry.name} - {'目录' if entry.is_dir() else '文件'}")
复制代码
递归遍历目录树时,os.walk() 是经典方案,它为每个目录返回 (dirpath, dirnames, filenames) 三元组。pathlib 则推荐使用 rglob() 和 glob()。rglob(pattern) 会递归匹配所有层级,glob(pattern) 仅匹配当前目录。例如递归查找所有 .py 文件:
- import os
- for dirpath, dirnames, filenames in os.walk("./project"):
- print(f"目录: {dirpath}")
- print(f"子目录: {dirnames}")
- print(f"文件: {filenames}")
- print("-" * 40)
- from pathlib import Path
- project = Path("./project")
- py_files = list(project.rglob("*.py"))
- for f in py_files:
- print(f)
复制代码
获取绝对路径也很关键。相对路径容易受脚本启动位置影响,使用绝对路径更可靠。os.path.abspath() 可以把相对路径转为绝对路径;pathlib 的 resolve() 不仅返回绝对路径,还会解析符号链接。
- import os
- abs_path = os.path.abspath("./docs/readme.txt")
- print(abs_path)
- from pathlib import Path
- abs_path2 = Path("./docs/readme.txt").resolve()
- print(abs_path2)
复制代码
在操作前检查路径是否是目录,可以提前避免很多错误。os.path.isdir() 接收字符串,Path.is_dir() 是实例方法,两者注意区分。
创建目录时,os.makedirs() 和 Path.mkdir() 都支持递归创建多级目录。os.makedirs() 需要设置 exist_ok=True 才能避免目录已存在时抛异常;Path.mkdir() 需要设置 parents=True 才能递归创建父目录,同时配合 exist_ok=True 避免冲突。删除目录方面,os.rmdir() 只能删除空目录,非空目录必须用 shutil.rmtree(),该操作不可逆,要特别谨慎。
- import os
- from pathlib import Path
- import shutil
- os.makedirs("./parent/child/grandchild", exist_ok=True)
- Path("./deep/nested/path").mkdir(parents=True, exist_ok=True)
- os.rmdir("./empty_dir") # 仅空目录
- shutil.rmtree("./dir_with_content") # 递归删除,危险!
复制代码
路径拼接与分解在跨平台开发中尤为重要。直接使用字符串拼接“/”在 Windows 上可能出错。os.path.join() 能根据操作系统自动选择分隔符,pathlib 则直接用“/”运算符重载,更为优雅。
- import os
- full_path = os.path.join("/home/user", "docs", "file.txt")
- print(full_path)
- from pathlib import Path
- base = Path("/home/user")
- full_path2 = base / "docs" / "file.txt"
- print(full_path2)
- print(f"父目录: {full_path2.parent}")
- print(f"文件名: {full_path2.name}")
- print(f"后缀: {full_path2.suffix}")
- print(f"无后缀文件名: {full_path2.stem}")
复制代码
实战中,统计目录下文件类型分布是常见需求。通过 rglob("*") 遍历全部条目,筛选出文件,再按后缀名分组计数,很容易实现:
- from pathlib import Path
- from collections import Counter
- def analyze_directory(directory_path):
- path = Path(directory_path)
- if not path.exists() or not path.is_dir():
- print(f"错误: '{directory_path}' 不是有效目录")
- return
- extensions = []
- for item in path.rglob("*"):
- if item.is_file():
- ext = item.suffix.lower() or "无后缀"
- extensions.append(ext)
- if not extensions:
- print("该目录下未找到文件。")
- return
- counter = Counter(extensions)
- print(f"目录 '{directory_path}' 下的文件类型统计:")
- for ext, count in counter.most_common():
- print(f" {ext}: {count} 个文件")
- analyze_directory("./my_project")
复制代码
总结几点最佳实践:优先使用 pathlib,尤其在新项目中,它封装了路径对象,方法链式调用更清晰;文件系统操作必须处理异常,如权限不足、路径不存在,用 try/except 包裹关键步骤;脚本中涉及关键路径时尽量使用绝对路径或 resolve(),避免工作目录变化导致找不到文件;递归操作如 rglob() 和 shutil.rmtree() 要反复确认目标路径,防止误删;跨平台项目统一使用 os.path.join() 或 pathlib 的“/”操作符,让代码在 Windows 和 Linux/macOS 上都能正确处理路径分隔符。
掌握这些目录操作细节后,后续编写文件处理脚本、日志分析工具或自动化构建流程都会更加得心应手。 |