26.Python os.path 完全指南

文章目录

    • 第一部分:路径是什么?为什么要处理路径?
      • [1.1 文件路径的基本概念](#1.1 文件路径的基本概念)
      • [1.2 三个核心角色](#1.2 三个核心角色)
    • 第二部分:os.path.abspath()
      • [2.1 基本用法](#2.1 基本用法)
      • [2.2 abspath 的工作原理](#2.2 abspath 的工作原理)
    • 第三部分:os.path.dirname()
      • [3.1 基本用法](#3.1 基本用法)
      • [3.2 dirname 的直觉理解](#3.2 dirname 的直觉理解)
      • [3.3 多级目录向上走](#3.3 多级目录向上走)
    • [第四部分:file 魔法变量](#第四部分:file 魔法变量)
      • [4.1 file 是什么?](#4.1 file 是什么?)
      • [4.2 file 在不同情况下的行为](#4.2 file 在不同情况下的行为)
    • 第五部分:核心组合------dirname(abspath(file))
      • [5.1 这行代码的完整解析](#5.1 这行代码的完整解析)
      • [5.2 实际项目应用------构建跨平台路径](#5.2 实际项目应用——构建跨平台路径)
      • [5.3 完整的项目路径配置模板](#5.3 完整的项目路径配置模板)
      • [5.4 为什么不直接用 os.getcwd()?](#5.4 为什么不直接用 os.getcwd()?)
    • [第六部分:os.path 其他常用函数](#第六部分:os.path 其他常用函数)
      • [6.1 os.path.join()------路径拼接(跨平台神器)](#6.1 os.path.join()——路径拼接(跨平台神器))
      • [6.2 os.path.basename()------获取文件名](#6.2 os.path.basename()——获取文件名)
      • [6.3 os.path.split()------同时获取目录和文件名](#6.3 os.path.split()——同时获取目录和文件名)
      • [6.4 os.path.splitext()------分离文件名和扩展名](#6.4 os.path.splitext()——分离文件名和扩展名)
      • [6.5 os.path.exists() / isfile() / isdir()------判断路径](#6.5 os.path.exists() / isfile() / isdir()——判断路径)
      • [6.6 os.path.getsize()------获取文件大小](#6.6 os.path.getsize()——获取文件大小)
      • [6.7 os.path.normpath()------规范化路径](#6.7 os.path.normpath()——规范化路径)
      • [6.8 os.path.relpath()------计算相对路径](#6.8 os.path.relpath()——计算相对路径)
      • [6.9 os.path.commonpath() / commonprefix()------公共路径](#6.9 os.path.commonpath() / commonprefix()——公共路径)
      • [6.10 os.path.expanduser()------展开用户目录](#6.10 os.path.expanduser()——展开用户目录)
    • [第七部分:os.path 综合实战](#第七部分:os.path 综合实战)
      • [7.1 实战一:扫描目录,按类型归类文件](#7.1 实战一:扫描目录,按类型归类文件)
      • [7.2 实战二:递归搜索文件](#7.2 实战二:递归搜索文件)
      • [7.3 实战三:路径处理工具函数集合](#7.3 实战三:路径处理工具函数集合)
    • 第八部分:pathlib------更现代的路径写法
      • [8.1 pathlib 简介(Python 3.4+)](#8.1 pathlib 简介(Python 3.4+))
      • [8.2 pathlib 核心操作](#8.2 pathlib 核心操作)
      • [8.3 os.path 和 pathlib 对照表](#8.3 os.path 和 pathlib 对照表)
    • [第九部分:Windows / Linux 跨平台注意事项](#第九部分:Windows / Linux 跨平台注意事项)
      • [9.1 路径分隔符问题](#9.1 路径分隔符问题)
      • [9.2 大小写问题](#9.2 大小写问题)
      • [9.3 路径长度限制](#9.3 路径长度限制)
    • 第十部分:完整速查表
      • [10.1 函数速查](#10.1 函数速查)
      • [10.2 pathlib 替代写法(现代推荐)](#10.2 pathlib 替代写法(现代推荐))
    • 总结

https://www.quanzhankaige.com/python26/

本文档面向零基础新手,目标是让你真正理解:

  • os.path.dirname() 是什么,怎么用
  • os.path.abspath() 是什么,怎么用
  • __file__ 是什么魔法变量
  • os.path.dirname(os.path.abspath(__file__)) 为什么是 Python 项目的"万能定位公式"
  • os.path 其他常用函数(joinexistsbasenamesplit 等)
  • Windows / Linux / macOS 路径差异与跨平台写法
  • pathlib:更现代的路径写法(Python 3.4+)

配有大量可运行示例,全部从最基础讲起。


第一部分:路径是什么?为什么要处理路径?

1.1 文件路径的基本概念

复制代码
文件路径就是"文件在磁盘上的地址",分两种:

绝对路径(Absolute Path):
  从磁盘根目录开始的完整地址
  Windows:C:\Users\Administrator\Desktop\project\main.py
  Linux:  /home/user/project/main.py
  macOS:  /Users/user/project/main.py

相对路径(Relative Path):
  相对于"当前工作目录"的地址
  ./config.json        → 当前目录下的 config.json
  ../data/input.csv    → 上一级目录的 data 文件夹里的 input.csv
  data/output.txt      → 当前目录的 data 子目录下

为什么新手经常遇到"找不到文件"的错误?

python 复制代码
# 假设你的项目结构是:
# project/
# ├── main.py
# └── data/
#     └── input.txt

# main.py 里这样写:
with open('data/input.txt') as f:
    content = f.read()

# 问题来了:这里的 'data/input.txt' 是相对路径
# 它相对的是你"运行 python 命令时所在的目录"
# 而不是 main.py 文件所在的目录!

# 在 project/ 下运行 python main.py   ✅ 成功
# 在 /home/user/ 下运行 python project/main.py   ❌ 报错!
# 因为此时当前目录是 /home/user/,找不到 data/input.txt

这就是为什么要用 os.path.dirname(os.path.abspath(__file__))------它总能找到脚本自己的位置!


1.2 三个核心角色

复制代码
os.path.abspath(__file__)                → 当前脚本的绝对路径(含文件名)
os.path.dirname(os.path.abspath(__file__)) → 当前脚本所在的目录
os.path.join(当前目录, '相对子路径')      → 拼接成完整路径

举个例子:
  脚本位置:C:\project\src\utils.py

  __file__                              = 'utils.py'(或相对路径)
  os.path.abspath(__file__)             = 'C:\project\src\utils.py'
  os.path.dirname(os.path.abspath(...)) = 'C:\project\src'

  然后就可以:
  os.path.join('C:\project\src', '..', 'data', 'input.txt')
                                        = 'C:\project\data\input.txt'

第二部分:os.path.abspath()

2.1 基本用法

os.path.abspath(path)任意路径 转换成绝对路径

python 复制代码
import os

# ===== 基础示例 =====

# 当前工作目录假设是 C:\Users\Admin\Desktop

# 相对路径 → 绝对路径
print(os.path.abspath('test.txt'))
# C:\Users\Admin\Desktop\test.txt

print(os.path.abspath('./data/input.csv'))
# C:\Users\Admin\Desktop\data\input.csv

print(os.path.abspath('../other_folder'))
# C:\Users\Admin\other_folder   (上一级目录)

print(os.path.abspath('.'))
# C:\Users\Admin\Desktop        (当前目录)

print(os.path.abspath('..'))
# C:\Users\Admin                (上一级目录)

# 已经是绝对路径 → 原样返回(只是规范化格式)
print(os.path.abspath('C:/Users/Admin/Desktop'))
# C:\Users\Admin\Desktop        (正斜杠变反斜杠,Windows 上规范化)

2.2 abspath 的工作原理

python 复制代码
import os

# abspath 的等价实现(理解原理):
# os.path.abspath(path) ≈ os.path.normpath(os.path.join(os.getcwd(), path))

cwd  = os.getcwd()      # 获取当前工作目录
path = '../data'

# 方式1:abspath(推荐)
result1 = os.path.abspath(path)

# 方式2:手动实现(效果相同)
result2 = os.path.normpath(os.path.join(cwd, path))

print(result1)
print(result2)
print(result1 == result2)   # True

重要:abspath 基于的是"当前工作目录",不是脚本所在目录!

python 复制代码
import os

# 假设当前工作目录 = C:\Users\Admin(从这里启动 Python)
# 脚本位置        = C:\project\src\utils.py

# ❌ 这个结果取决于你从哪里运行脚本,不可靠
result = os.path.abspath('data/input.txt')
# 结果:C:\Users\Admin\data\input.txt
# 而不是:C:\project\src\data\input.txt   ← 你想要的

# ✅ 正确做法:先获取脚本所在目录,再拼接
script_dir = os.path.dirname(os.path.abspath(__file__))
result     = os.path.join(script_dir, 'data', 'input.txt')
# 结果:C:\project\src\data\input.txt   ← 无论从哪里运行都对!

第三部分:os.path.dirname()

3.1 基本用法

os.path.dirname(path) 返回路径中的目录部分(去掉最后一个文件名或目录名)。

python 复制代码
import os

# ===== Windows 路径示例 =====
print(os.path.dirname(r'C:\Users\Admin\Desktop\main.py'))
# C:\Users\Admin\Desktop

print(os.path.dirname(r'C:\Users\Admin\Desktop'))
# C:\Users\Admin

print(os.path.dirname(r'C:\Users'))
# C:\

print(os.path.dirname(r'C:\\'))
# C:\

# ===== Linux/macOS 路径示例 =====
print(os.path.dirname('/home/user/project/main.py'))
# /home/user/project

print(os.path.dirname('/home/user/project'))
# /home/user

print(os.path.dirname('/home'))
# /

print(os.path.dirname('/'))
# /   (根目录的父目录还是根目录)


# ===== 只有文件名(无目录)=====
print(os.path.dirname('main.py'))
# ''(空字符串!)
print(repr(os.path.dirname('main.py')))
# ''

# ===== 结尾有斜杠的目录 =====
print(os.path.dirname('/home/user/project/'))
# /home/user/project   (去掉了结尾的斜杠)

3.2 dirname 的直觉理解

复制代码
把路径想象成一棵树,dirname 就是"找父目录":

  C:\project\src\utils.py
  ↑            ↑  ↑
  根           目录 文件名

  os.path.dirname('C:\\project\\src\\utils.py')
  = 'C:\\project\\src'     ← 去掉了最后的 \utils.py

  再来一次:
  os.path.dirname('C:\\project\\src')
  = 'C:\\project'          ← 去掉了最后的 \src

  再来一次:
  os.path.dirname('C:\\project')
  = 'C:\\'                 ← 去掉了 \project,只剩根目录

3.3 多级目录向上走

python 复制代码
import os

# 从脚本路径逐级向上
path = r'C:\project\src\utils\helper.py'

print("原始路径:", path)
print("上一级:  ", os.path.dirname(path))
print("上两级:  ", os.path.dirname(os.path.dirname(path)))
print("上三级:  ", os.path.dirname(os.path.dirname(os.path.dirname(path))))
print("上四级:  ", os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(path)))))

# 输出:
# 原始路径:  C:\project\src\utils\helper.py
# 上一级:    C:\project\src\utils
# 上两级:    C:\project\src
# 上三级:    C:\project
# 上四级:    C:\

嵌套太多?用循环代替:

python 复制代码
import os

def go_up(path, levels=1):
    """从路径向上走 levels 级"""
    result = path
    for _ in range(levels):
        result = os.path.dirname(result)
    return result


path = r'C:\project\src\utils\helper.py'

print(go_up(path, 1))   # C:\project\src\utils
print(go_up(path, 2))   # C:\project\src
print(go_up(path, 3))   # C:\project
print(go_up(path, 4))   # C:\

第四部分:file 魔法变量

4.1 file 是什么?

__file__ 是 Python 自动设置的模块级别内置变量 ,代表当前脚本文件的路径

python 复制代码
# 在某个脚本文件(如 utils.py)中:
print(__file__)

# 可能的输出(取决于运行方式):
# utils.py                          ← 在脚本所在目录运行
# src/utils.py                      ← 从上级目录运行
# /home/user/project/src/utils.py   ← 某些环境的完整路径

关键点:__file__ 的值不稳定,可能是相对路径或绝对路径!

复制代码
运行方式不同,__file__ 不同:

  场景1:在 C:\project 下运行 python src\utils.py
    → __file__ = 'src\\utils.py'(相对路径)

  场景2:用 IDE(如 PyCharm、VS Code)运行
    → __file__ = 'C:\\project\\src\\utils.py'(绝对路径)

  场景3:在 C:\project\src 下运行 python utils.py
    → __file__ = 'utils.py'(只有文件名)

这就是为什么要先用 abspath() 把它转成绝对路径!

4.2 file 在不同情况下的行为

python 复制代码
# ===== 普通脚本文件中 =====
# 文件:C:\project\main.py
print(__file__)   # 可能是 main.py 或 C:\project\main.py

# ===== 作为模块被导入时 =====
# 文件:C:\project\utils.py,被 main.py 导入
import utils
print(utils.__file__)   # C:\project\utils.py(导入时通常是绝对路径)

# ===== 在交互式解释器中 =====
# 在 Python REPL (>>>提示符) 中:
# print(__file__)   → NameError: name '__file__' is not defined
# 交互式环境没有 __file__!

# ===== 在 Jupyter Notebook 中 =====
# print(__file__)   → 也会报错,因为 Notebook 不是普通脚本文件

第五部分:核心组合------dirname(abspath(file))

5.1 这行代码的完整解析

python 复制代码
import os

# 分步理解每一层:
# 假设脚本是 C:\project\src\app.py

# 第1步:__file__ 获取当前文件路径(可能不稳定)
step1 = __file__
print(f"__file__           = {step1!r}")
# 可能是:'app.py'  或  'src\\app.py'  或  'C:\\project\\src\\app.py'

# 第2步:abspath() 将其转为绝对路径(稳定!)
step2 = os.path.abspath(__file__)
print(f"abspath(__file__)  = {step2!r}")
# 一定是:'C:\\project\\src\\app.py'

# 第3步:dirname() 去掉文件名,只保留目录
step3 = os.path.dirname(os.path.abspath(__file__))
print(f"dirname(abspath()) = {step3!r}")
# 一定是:'C:\\project\\src'

# 结论:step3 就是"无论怎么运行,都能正确找到脚本所在目录"

这是 Python 项目中最常见的路径定位方式,务必牢记!


5.2 实际项目应用------构建跨平台路径

python 复制代码
import os

# ==================================================
# 典型项目结构:
# myproject/
# ├── src/
# │   ├── app.py        ← 当前文件
# │   └── utils.py
# ├── config/
# │   └── settings.json
# ├── data/
# │   └── input.csv
# └── logs/
#     └── app.log
# ==================================================

# 当前脚本所在目录(myproject/src/)
THIS_DIR    = os.path.dirname(os.path.abspath(__file__))

# 项目根目录(myproject/)
PROJECT_DIR = os.path.dirname(THIS_DIR)

# 常用目录的绝对路径
CONFIG_DIR  = os.path.join(PROJECT_DIR, 'config')
DATA_DIR    = os.path.join(PROJECT_DIR, 'data')
LOG_DIR     = os.path.join(PROJECT_DIR, 'logs')

# 具体文件的绝对路径
SETTINGS    = os.path.join(CONFIG_DIR, 'settings.json')
INPUT_CSV   = os.path.join(DATA_DIR,   'input.csv')
LOG_FILE    = os.path.join(LOG_DIR,    'app.log')

# 无论从哪里运行脚本,这些路径永远正确!
print(f"脚本目录:{THIS_DIR}")
print(f"项目根目录:{PROJECT_DIR}")
print(f"配置文件:{SETTINGS}")
print(f"数据文件:{INPUT_CSV}")
print(f"日志文件:{LOG_FILE}")


# 使用这些路径读写文件(永远不会因为"从哪里运行"而出错)
import json

with open(SETTINGS, 'r', encoding='utf-8') as f:
    settings = json.load(f)
    print(f"读取配置:{settings}")

5.3 完整的项目路径配置模板

python 复制代码
"""
paths.py ------ 项目路径配置文件
放在项目根目录,在其他模块中导入使用
"""
import os

# ==================== 目录定位 ====================

# 本文件(paths.py)所在目录 = 项目根目录
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))

# 各功能目录
SRC_DIR    = os.path.join(ROOT_DIR, 'src')
CONFIG_DIR = os.path.join(ROOT_DIR, 'config')
DATA_DIR   = os.path.join(ROOT_DIR, 'data')
OUTPUT_DIR = os.path.join(ROOT_DIR, 'output')
LOG_DIR    = os.path.join(ROOT_DIR, 'logs')
TEST_DIR   = os.path.join(ROOT_DIR, 'tests')

# ==================== 常用文件 ====================

CONFIG_FILE  = os.path.join(CONFIG_DIR, 'config.yaml')
DATABASE_URL = os.path.join(DATA_DIR,   'database.db')
LOG_FILE     = os.path.join(LOG_DIR,    'app.log')

# ==================== 自动创建必要目录 ====================

for d in [OUTPUT_DIR, LOG_DIR]:
    os.makedirs(d, exist_ok=True)   # exist_ok=True:已存在时不报错


# ==================== 在其他模块中使用 ====================
# from paths import ROOT_DIR, DATA_DIR, LOG_FILE
# with open(LOG_FILE, 'a') as f:
#     f.write('程序启动\n')

5.4 为什么不直接用 os.getcwd()?

python 复制代码
import os

# os.getcwd():获取"当前工作目录"(你在哪个目录下运行 Python)
# os.path.dirname(os.path.abspath(__file__)):脚本文件所在目录

# 两者的区别:
# ┌─────────────────┬──────────────────────────────────────────────┐
# │ 场景             │ os.getcwd()     vs   dirname(abspath(__file__))│
# ├─────────────────┼──────────────────────────────────────────────┤
# │ 在 C:\project   │ C:\project          C:\project\src           │
# │ 下运行           │ (运行时目录)      (脚本文件位置,稳定!) │
# │ python src\app.py│                                              │
# ├─────────────────┼──────────────────────────────────────────────┤
# │ 在 C:\project\src│ C:\project\src      C:\project\src           │
# │ 下运行 python app│ (这次碰巧一样)    (总是正确)             │
# └─────────────────┴──────────────────────────────────────────────┘

# 结论:
# os.getcwd()   → 取决于运行时的工作目录,不稳定
# dirname(abspath(__file__)) → 永远是脚本自己所在的目录,稳定可靠

print("当前工作目录:", os.getcwd())
print("脚本所在目录:", os.path.dirname(os.path.abspath(__file__)))

第六部分:os.path 其他常用函数

6.1 os.path.join()------路径拼接(跨平台神器)

python 复制代码
import os

# ===== 基础用法 =====
# join 会自动用当前操作系统的路径分隔符拼接
# Windows:\,Linux/macOS:/

path = os.path.join('home', 'user', 'project', 'main.py')
print(path)
# Windows:home\user\project\main.py
# Linux:  home/user/project/main.py

# 从绝对路径开始
path2 = os.path.join('C:\\', 'Users', 'Admin', 'Desktop')
print(path2)
# C:\Users\Admin\Desktop


# ===== 重要陷阱:遇到绝对路径会"截断"前面的部分 =====
path3 = os.path.join('/home/user', 'project', '/etc/config')
print(path3)
# /etc/config   ← 前面的 /home/user/project 被丢弃了!

# 原因:/etc/config 是绝对路径,join 认为它就是新的根
# 记住:join 中如果某个部分是绝对路径,前面的都会被忽略

# ===== 正确做法:相对路径拼接 =====
base = '/home/user/project'
rel  = 'data/input.csv'    # 不要以 / 开头

print(os.path.join(base, rel))
# /home/user/project/data/input.csv   ✅

# ===== 拼接多级 =====
print(os.path.join('/var', 'log', 'nginx', 'access.log'))
# /var/log/nginx/access.log


# ===== 实际应用:构建多个文件路径 =====
base_dir = os.path.dirname(os.path.abspath(__file__))
files    = ['config.json', 'data.csv', 'output.txt']

for filename in files:
    full_path = os.path.join(base_dir, filename)
    print(f"  {filename:15s} → {full_path}")

6.2 os.path.basename()------获取文件名

python 复制代码
import os

# basename:返回路径中的"最后一部分"(文件名或最末级目录名)

print(os.path.basename('/home/user/project/main.py'))
# main.py

print(os.path.basename('C:\\Users\\Admin\\data.csv'))
# data.csv

print(os.path.basename('/home/user/project'))
# project(目录名)

print(os.path.basename('/home/user/project/'))
# ''(空字符串!注意末尾有斜杠)

print(os.path.basename('main.py'))
# main.py(本来就只有文件名)

# ===== 实际应用:获取文件名(不含目录) =====
files = [
    '/var/log/nginx/access.log',
    '/home/user/documents/report.pdf',
    'C:\\Downloads\\photo.jpg'
]
for f in files:
    print(f"{os.path.basename(f):20s}  ← 来自  {os.path.dirname(f)}")

6.3 os.path.split()------同时获取目录和文件名

python 复制代码
import os

# split:将路径拆分为 (目录, 文件名) 两部分
# 等价于:(dirname(path), basename(path))

path = '/home/user/project/main.py'
directory, filename = os.path.split(path)

print(f"目录部分:{directory}")    # /home/user/project
print(f"文件名部分:{filename}")   # main.py

# 一次赋值给两个变量
head, tail = os.path.split(r'C:\project\src\utils.py')
print(head)   # C:\project\src
print(tail)   # utils.py

# 注意:末尾有斜杠的情况
head2, tail2 = os.path.split('/home/user/project/')
print(head2)  # /home/user/project
print(tail2)  # ''(空字符串)

# ===== 实际应用:批量重命名前获取路径信息 =====
files = [
    '/data/jan/report.csv',
    '/data/feb/report.csv',
    '/data/mar/report.csv'
]
for f in files:
    folder, name = os.path.split(f)
    new_name = os.path.join(folder, 'backup_' + name)
    print(f"{f:30s} → {new_name}")

6.4 os.path.splitext()------分离文件名和扩展名

python 复制代码
import os

# splitext:将路径拆分为 (路径去掉扩展名, 扩展名) 两部分

name, ext = os.path.splitext('document.pdf')
print(name, ext)   # document  .pdf(注意扩展名含点)

name2, ext2 = os.path.splitext('/home/user/photo.jpg')
print(name2, ext2)  # /home/user/photo  .jpg

# 没有扩展名
name3, ext3 = os.path.splitext('/etc/hosts')
print(name3, ext3)  # /etc/hosts  ''(空字符串)

# 多个点
name4, ext4 = os.path.splitext('archive.tar.gz')
print(name4, ext4)  # archive.tar  .gz(只取最后一个点后的内容)

# 隐藏文件(Linux 以点开头)
name5, ext5 = os.path.splitext('.bashrc')
print(name5, ext5)  # .bashrc  ''(没有扩展名!)

name6, ext6 = os.path.splitext('.config.json')
print(name6, ext6)  # .config  .json


# ===== 实际应用:批量转换文件格式 =====
files = ['image.jpg', 'photo.png', 'avatar.bmp', 'logo.gif']

for filename in files:
    name, ext = os.path.splitext(filename)
    new_file  = name + '.webp'   # 统一转为 webp 格式
    print(f"{filename:15s} → {new_file}")

# ===== 按扩展名过滤文件 =====
all_files = ['a.py', 'b.txt', 'c.py', 'd.csv', 'e.py']
py_files  = [f for f in all_files if os.path.splitext(f)[1] == '.py']
print(f"Python 文件:{py_files}")   # ['a.py', 'c.py', 'e.py']

6.5 os.path.exists() / isfile() / isdir()------判断路径

python 复制代码
import os

# ===== 判断是否存在 =====
print(os.path.exists('/etc/passwd'))   # True(Linux)或 False(Windows)
print(os.path.exists('nonexistent'))   # False

# ===== 判断是文件还是目录 =====
print(os.path.isfile('/etc/passwd'))    # True(是文件)
print(os.path.isfile('/etc'))           # False(是目录,不是文件)
print(os.path.isdir('/etc'))            # True(是目录)
print(os.path.isdir('/etc/passwd'))     # False(是文件,不是目录)

# 不存在的路径
print(os.path.isfile('no_such_file'))   # False(不报错,返回False)
print(os.path.isdir('no_such_dir'))     # False

# ===== 判断是否是符号链接(软链接)=====
print(os.path.islink('/usr/bin/python'))  # 可能是 True(软链接到具体版本)


# ===== 实际应用:安全读取文件 =====
def safe_read(filepath):
    if not os.path.exists(filepath):
        print(f"❌ 文件不存在:{filepath}")
        return None
    if not os.path.isfile(filepath):
        print(f"❌ 不是文件(可能是目录):{filepath}")
        return None
    with open(filepath, 'r', encoding='utf-8') as f:
        return f.read()


content = safe_read('/etc/hosts')
if content:
    print(f"文件内容(前100字符):{content[:100]}")


# ===== 确保目录存在再写文件 =====
def write_safely(filepath, content):
    """确保目录存在后再写文件"""
    directory = os.path.dirname(filepath)
    if directory and not os.path.exists(directory):
        os.makedirs(directory, exist_ok=True)
        print(f"📁 创建目录:{directory}")
    with open(filepath, 'w', encoding='utf-8') as f:
        f.write(content)
    print(f"✅ 写入完成:{filepath}")


write_safely('/tmp/newdir/subdir/output.txt', '这是内容')

6.6 os.path.getsize()------获取文件大小

python 复制代码
import os

# 返回文件大小(字节数)
size = os.path.getsize('/etc/passwd')
print(f"文件大小:{size} 字节")

# 格式化显示文件大小
def format_size(size_bytes):
    """将字节数转换为人类可读的格式"""
    for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
        if size_bytes < 1024:
            return f"{size_bytes:.2f} {unit}"
        size_bytes /= 1024
    return f"{size_bytes:.2f} PB"


files = ['/etc/passwd', '/var/log/syslog', '/boot/vmlinuz']
for f in files:
    if os.path.exists(f):
        print(f"{f:30s}  {format_size(os.path.getsize(f))}")

6.7 os.path.normpath()------规范化路径

python 复制代码
import os

# normpath:消除路径中的多余斜杠、. 和 .. 等

print(os.path.normpath('/home/user/../user/./project'))
# /home/user/project(消除了 .. 和 .)

print(os.path.normpath('C://Users//Admin//Desktop'))
# C:\Users\Admin\Desktop(重复斜杠 → 单斜杠;正斜杠 → 反斜杠)

print(os.path.normpath('./data/../output/./result.txt'))
# output\result.txt(化简后的相对路径)

print(os.path.normpath('/'))
# \(Windows)或 /(Linux)


# ===== 实际应用:处理用户输入的路径 =====
user_input = input("请输入文件路径:")   # 用户可能乱填斜杠
clean_path = os.path.normpath(user_input)
print(f"规范化后:{clean_path}")

6.8 os.path.relpath()------计算相对路径

python 复制代码
import os

# relpath(path, start):计算从 start 到 path 的相对路径

# 从 /home/user 到 /home/user/project/main.py
rel = os.path.relpath('/home/user/project/main.py', '/home/user')
print(rel)   # project/main.py

# 从 /home/user/project 到 /home/user/data
rel2 = os.path.relpath('/home/user/data', '/home/user/project')
print(rel2)   # ../data(需要先上去一级)

# 从当前目录(默认)计算
os.chdir('/home/user')
rel3 = os.path.relpath('/home/user/project/main.py')
print(rel3)   # project/main.py


# ===== 实际应用:生成 HTML 中的相对链接 =====
base_html = '/website/pages/about.html'
image     = '/website/assets/images/logo.png'
css       = '/website/assets/css/style.css'

base_dir = os.path.dirname(base_html)
print(f"图片链接:{os.path.relpath(image, base_dir)}")
# ../assets/images/logo.png

print(f"CSS链接:{os.path.relpath(css, base_dir)}")
# ../assets/css/style.css

6.9 os.path.commonpath() / commonprefix()------公共路径

python 复制代码
import os

# commonpath:找多个路径的公共前缀(目录级别)
paths = [
    '/home/user/project/src/main.py',
    '/home/user/project/src/utils.py',
    '/home/user/project/config/settings.json',
]
common = os.path.commonpath(paths)
print(common)   # /home/user/project

# commonprefix(旧版,字符串级别,不推荐)
prefix = os.path.commonprefix(paths)
print(prefix)   # /home/user/project/(字符串前缀,不是目录级别)
# 注意:commonprefix 可能返回不完整的目录名!

# ===== 实际应用:找出文件集合的共同根目录 =====
backup_files = [
    '/data/2024/jan/report.csv',
    '/data/2024/feb/report.csv',
    '/data/2024/mar/report.csv',
]
root = os.path.commonpath(backup_files)
print(f"公共根目录:{root}")   # /data/2024

6.10 os.path.expanduser()------展开用户目录

python 复制代码
import os

# ~ 表示当前用户的主目录
print(os.path.expanduser('~'))
# Windows:C:\Users\Admin
# Linux:  /home/user
# macOS:  /Users/user

print(os.path.expanduser('~/.ssh/id_rsa'))
# Windows:C:\Users\Admin\.ssh\id_rsa
# Linux:  /home/user/.ssh/id_rsa

# 指定用户
print(os.path.expanduser('~root'))
# /root(Linux 的 root 用户主目录)


# ===== 实际应用:跨平台找配置文件 =====
config_path = os.path.expanduser('~/.myapp/config.json')
print(f"配置文件路径:{config_path}")

# 确保目录存在
os.makedirs(os.path.dirname(config_path), exist_ok=True)

第七部分:os.path 综合实战

7.1 实战一:扫描目录,按类型归类文件

python 复制代码
import os

def classify_files(directory):
    """
    扫描目录,按文件扩展名归类,统计文件数量和总大小
    """
    result = {}   # 格式:{扩展名: {'count': n, 'size': n, 'files': [...]}}

    if not os.path.isdir(directory):
        print(f"❌ 不是有效目录:{directory}")
        return result

    for item in os.listdir(directory):
        full_path = os.path.join(directory, item)

        if not os.path.isfile(full_path):
            continue   # 跳过子目录

        _, ext = os.path.splitext(item)
        ext    = ext.lower() or '(无扩展名)'

        if ext not in result:
            result[ext] = {'count': 0, 'size': 0, 'files': []}

        result[ext]['count'] += 1
        result[ext]['size']  += os.path.getsize(full_path)
        result[ext]['files'].append(item)

    return result


def format_size(size):
    for unit in ['B', 'KB', 'MB', 'GB']:
        if size < 1024:
            return f"{size:.1f}{unit}"
        size /= 1024
    return f"{size:.1f}TB"


# 扫描某个目录
scan_dir = os.path.expanduser('~/Downloads')   # 用户下载目录
classified = classify_files(scan_dir)

print(f"目录:{scan_dir}")
print(f"{'扩展名':12s}  {'文件数':>6s}  {'总大小':>10s}")
print("-" * 35)
for ext, info in sorted(classified.items(), key=lambda x: -x[1]['size']):
    print(f"{ext:12s}  {info['count']:>6d}  {format_size(info['size']):>10s}")

7.2 实战二:递归搜索文件

python 复制代码
import os

def find_files(root_dir, extension=None, pattern=None, min_size=None):
    """
    递归搜索文件

    参数:
        root_dir  - 搜索的根目录
        extension - 文件扩展名(如 '.py',含点)
        pattern   - 文件名包含的关键词
        min_size  - 最小文件大小(字节)
    """
    matches = []

    for dirpath, dirnames, filenames in os.walk(root_dir):
        # os.walk 递归遍历目录树
        # dirpath  = 当前目录路径
        # dirnames = 当前目录下的子目录列表
        # filenames= 当前目录下的文件列表

        # 跳过 .git、__pycache__ 等隐藏/缓存目录
        dirnames[:] = [d for d in dirnames if not d.startswith('.') and d != '__pycache__']

        for filename in filenames:
            full_path = os.path.join(dirpath, filename)

            # 扩展名过滤
            if extension and not filename.endswith(extension):
                continue

            # 关键词过滤
            if pattern and pattern.lower() not in filename.lower():
                continue

            # 大小过滤
            if min_size and os.path.getsize(full_path) < min_size:
                continue

            matches.append(full_path)

    return matches


# 查找当前项目中所有 .py 文件
project_dir = os.path.dirname(os.path.abspath(__file__))

py_files = find_files(project_dir, extension='.py')
print(f"找到 {len(py_files)} 个 Python 文件:")
for f in py_files[:10]:   # 只显示前10个
    # 显示相对路径,更清晰
    rel = os.path.relpath(f, project_dir)
    size = os.path.getsize(f)
    print(f"  {rel:40s}  {size:>8} bytes")

# 查找大于 1MB 的文件
large_files = find_files('/home', min_size=1024*1024)
print(f"\n大于1MB的文件:{len(large_files)} 个")

7.3 实战三:路径处理工具函数集合

python 复制代码
import os
import shutil
from datetime import datetime


def ensure_dir(path):
    """确保目录存在,不存在则创建(含所有父目录)"""
    os.makedirs(path, exist_ok=True)
    return path


def safe_filename(filename):
    """
    把文件名中的非法字符替换掉(跨平台兼容)
    Windows 不允许:\ / : * ? " < > |
    """
    illegal = r'\/:*?"<>|'
    for ch in illegal:
        filename = filename.replace(ch, '_')
    return filename


def add_timestamp(filepath):
    """在文件名中插入时间戳(避免覆盖)"""
    dir_part, filename = os.path.split(filepath)
    name, ext         = os.path.splitext(filename)
    timestamp         = datetime.now().strftime('%Y%m%d_%H%M%S')
    new_name          = f"{name}_{timestamp}{ext}"
    return os.path.join(dir_part, new_name)


def make_unique_path(filepath):
    """如果文件已存在,自动编号避免覆盖"""
    if not os.path.exists(filepath):
        return filepath

    dir_part, filename = os.path.split(filepath)
    name, ext          = os.path.splitext(filename)
    counter            = 1

    while True:
        new_path = os.path.join(dir_part, f"{name}_{counter}{ext}")
        if not os.path.exists(new_path):
            return new_path
        counter += 1


def get_project_root(marker='setup.py'):
    """
    从当前文件向上寻找项目根目录
    (通过查找标志文件:setup.py、pyproject.toml、.git 等)
    """
    current = os.path.dirname(os.path.abspath(__file__))

    while True:
        if os.path.exists(os.path.join(current, marker)):
            return current
        parent = os.path.dirname(current)
        if parent == current:   # 到达根目录
            return None
        current = parent


# ===== 使用示例 =====

# 1. 确保目录存在
log_dir = ensure_dir('/tmp/myapp/logs')
print(f"日志目录:{log_dir}")

# 2. 安全文件名
unsafe = 'report: 2024/01/01 < data >.csv'
safe   = safe_filename(unsafe)
print(f"原始:{unsafe}")
print(f"安全:{safe}")

# 3. 带时间戳的文件名
ts_path = add_timestamp('/backup/data.csv')
print(f"带时间戳:{ts_path}")

# 4. 不覆盖现有文件
unique = make_unique_path('/tmp/output.txt')
print(f"唯一路径:{unique}")

第八部分:pathlib------更现代的路径写法

8.1 pathlib 简介(Python 3.4+)

pathlib 是 Python 3.4 引入的面向对象路径库,让路径操作更直观:

python 复制代码
from pathlib import Path

# ===== 基础对比 =====

# os.path 的写法(函数式)
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
data_path  = os.path.join(script_dir, 'data', 'input.csv')

# pathlib 的写法(面向对象,更简洁)
script_dir = Path(__file__).resolve().parent
data_path  = script_dir / 'data' / 'input.csv'   # 用 / 拼接路径!

8.2 pathlib 核心操作

python 复制代码
from pathlib import Path

# ===== 创建 Path 对象 =====
p = Path('/home/user/project/main.py')

print(p.name)        # main.py           ← 等同于 os.path.basename
print(p.stem)        # main              ← 文件名(不含扩展名)
print(p.suffix)      # .py               ← 扩展名(含点)
print(p.suffixes)    # ['.py']           ← 所有扩展名(如 ['.tar', '.gz'])
print(p.parent)      # /home/user/project ← 等同于 os.path.dirname
print(p.parents[0])  # /home/user/project ← 直接父目录
print(p.parents[1])  # /home/user         ← 上两级
print(p.parents[2])  # /home              ← 上三级


# ===== 路径拼接(用 / 运算符)=====
base     = Path('/home/user/project')
data_dir = base / 'data'
csv_file = base / 'data' / 'input.csv'
print(csv_file)   # /home/user/project/data/input.csv


# ===== 绝对路径和解析 =====
p_rel = Path('data/input.csv')
p_abs = p_rel.resolve()   # 等同于 os.path.abspath,但返回 Path 对象
print(p_abs)


# ===== 等同于 dirname(abspath(__file__)) =====
THIS_DIR = Path(__file__).resolve().parent
print(THIS_DIR)

# 向上多级
PROJECT_DIR = Path(__file__).resolve().parent.parent
print(PROJECT_DIR)


# ===== 检查路径 =====
p = Path('/etc/passwd')
print(p.exists())    # True
print(p.is_file())   # True
print(p.is_dir())    # False


# ===== 读写文件(pathlib 特有的便捷方法)=====
config = Path('/tmp/config.txt')

config.write_text('name=test\nversion=1.0', encoding='utf-8')
content = config.read_text(encoding='utf-8')
print(content)

config.write_bytes(b'\x89PNG\r\n')   # 写二进制
data = config.read_bytes()           # 读二进制


# ===== 遍历目录 =====
data_dir = Path('/var/log')
if data_dir.exists():
    # 列出所有文件
    for f in data_dir.iterdir():
        print(f.name)

    # 只列出 .log 文件(glob 模式)
    for f in data_dir.glob('*.log'):
        print(f"{f.name:20s}  {f.stat().st_size} bytes")

    # 递归搜索所有 .py 文件
    for f in data_dir.rglob('*.py'):
        print(f)


# ===== 创建/删除目录 =====
new_dir = Path('/tmp/myapp/data/2024')
new_dir.mkdir(parents=True, exist_ok=True)   # 等同于 os.makedirs

# 重命名文件
old_file = Path('/tmp/old.txt')
old_file.rename('/tmp/new.txt')

# 删除文件
p = Path('/tmp/temp.txt')
if p.exists():
    p.unlink()   # 删除文件(unlink 是 Unix 术语)

8.3 os.path 和 pathlib 对照表

复制代码
┌──────────────────────────────────────────────────────────────────────┐
│                  os.path  vs  pathlib 对照                           │
├──────────────────────────────┬───────────────────────────────────────┤
│ os.path 写法                 │ pathlib 写法                         │
├──────────────────────────────┼───────────────────────────────────────┤
│ os.path.abspath(__file__)    │ Path(__file__).resolve()             │
│ os.path.dirname(path)        │ Path(path).parent                    │
│ os.path.basename(path)       │ Path(path).name                      │
│ os.path.splitext(f)[0]       │ Path(f).stem                         │
│ os.path.splitext(f)[1]       │ Path(f).suffix                       │
│ os.path.join(a, b, c)        │ Path(a) / b / c                      │
│ os.path.exists(p)            │ Path(p).exists()                     │
│ os.path.isfile(p)            │ Path(p).is_file()                    │
│ os.path.isdir(p)             │ Path(p).is_dir()                     │
│ os.path.getsize(p)           │ Path(p).stat().st_size               │
│ os.makedirs(p, exist_ok=True)│ Path(p).mkdir(parents=True,          │
│                              │              exist_ok=True)           │
│ open(p, 'r').read()          │ Path(p).read_text()                  │
│ open(p, 'w').write(s)        │ Path(p).write_text(s)                │
│ os.listdir(p)                │ [x.name for x in Path(p).iterdir()]  │
│ glob.glob('**/*.py')         │ Path(p).rglob('*.py')                │
└──────────────────────────────┴───────────────────────────────────────┘

第九部分:Windows / Linux 跨平台注意事项

9.1 路径分隔符问题

python 复制代码
import os

# ❌ 硬编码 Windows 反斜杠(移植到 Linux 会出错)
bad_path = 'C:\\Users\\Admin\\data\\file.txt'

# ✅ 方式1:用 os.path.join(推荐,最通用)
good_path = os.path.join('data', 'subdir', 'file.txt')
# Windows:data\subdir\file.txt
# Linux:  data/subdir/file.txt

# ✅ 方式2:用正斜杠(在 Windows 上大多数函数也能识别)
path2 = 'data/subdir/file.txt'

# ✅ 方式3:用 pathlib(最现代)
from pathlib import Path
path3 = Path('data') / 'subdir' / 'file.txt'

# 获取当前平台的路径分隔符
print(os.sep)      # Windows:\    Linux/macOS:/
print(os.pathsep)  # Windows:;(PATH 分隔符)  Linux::
print(os.altsep)   # Windows:/    Linux:None

# 判断当前操作系统
import sys
print(sys.platform)      # win32 / linux / darwin(macOS)
print(os.name)           # nt(Windows)/ posix(Linux/macOS)

9.2 大小写问题

python 复制代码
import os

# Windows 文件系统不区分大小写
# Linux/macOS 区分大小写(默认)

# 同一个文件:
# Windows:data.csv == DATA.CSV == Data.Csv(都能访问)
# Linux:  data.csv ≠ DATA.CSV ≠ Data.Csv(是不同的文件!)

# 跨平台安全做法:统一用小写文件名
filename = user_input.lower()

# 在 Windows 上规范化大小写
if os.name == 'nt':
    path = os.path.normcase(path)   # 把路径转为小写

9.3 路径长度限制

python 复制代码
# Windows:传统限制 MAX_PATH = 260 个字符
# Linux:  通常 4096 个字符
# 解决 Windows 路径过长的方法:在路径前加 \\?\ 前缀

# ===== 检查路径长度 =====
path = r'C:\very\long\...\path\file.txt'

if os.name == 'nt' and len(path) > 260:
    # 启用长路径支持(Windows 10 以上,需要修改注册表或组策略)
    path = '\\\\?\\' + path

第十部分:完整速查表

10.1 函数速查

复制代码
📌 核心组合(最常用!)
  os.path.dirname(os.path.abspath(__file__))
    → 当前脚本所在目录(无论从哪里运行,永远正确)

📌 路径转换
  os.path.abspath(path)     → 转为绝对路径(基于当前工作目录)
  os.path.normpath(path)    → 规范化路径(消除 . .. 和多余斜杠)
  os.path.expanduser(path)  → 展开 ~ 为用户主目录
  os.path.relpath(path, start) → 计算相对路径

📌 路径拆解
  os.path.dirname(path)     → 目录部分
  os.path.basename(path)    → 文件名部分(最末级)
  os.path.split(path)       → (目录, 文件名) 元组
  os.path.splitext(path)    → (路径去扩展名, 扩展名) 元组

📌 路径拼接
  os.path.join(a, b, c)     → 跨平台路径拼接(推荐!)

📌 路径判断
  os.path.exists(path)      → 是否存在(文件或目录)
  os.path.isfile(path)      → 是否是文件
  os.path.isdir(path)       → 是否是目录
  os.path.islink(path)      → 是否是符号链接
  os.path.isabs(path)       → 是否是绝对路径

📌 文件信息
  os.path.getsize(path)     → 文件大小(字节)
  os.path.getmtime(path)    → 最后修改时间(时间戳)
  os.path.getatime(path)    → 最后访问时间(时间戳)
  os.path.getctime(path)    → 创建时间(时间戳,Windows)

📌 其他
  os.path.commonpath(paths) → 多路径的公共目录前缀
  os.getcwd()               → 获取当前工作目录
  os.chdir(path)            → 改变当前工作目录

10.2 pathlib 替代写法(现代推荐)

python 复制代码
from pathlib import Path

THIS_FILE = Path(__file__).resolve()         # 当前文件绝对路径
THIS_DIR  = Path(__file__).resolve().parent  # 当前文件所在目录(最常用!)

# 向上N级
ROOT = Path(__file__).resolve().parents[2]   # 上3级(parents[0]=直接父,以此类推)

# 拼接子路径
data_file = THIS_DIR / 'data' / 'input.csv'

# 读写
text    = data_file.read_text(encoding='utf-8')
content = data_file.read_bytes()
data_file.write_text("hello", encoding='utf-8')

# 判断
data_file.exists()
data_file.is_file()
data_file.parent.is_dir()

# 遍历
for f in THIS_DIR.glob('*.py'):     # 当前目录的 .py 文件
    print(f.name, f.stat().st_size)

for f in THIS_DIR.rglob('*.txt'):   # 递归搜索所有 .txt 文件
    print(f)

总结

学完本章,你应该彻底理解:

  1. __file__ 是当前脚本路径,但可能不稳定(相对路径)
  2. os.path.abspath(__file__) 将其转为永远稳定的绝对路径
  3. os.path.dirname(...) 去掉文件名,只保留目录部分
  4. os.path.dirname(os.path.abspath(__file__)) 是"定位脚本自身位置"的万能公式,生产项目必用
  5. os.path.join() 是跨平台拼接路径的最佳方式,永远不要用字符串手动拼
  6. 其他常用函数existsisfileisdirbasenamesplitextnormpath
  7. pathlib.Path 是更现代的写法,用 / 拼接路径,代码更优雅

记住这个项目模板:

python 复制代码
import os
from pathlib import Path

# os.path 风格(兼容性最好)
THIS_DIR    = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = os.path.dirname(THIS_DIR)
DATA_FILE   = os.path.join(PROJECT_DIR, 'data', 'input.csv')

# pathlib 风格(Python 3.4+,更现代)
THIS_DIR    = Path(__file__).resolve().parent
PROJECT_DIR = THIS_DIR.parent
DATA_FILE   = PROJECT_DIR / 'data' / 'input.csv'

https://www.quanzhankaige.com/python26/

相关推荐
2401_874732532 小时前
实战:用Python分析某电商销售数据
jvm·数据库·python
2301_793804692 小时前
Python内存管理机制:垃圾回收与引用计数
jvm·数据库·python
(@近墨清思%)2 小时前
使用PyQt5创建现代化的桌面应用程序
jvm·数据库·python
2301_795741792 小时前
在构建企业级文生视频存储架构时,RustFS相比传统存储方案有哪些独特优势?
开发语言·python·pygame
小陈工2 小时前
2026年3月25日技术资讯洞察:开源芯片革命、Postgres文件系统与AI Agent安全新范式
开发语言·数据库·人工智能·python·安全·web安全·开源
飞Link2 小时前
Python `warnings` 库底层机制全解析与企业级 API 演进实战
开发语言·python
irpywp2 小时前
SentrySearch:一款支持用自然语言检索原生 MP4 视频的 Python 命令行工具
python·音视频·概率论
木易GIS2 小时前
使用arcpy,批量读取多个文件夹的*.shp中的图层,统计提取图层的个数和要素总个数
python·arcgis
程序员小远2 小时前
Python+requests+unittest+excel 实现接口自动化测试框架
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·excel