# Python 路径操作与文件系统

## 学习目标
- 掌握 `pathlib` 模块的使用
- 理解面向对象的路径操作
- 学会遍历目录和处理文件路径
- 掌握文件元数据获取和权限操作

---

## 1. pathlib 基础

`pathlib` 是 Python 3.4+ 推荐的路径处理方式，用面向对象的方式替代 `os.path`。

### 1.1 创建路径对象

```python
from pathlib import Path

# 当前目录
p = Path('.')
print(p)  # .

# 绝对路径
p = Path('/usr/bin/python')
print(p)  # /usr/bin/python

# 相对路径
p = Path('data/file.txt')
print(p)  # data/file.txt

# 用户主目录
home = Path.home()
print(home)  # /Users/username

# 当前工作目录
cwd = Path.cwd()
print(cwd)
```

### 1.2 路径拼接

```python
from pathlib import Path

# 使用 / 拼接路径
base = Path('/home/user')
path = base / 'documents' / 'file.txt'
print(path)  # /home/user/documents/file.txt

# 混合使用字符串
path = Path('/home') / 'user' / 'test.py'
print(path)
```

---

## 2. 路径属性和方法

### 2.1 路径组成部分

```python
from pathlib import Path

p = Path('/home/user/documents/file.txt')

print(p.name)       # file.txt（文件名）
print(p.stem)       # file（不含后缀）
print(p.suffix)     # .txt（后缀）
print(p.suffixes)   # ['.txt']（所有后缀）
print(p.parent)     # /home/user/documents（父目录）
print(p.parents)    # 所有上级路径的序列
print(p.parts)      # ('/', 'home', 'user', 'documents', 'file.txt')
print(p.anchor)     # /（盘符或根）

# 多级父目录
for parent in p.parents:
    print(parent)
```

### 2.2 路径查询

```python
from pathlib import Path

p = Path('example.txt')

print(p.exists())        # 是否存在
print(p.is_file())       # 是否是文件
print(p.is_dir())        # 是否是目录
print(p.is_absolute())   # 是否是绝对路径

# 文件元数据
if p.exists():
    print(p.stat())          # 完整状态信息
    print(p.stat().st_size)  # 文件大小（字节）
    print(p.stat().st_mtime) # 最后修改时间

# 路径信息
print(p.resolve())       # 解析为绝对路径
```

---

## 3. 文件和目录操作

### 3.1 创建和删除

```python
from pathlib import Path

# 创建目录
Path('new_folder').mkdir()           # 创建单级目录
Path('a/b/c').mkdir(parents=True)    # 递归创建
Path('a/b/c').mkdir(parents=True, exist_ok=True)  # 已存在不报错

# 创建文件
Path('new_file.txt').touch()         # 创建空文件
Path('new_file.txt').touch(exist_ok=True)  # 已存在更新修改时间

# 删除
Path('file.txt').unlink()            # 删除文件
Path('file.txt').unlink(missing_ok=True)  # 不存在不报错
Path('empty_folder').rmdir()         # 删除空目录

# 重命名
Path('old.txt').rename('new.txt')
Path('file.txt').replace('dest/file.txt')  # 移动并覆盖
```

### 3.2 读写文件

```python
from pathlib import Path

p = Path('data.txt')

# 写入
p.write_text('Hello, World!', encoding='utf-8')
p.write_bytes(b'Binary data')

# 读取
content = p.read_text(encoding='utf-8')
data = p.read_bytes()

# 追加
with p.open('a', encoding='utf-8') as f:
    f.write('\nAppend line')
```

---

## 4. 目录遍历

### 4.1 遍历目录内容

```python
from pathlib import Path

p = Path('.')

# 列出目录内容
for item in p.iterdir():
    print(item)

# 筛选文件
files = [f for f in p.iterdir() if f.is_file()]
print(f"文件: {files}")

# 筛选目录
dirs = [d for d in p.iterdir() if d.is_dir()]
print(f"目录: {dirs}")
```

### 4.2 通配符匹配

```python
from pathlib import Path

p = Path('.')

# glob - 当前目录
py_files = list(p.glob('*.py'))
print(f"Python 文件: {py_files}")

# rglob - 递归所有子目录
all_py = list(p.rglob('*.py'))
print(f"所有 Python 文件: {all_py}")

# 匹配模式
# *      匹配任意字符
# ?      匹配单个字符
# **     匹配任意目录层级

# 递归查找所有 .txt
for txt in p.rglob('**/*.txt'):
    print(txt)
```

### 4.3 递归遍历

```python
from pathlib import Path

def tree(directory, prefix=''):
    """打印目录树"""
    path = Path(directory)
    
    # 获取目录内容并排序（目录在前，文件在后）
    entries = sorted(path.iterdir(), key=lambda e: (e.is_file(), e.name))
    
    for i, entry in enumerate(entries):
        is_last = i == len(entries) - 1
        current = '└── ' if is_last else '├── '
        print(prefix + current + entry.name)
        
        if entry.is_dir():
            extension = '    ' if is_last else '│   '
            tree(entry, prefix + extension)

# 使用
tree('.')
```

---

## 5. 文件元数据和权限

### 5.1 获取元数据

```python
from pathlib import Path
import datetime

p = Path('example.txt')

if p.exists():
    stat = p.stat()
    
    print(f"大小: {stat.st_size} 字节")
    print(f"创建时间: {datetime.datetime.fromtimestamp(stat.st_ctime)}")
    print(f"修改时间: {datetime.datetime.fromtimestamp(stat.st_mtime)}")
    print(f"访问时间: {datetime.datetime.fromtimestamp(stat.st_atime)}")
```

### 5.2 修改权限

```python
from pathlib import Path
import stat

p = Path('script.sh')

# 添加可执行权限（Unix）
p.chmod(p.stat().st_mode | stat.S_IEXEC)

# 设置指定权限
p.chmod(0o755)  # rwxr-xr-x

# 获取权限
mode = p.stat().st_mode
print(oct(mode)[-3:])  # 权限数字表示
```

---

## 6. 实用示例

### 6.1 查找大文件

```python
from pathlib import Path

def find_large_files(directory, min_size_mb=100):
    """查找大文件"""
    min_bytes = min_size_mb * 1024 * 1024
    path = Path(directory)
    
    large_files = []
    for file_path in path.rglob('*'):
        if file_path.is_file() and file_path.stat().st_size > min_bytes:
            size_mb = file_path.stat().st_size / (1024 * 1024)
            large_files.append((file_path, size_mb))
    
    return sorted(large_files, key=lambda x: x[1], reverse=True)

# 使用
# for path, size in find_large_files('.', 1):
#     print(f"{path}: {size:.2f} MB")
```

### 6.2 批量重命名

```python
from pathlib import Path

def batch_rename(directory, old_ext, new_ext):
    """批量修改文件后缀"""
    path = Path(directory)
    
    for file_path in path.glob(f'*.{old_ext}'):
        new_name = file_path.with_suffix(f'.{new_ext}')
        file_path.rename(new_name)
        print(f"重命名: {file_path.name} -> {new_name.name}")

# 使用
# batch_rename('.', 'txt', 'md')
```

### 6.3 清理空目录

```python
from pathlib import Path

def remove_empty_dirs(directory):
    """递归删除空目录"""
    path = Path(directory)
    
    for dir_path in path.rglob('*'):
        if dir_path.is_dir():
            try:
                dir_path.rmdir()  # 只能删除空目录
                print(f"删除空目录: {dir_path}")
            except OSError:
                pass  # 目录不为空

# 使用
# remove_empty_dirs('.')
```

### 6.4 文件备份

```python
from pathlib import Path
import shutil
from datetime import datetime

def backup_file(filepath):
    """创建文件备份"""
    path = Path(filepath)
    
    if not path.exists():
        raise FileNotFoundError(f"文件不存在: {filepath}")
    
    # 生成备份文件名
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    backup_name = f"{path.stem}_{timestamp}{path.suffix}"
    backup_path = path.parent / 'backup' / backup_name
    
    # 确保备份目录存在
    backup_path.parent.mkdir(exist_ok=True)
    
    # 复制文件
    shutil.copy2(path, backup_path)
    print(f"备份创建: {backup_path}")
    
    return backup_path

# 使用
# backup_file('important.txt')
```

---

## 7. pathlib vs os.path

| 操作 | os.path | pathlib |
|------|---------|---------|
| 拼接路径 | `os.path.join(a, b)` | `Path(a) / b` |
| 文件名 | `os.path.basename(p)` | `Path(p).name` |
| 父目录 | `os.path.dirname(p)` | `Path(p).parent` |
| 后缀 | `os.path.splitext(p)[1]` | `Path(p).suffix` |
| 绝对路径 | `os.path.abspath(p)` | `Path(p).resolve()` |
| 是否存在 | `os.path.exists(p)` | `Path(p).exists()` |

```python
# os.path 方式
import os.path
filepath = os.path.join(os.path.expanduser('~'), 'documents', 'file.txt')
filename = os.path.basename(filepath)
directory = os.path.dirname(filepath)

# pathlib 方式
from pathlib import Path
filepath = Path.home() / 'documents' / 'file.txt'
filename = filepath.name
directory = filepath.parent
```

---

## 本节小结

- **pathlib**：面向对象的路径处理，替代 os.path
- **路径拼接**：使用 `/` 运算符
- **路径属性**：`name`、`stem`、`suffix`、`parent` 等
- **目录遍历**：`iterdir()`、`glob()`、`rglob()`
- **文件操作**：`touch()`、`mkdir()`、`read_text()`、`write_text()`
- **推荐**：新项目优先使用 pathlib

---

## 练习

1. 编写一个函数，统计目录中各类文件的数量和总大小
2. 实现一个文件搜索工具，支持按名称、大小、修改时间筛选
3. 创建一个目录同步工具，将源目录的文件同步到目标目录
4. 编写脚本，自动清理超过 N 天的临时文件

