操作文件和目录

跨平台写 Python 脚本时,最常踩的坑就是硬编码路径分隔符适配系统命令,或者根本不知道当前代码跑在哪个操作系统上。这时候,os 家族(含 os.path)、shutil、还有 Python 3.4 起就超好用的 pathlib,就是你的救星!

这篇文章会带你梳理 Python 里文件和目录操作的核心工具链,顺便给出一堆安全提示和实用案例,让你写出的脚本清晰可靠,还能丝滑运行在 Windows / Linux / macOS 上。


操作系统类型检测

先搞清楚脚本的运行环境,才能做针对性适配——比如路径处理、调用系统命令等。

import os

# 获取最基础的系统标识
print(os.name)  # 输出结果只有两种

💡 常见标识对应的系统

  • posix:Linux、macOS、BSD 等 Unix-like 系统
  • nt:Windows 系统

如果需要获取更详细的内核、主机名等信息(仅支持 Unix-like 系统),可以这样:

if os.name == 'posix':
    print(os.uname())
    # 输出示例:posix.uname_result(sysname='Linux', nodename='ubuntu-server', 
    #                        release='6.5.0', version='#1 SMP PREEMPT_DYNAMIC', machine='x86_64')

⚠️ 注意:Windows 上直接调用 os.uname() 会报错,一定要先判断 os.name


环境变量操作

所有系统环境变量都存储在类字典结构 os.environ 里。
虽然它可以直接赋值,但强烈推荐用 .get() 方法,避免因变量不存在而抛出 KeyError

# ⚠️ 生产环境中不要随便打印所有环境变量,可能会泄露敏感信息!
# print(os.environ)

# 获取已知变量(推荐)
python_path = os.environ.get('PYTHONPATH')

# 获取不存在的变量,并指定默认值
default_config = os.environ.get('APP_CONFIG_PATH', '/etc/app/default.conf')

这样写不仅更安全,还让你能优雅地给出回退方案。


文件与目录操作

核心原则💡

永远不要手动拼接路径字符串(例如 '/Users/xxx' + 'test.txt')。
不同系统的分隔符不一样——Windows 用 \,Unix-like 用 /。手动拼接会破坏跨平台兼容性,让脚本悄无声息地崩掉。

下面介绍两个路径工具:经典但兼容性极好的 os.path,以及更现代、更优雅的 pathlib


经典路径工具:os.path

虽然现在更推荐 pathlib,但 os.path 兼容性最好(Python 2.x/3.x 通用),很多老项目还在用。掌握它依然很有必要:

# 1. 获取绝对路径
current_dir = os.path.abspath('.')
print(current_dir)  # 输出类似 /home/xxx/Desktop 或 C:\Users\xxx\Desktop

# 2. 跨平台路径拼接(自动适配分隔符)
full_path = os.path.join('/Users/xxx', 'docs', 'python', 'intro.txt')
print(full_path)  # Unix-like: /Users/xxx/docs/python/intro.txt;Windows 自动变为反斜杠

# 3. 拆分路径(目录 + 文件名/目录名)
parent_dir, last_part = os.path.split('/Users/xxx/docs/intro.txt')
print(f"父目录:{parent_dir},最后部分:{last_part}")

# 4. 拆分文件名(主名 + 扩展名)
root_name, ext = os.path.splitext('intro.txt')
print(f"主名:{root_name},扩展名:{ext}")

# 5. 常见属性检查
print(os.path.exists('test.txt'))     # True/False
print(os.path.isfile('test.txt'))     # True/False
print(os.path.isdir('my_project'))    # True/False

现代路径工具:pathlib(Python 3.4+ 首选)

pathlib面向对象的方式处理路径,把路径变成对象而不是字符串,代码更简洁、可读性更高,堪称 os.path 的全面替代品。

from pathlib import Path

# 1. 初始化路径对象
p = Path('.')                        # 当前目录
full_p = Path('/Users/xxx/docs/python/intro.txt')

# 2. 路径操作(链式调用,一气呵成)
parent_dir = full_p.parent
last_part  = full_p.name
root_name  = full_p.stem
ext        = full_p.suffix

# 3. 跨平台拼接(用 / 操作符,超级直观!)
new_p = Path('/Users/xxx') / 'docs' / 'python' / 'intro.txt'

# 4. 常见属性检查
print(new_p.exists())
print(new_p.is_file())
print(new_p.is_dir())

# 5. 快速遍历
py_files = list(p.glob('*.py'))       # 当前目录下的所有 .py 文件
all_py_files = list(p.rglob('*.py'))  # 递归遍历所有子目录里的 .py 文件

对新手来说,pathlib 的语法更像自然语言,推荐在新的 Python 项目里直接使用。


目录操作

不管用 os 还是 pathlib,底层逻辑差不多,但语法有区别。这里把两种方式放在一起,方便对比:

用 os 模块
# 1. 创建单级目录(已存在会报错)
os.mkdir('single_dir')

# 2. 创建多级目录(Python 3.2+ 推荐,加 exist_ok=True 已存在不报错)
os.makedirs('multi/level/dir', exist_ok=True)

# 3. 删除**空目录**(非空会报错)
os.rmdir('single_dir')

# 4. 列出目录下所有内容(不含子目录内容)
print(os.listdir('.'))
用 pathlib
p = Path('.')

# 1. 创建单级/多级目录(parents=True 自动创建父目录,exist_ok=True 已存在不报错)
(p / 'multi/level/dir').mkdir(parents=True, exist_ok=True)

# 2. 删除空目录
(p / 'single_dir').rmdir()

# 3. 列出目录下所有内容(返回 Path 对象列表,更方便后续操作)
print(list(p.iterdir()))

文件操作

用 os 模块
# 1. 重命名/移动文件(也可移动目录,但空目录推荐用 rmdir)
os.rename('old.txt', 'new.txt')
os.rename('src_dir/file.txt', 'dst_dir/file.txt')

# 2. 删除文件(删除目录会报错)
os.remove('file_to_delete.txt')
用 pathlib
old_p = Path('old.txt')
new_p = Path('new.txt')

# 1. 重命名/移动文件
old_p.rename(new_p)

# 2. 移动时如果目标目录还不存在,可以先创建
# (new_p.parent).mkdir(parents=True, exist_ok=True)
# old_p.rename(new_p)

# 3. 删除文件(Python 3.8+ 加 missing_ok=True,不存在也不报错)
old_p.unlink(missing_ok=True)

高级文件操作

os / pathlib 能覆盖基础需求,但像复制文件、递归删除、移动大目录这类进阶操作,最好交给 shutil 模块(“shell utilities”的缩写)。它封装了很多高效、安全的文件操作,比你自己手动实现靠谱得多。

import shutil

# 1. 复制文件(推荐 copy2,保留修改时间、权限等元数据)
shutil.copy2('source.txt', 'destination.txt')
shutil.copy2('source.txt', 'backup_dir/')  # 复制到指定目录

# 2. 递归复制整个目录(目标目录不能已存在!)
shutil.copytree('src_project', 'dst_project_backup')

# 3. 递归删除目录(⚠️ 超级危险!没有回收站!操作前务必确认!)
# shutil.rmtree('dir_to_delete')

# 4. 移动文件/目录(自动判断是复制+删除还是直接移动)
shutil.move('src', 'dst')

使用 shutil.rmtree 之前一定要确保变量名正确,最好加上二次确认,误删毁一生。


实用小技巧

1. 列出当前目录下的所有子目录

# os.path 版本
subdirs = [d for d in os.listdir('.') if os.path.isdir(d)]

# pathlib 版本(更直观)
subdirs = [d for d in Path('.').iterdir() if d.is_dir()]

2. 递归查找文件

比如找出当前目录和所有子目录里,文件名包含 log 的文件:

# os.walk 版本(Python 2.x/3.x 通用)
def find_logs(root='.'):
    for root_dir, _, files in os.walk(root):
        for f in files:
            if 'log' in f:
                print(os.path.relpath(os.path.join(root_dir, f)))

# pathlib 版本(更简洁)
def find_logs(root='.'):
    for log_file in Path(root).rglob('*log*'):
        if log_file.is_file():
            print(log_file.relative_to(root))

3. 计算目录总大小

# os.walk 版本
def get_dir_size(root='.'):
    total = 0
    for dirpath, _, filenames in os.walk(root):
        for f in filenames:
            fp = os.path.join(dirpath, f)
            # 跳过可能已不存在的临时文件,避免 os.path.getsize 报错
            if os.path.isfile(fp):
                total += os.path.getsize(fp)
    return total

# 一个简单的大小格式化工具(可选)
def format_size(size):
    for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
        if size < 1024.0:
            return f"{size:.2f} {unit}"
        size /= 1024.0

总结

  • 环境检测:用 os.name 快速判断系统,Unix-like 下可以进一步用 os.uname() 获取详细信息。
  • 路径处理:Python 3.4+ 首选 pathlib(面向对象、支持 / 操作符、链式调用),老项目或需兼容 Python 2 时用 os.path
  • 基础目录/文件操作:使用 ospathlib 中的对应方法,注意跨平台和exception-handling。
  • 复杂操作:复制、递归删除、移动等交给 shutil,功能强大且安全封装。
  • 安全提醒
    • 生产环境不要轻易打印 os.environ 的全部内容。
    • 删除操作(尤其是 rmtree)之前一定要做好判断或二次确认。
    • 操作前尽量用 .exists().is_file().is_dir() 检查状态,避免无谓异常。

掌握了这套工具链,无论是日常脚本还是项目代码,你都能写出既简洁又健壮的跨平台文件操作逻辑。