1. Python文件与目录操作基础
在日常开发中,文件与目录操作是每个Python开发者必须掌握的基本功。Python标准库提供了三个强大的模块来应对不同场景下的文件系统操作需求:os、pathlib和shutil。这三个模块各有侧重,共同构成了Python文件操作的"三剑客"。
os模块是Python最早提供的文件系统操作模块,它直接与操作系统交互,提供了大量底层文件操作方法。pathlib则是Python 3.4引入的面向对象的文件系统路径操作库,它用更符合Python风格的面向对象方式封装了路径操作。shutil模块则专注于高级文件操作,提供了文件复制、移动、归档等实用功能。
提示:在Python 3中,pathlib已经成为处理文件路径的首选方式,它解决了传统os.path模块中许多令人困扰的问题,如路径拼接时的斜杠处理、跨平台兼容性等。
1.1 os模块:传统但强大的文件系统接口
os模块是与操作系统交互的瑞士军刀,它不仅能处理文件和目录,还能获取系统信息、管理进程等。在文件操作方面,os模块提供了以下核心功能:
- 文件和目录的基本操作(创建、删除、重命名)
- 路径操作(通过os.path子模块)
- 文件属性获取(大小、修改时间等)
- 目录遍历和文件搜索
python复制import os
# 创建目录
os.mkdir('test_dir')
# 重命名文件
os.rename('old.txt', 'new.txt')
# 删除文件
os.remove('file.txt')
# 获取文件大小
size = os.path.getsize('file.txt')
# 检查路径是否存在
exists = os.path.exists('path/to/file')
os.path子模块特别值得关注,它提供了跨平台的路径操作方法。例如,os.path.join()能正确处理不同操作系统下的路径分隔符问题:
python复制# 跨平台路径拼接
path = os.path.join('dir', 'subdir', 'file.txt')
# 在Windows上得到: dir\subdir\file.txt
# 在Linux/Mac上得到: dir/subdir/file.txt
1.2 pathlib:面向对象的路径操作
pathlib模块引入了Path类,将文件系统路径表示为对象而非字符串,这使得路径操作更加直观和安全。Path对象是不可变的,这意味着所有操作都会返回新的Path实例,而不是修改原对象。
python复制from pathlib import Path
# 创建Path对象
p = Path('dir/subdir/file.txt')
# 获取父目录
parent = p.parent
# 获取文件名
name = p.name
# 获取文件后缀
suffix = p.suffix
# 拼接路径
new_path = p.parent / 'new_file.txt'
pathlib的一个巨大优势是它统一了路径操作的方法调用风格,不再需要记忆是应该调用os.path.join()还是os.mkdir()。所有操作都通过Path对象的方法完成:
python复制# 创建目录
Path('new_dir').mkdir(exist_ok=True)
# 重命名文件
p.rename('new_name.txt')
# 读取文件内容
content = p.read_text()
# 写入文件
p.write_text('Hello, world!')
注意:使用pathlib时,exist_ok=True参数可以避免目录已存在时的错误,这比先检查再创建的代码更简洁。
1.3 shutil:高级文件操作
shutil模块提供了对文件和目录集合的高级操作,包括:
- 文件复制(保留元数据)
- 递归目录复制和删除
- 归档(打包/解包)
- 磁盘空间使用统计
python复制import shutil
# 复制文件(保留元数据)
shutil.copy2('src.txt', 'dst.txt')
# 递归复制目录
shutil.copytree('src_dir', 'dst_dir')
# 递归删除目录
shutil.rmtree('dir_to_remove')
# 创建zip归档
shutil.make_archive('backup', 'zip', 'src_dir')
shutil在复制文件时会尽量保留所有元数据(如权限、时间戳等),这比简单的读写复制更可靠。对于需要保留文件属性的场景,应该总是使用shutil.copy2()而非手动复制文件内容。
2. 核心操作详解与实战
2.1 文件读写的最佳实践
Python提供了多种文件读写方式,选择合适的方式可以显著提高代码的效率和可读性。
传统文件操作模式:
python复制# 不推荐的方式
f = open('file.txt', 'r')
try:
content = f.read()
finally:
f.close()
# 推荐使用with语句
with open('file.txt', 'r', encoding='utf-8') as f:
content = f.read()
with语句会自动管理文件对象的生命周期,即使在读取过程中发生异常也能确保文件被正确关闭。这是避免资源泄露的最佳实践。
现代pathlib读写方式:
python复制from pathlib import Path
# 读取文本
content = Path('file.txt').read_text(encoding='utf-8')
# 写入文本
Path('file.txt').write_text('Hello, world!', encoding='utf-8')
# 读取二进制
data = Path('image.png').read_bytes()
# 写入二进制
Path('image.png').write_bytes(data)
pathlib的读写方法更加简洁,内部同样使用了资源管理的最佳实践,是Python 3推荐的文件操作方式。
大文件处理技巧:
处理大文件时,不应该一次性读取全部内容,而应该使用迭代或分块读取:
python复制# 逐行读取(内存友好)
with open('large_file.txt', 'r', encoding='utf-8') as f:
for line in f:
process_line(line)
# 分块读取
chunk_size = 4096
with open('large_file.bin', 'rb') as f:
while chunk := f.read(chunk_size):
process_chunk(chunk)
2.2 目录遍历与文件搜索
在实际项目中,经常需要遍历目录结构或搜索特定文件。Python提供了多种方式来实现这些功能。
使用os.walk()递归遍历:
python复制import os
for root, dirs, files in os.walk('start_dir'):
print(f"当前目录: {root}")
print(f"子目录: {dirs}")
print(f"文件: {files}")
print("-" * 40)
os.walk()是递归遍历目录树的经典方法,它返回一个生成器,每次迭代产生一个三元组(当前目录路径,子目录列表,文件列表)。
使用pathlib进行现代遍历:
python复制from pathlib import Path
# 遍历当前目录下的文件和子目录
for item in Path('.').iterdir():
print(item.name)
# 递归遍历所有.py文件
for py_file in Path('src').rglob('*.py'):
print(py_file)
pathlib的glob()和rglob()方法提供了强大的文件匹配功能,支持类似Unix shell的通配符语法:
*.py- 匹配当前目录下所有.py文件**/*.py- 递归匹配所有子目录中的.py文件project/?/main.py- 匹配project下任意单字母目录中的main.py
高级文件搜索示例:
python复制from pathlib import Path
from datetime import datetime, timedelta
# 找出最近7天内修改过的.py文件
cutoff = datetime.now() - timedelta(days=7)
recent_py_files = [
p for p in Path('src').rglob('*.py')
if p.stat().st_mtime > cutoff.timestamp()
]
这个例子结合了pathlib的文件匹配和文件属性访问,展示了如何构建复杂的文件搜索逻辑。
2.3 文件与目录的CRUD操作
完整的文件系统操作离不开创建、读取、更新和删除(CRUD)这四种基本操作。
创建操作:
python复制from pathlib import Path
# 创建单个目录
Path('new_dir').mkdir()
# 创建多级目录(类似mkdir -p)
Path('deeply/nested/dir').mkdir(parents=True, exist_ok=True)
# 创建空文件
Path('empty.txt').touch()
# 创建带有内容的文件
Path('content.txt').write_text('Hello, world!')
读取操作:
python复制# 检查文件/目录是否存在
Path('file.txt').exists()
# 检查是否是文件
Path('file.txt').is_file()
# 检查是否是目录
Path('dir').is_dir()
# 获取绝对路径
abs_path = Path('file.txt').resolve()
# 获取文件属性
stat = Path('file.txt').stat()
print(f"大小: {stat.st_size} bytes")
print(f"修改时间: {datetime.fromtimestamp(stat.st_mtime)}")
更新操作:
python复制# 重命名/移动文件
Path('old.txt').rename('new.txt')
# 修改文件权限
Path('script.sh').chmod(0o755) # rwxr-xr-x
# 更新修改时间(触摸)
Path('file.txt').touch()
删除操作:
python复制# 删除文件
Path('file.txt').unlink()
# 删除空目录
Path('empty_dir').rmdir()
# 递归删除目录及其内容(危险!)
import shutil
shutil.rmtree('dir_to_remove')
警告:shutil.rmtree()会递归删除目录及其所有内容,且不可恢复。使用前务必确认目录路径正确,最好先打印出要删除的目录内容进行确认。
3. 高级技巧与实战应用
3.1 临时文件与目录管理
处理临时文件是许多程序的常见需求,Python的tempfile模块提供了安全创建临时文件和目录的方法。
python复制import tempfile
from pathlib import Path
# 创建临时文件(自动删除)
with tempfile.NamedTemporaryFile(delete=True) as tmp:
print(f"临时文件路径: {tmp.name}")
tmp.write(b"Hello, temp world!")
tmp.seek(0)
print(tmp.read())
# 创建临时目录(需手动清理)
with tempfile.TemporaryDirectory() as tmpdir:
print(f"临时目录: {tmpdir}")
tmp_file = Path(tmpdir) / 'temp.txt'
tmp_file.write_text("Temporary content")
临时文件的最佳实践包括:
- 使用上下文管理器(with语句)确保资源释放
- 对于敏感数据,确保设置delete=True(默认)
- 临时目录需要手动清理其中的内容
更安全的临时文件处理:
python复制from tempfile import mkstemp
import os
# 更底层的安全临时文件创建
fd, path = mkstemp()
try:
with os.fdopen(fd, 'w') as tmp:
tmp.write('Secure temp content')
finally:
os.remove(path)
这种方式提供了更细粒度的控制,适合需要更高安全性的场景。
3.2 文件监控与变更检测
在某些应用中,我们需要监控文件系统的变更(如配置文件修改、新文件创建等)。Python的watchdog库提供了跨平台的文件系统事件监控功能。
python复制from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from pathlib import Path
import time
class FileChangeHandler(FileSystemEventHandler):
def on_modified(self, event):
if not event.is_directory:
print(f"文件被修改: {event.src_path}")
path = Path('.').resolve()
event_handler = FileChangeHandler()
observer = Observer()
observer.schedule(event_handler, path, recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
这个例子展示了如何监控当前目录及其子目录中的文件修改事件。watchdog支持多种事件类型:
- on_created:文件/目录创建
- on_deleted:文件/目录删除
- on_modified:文件修改/目录变更
- on_moved:文件/目录移动
3.3 跨平台路径处理技巧
编写跨平台应用时,正确处理路径分隔符是一个常见挑战。以下是几种处理方式:
传统os.path方式:
python复制import os.path
# 拼接路径
path = os.path.join('dir', 'subdir', 'file.txt')
# 标准化路径(处理./和../)
norm_path = os.path.normpath('dir/../subdir/./file.txt')
# 转换为平台特定路径
native_path = os.path.normcase(path)
现代pathlib方式:
python复制from pathlib import Path, PureWindowsPath, PurePosixPath
# 自动处理平台差异
path = Path('dir') / 'subdir' / 'file.txt'
# 强制特定平台格式
windows_path = PureWindowsPath(path)
posix_path = PurePosixPath(path)
# 转换路径风格
print(windows_path.as_posix()) # 转换为POSIX风格
处理网络路径和特殊设备:
python复制# UNC路径(Windows网络路径)
unc_path = Path('//server/share/folder/file.txt')
# 设备路径(Windows)
device_path = Path(r'\\.\PhysicalDrive1')
# 解析相对路径
base = Path('/base/dir')
relative = Path('../other/file.txt')
absolute = (base / relative).resolve()
3.4 性能优化与批量操作
处理大量文件时,性能往往成为瓶颈。以下是一些优化技巧:
减少stat调用:
每次检查文件属性(如exists()、is_file())都会触发系统调用,批量操作时应尽量减少这类调用。
python复制# 不优化的方式(多次stat调用)
files = [p for p in Path('dir').iterdir() if p.is_file()]
# 优化的方式(单次遍历)
files = []
for p in Path('dir').iterdir():
try:
if p.is_file():
files.append(p)
except OSError: # 处理权限等问题
continue
并行处理文件:
对于CPU密集型的文件处理,可以使用多进程加速:
python复制from concurrent.futures import ProcessPoolExecutor
from pathlib import Path
def process_file(path):
# 模拟耗时处理
return path.stat().st_size
if __name__ == '__main__':
files = list(Path('.').rglob('*.dat'))
with ProcessPoolExecutor() as executor:
results = list(executor.map(process_file, files))
print(f"处理了 {len(results)} 个文件")
批量文件操作:
使用shutil.copytree()和shutil.rmtree()进行批量操作比逐个文件处理更高效:
python复制import shutil
from pathlib import Path
# 批量复制目录结构
src = Path('src_dir')
dst = Path('backup_dir')
if dst.exists():
shutil.rmtree(dst)
shutil.copytree(src, dst)
# 批量修改文件权限
for f in dst.rglob('*.sh'):
f.chmod(0o755)
4. 常见问题与解决方案
4.1 权限问题处理
文件操作中经常会遇到各种权限问题,正确处理这些问题可以使程序更健壮。
常见权限错误:
- PermissionError:没有读/写/执行权限
- FileNotFoundError:文件不存在(可能是路径错误或权限不足)
- IsADirectoryError/NotADirectoryError:路径类型不匹配
防御性编程技巧:
python复制from pathlib import Path
import os
import stat
def make_writable(path):
"""确保文件可写,必要时修改权限"""
path = Path(path)
if not path.exists():
raise FileNotFoundError(f"{path} 不存在")
current_mode = path.stat().st_mode
if not os.access(path, os.W_OK):
new_mode = current_mode | stat.S_IWUSR
path.chmod(new_mode)
print(f"已修改 {path} 权限为可写")
return True
安全删除文件:
python复制def secure_delete(path):
"""更安全的文件删除,处理各种边缘情况"""
path = Path(path)
try:
if path.is_file():
path.unlink()
elif path.is_dir():
shutil.rmtree(path)
return True
except PermissionError as e:
print(f"权限不足,无法删除 {path}: {e}")
return False
except Exception as e:
print(f"删除 {path} 时出错: {e}")
return False
4.2 路径处理陷阱
路径处理中有许多容易出错的细节,了解这些陷阱可以避免很多bug。
绝对路径与相对路径:
python复制# 相对路径的基准是当前工作目录,可能变化
print(Path('file.txt').resolve()) # 总是获取绝对路径
# 改变工作目录会影响相对路径
original_cwd = Path.cwd()
try:
os.chdir('/tmp')
print(Path('file.txt').resolve()) # 现在是/tmp/file.txt
finally:
os.chdir(original_cwd)
符号链接处理:
python复制# 创建符号链接
Path('target.txt').write_text('content')
Path('link.txt').symlink_to('target.txt')
# 检查是否是符号链接
print(Path('link.txt').is_symlink()) # True
# 解析符号链接
print(Path('link.txt').resolve()) # 显示实际路径
# 注意:大多数方法默认跟随符号链接
print(Path('link.txt').read_text()) # 读取实际内容
大小写敏感问题:
python复制# 在Linux/Mac上,路径是大小写敏感的
Path('File.txt').touch()
print(Path('file.txt').exists()) # False
# 在Windows上,路径通常不区分大小写
Path('File.txt').touch()
print(Path('file.txt').exists()) # True
4.3 编码问题与文本处理
文本文件处理中最常见的问题就是编码不一致导致的解码错误。
自动检测文件编码:
python复制import chardet
def detect_encoding(file_path):
with open(file_path, 'rb') as f:
raw = f.read(1024) # 只读取前1KB通常足够
return chardet.detect(raw)['encoding']
file_path = 'unknown_encoding.txt'
encoding = detect_encoding(file_path)
content = Path(file_path).read_text(encoding=encoding)
安全文本读取:
python复制def safe_read_text(path, encodings=('utf-8', 'gbk', 'latin-1')):
"""尝试多种编码读取文本文件"""
path = Path(path)
for encoding in encodings:
try:
return path.read_text(encoding=encoding)
except UnicodeDecodeError:
continue
raise ValueError(f"无法用 {encodings} 解码文件 {path}")
处理行尾符差异:
不同操作系统使用不同的行尾符(Windows: \r\n, Unix: \n)。在跨平台处理文本文件时,可以使用universal newlines模式:
python复制# 自动统一行尾符为\n
with open('file.txt', 'r', newline='', encoding='utf-8') as f:
lines = f.readlines() # 所有行尾符都会被转换为\n
4.4 性能监控与调优
对于文件密集型的应用,监控性能瓶颈很重要。
测量文件操作时间:
python复制import time
from pathlib import Path
def time_file_op(func, *args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} 耗时: {elapsed:.3f}秒")
return result
# 测试文件复制性能
time_file_op(shutil.copy2, 'large_file.bin', 'copy.bin')
分析磁盘I/O:
在Linux上可以使用iostat,Windows可以使用Performance Monitor来监控磁盘I/O。Python中也可以使用psutil库:
python复制import psutil
disk_before = psutil.disk_io_counters()
# 执行文件操作...
disk_after = psutil.disk_io_counters()
print(f"读取次数: {disk_after.read_count - disk_before.read_count}")
print(f"写入次数: {disk_after.write_count - disk_before.write_count}")
优化策略:
- 减少小文件操作,批量处理数据
- 对大文件使用缓冲(默认已启用)
- 考虑使用内存文件系统(如/tmp)处理临时文件
- 对于高频读取的文件,考虑缓存内容
5. 综合实战案例
5.1 项目目录结构生成器
让我们实现一个实用的项目目录结构生成器,它可以基于模板快速创建标准化的项目结构。
python复制from pathlib import Path
import shutil
def create_project_structure(base_dir, structure):
"""
根据描述创建项目目录结构
:param base_dir: 项目根目录
:param structure: 嵌套字典描述目录结构
:example:
structure = {
'src': {
'main.py': None,
'utils': {
'__init__.py': None,
'helpers.py': None
}
},
'tests': None,
'docs': None,
'README.md': "默认项目说明"
}
"""
base_path = Path(base_dir)
if base_path.exists():
raise ValueError(f"目录 {base_dir} 已存在")
def _create(path, content):
if content is None: # 目录
path.mkdir(parents=True, exist_ok=True)
elif isinstance(content, dict): # 嵌套结构
path.mkdir(parents=True, exist_ok=True)
for name, sub_content in content.items():
_create(path / name, sub_content)
else: # 文件内容
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content)
_create(base_path, structure)
print(f"成功创建项目结构于 {base_path.resolve()}")
# 使用示例
project_template = {
'src': {
'main.py': "# 项目主模块\n\nif __name__ == '__main__':\n print('Hello, world!')",
'utils': {
'__init__.py': "# 工具包",
'file_utils.py': "# 文件操作工具函数"
}
},
'tests': {
'__init__.py': "",
'test_utils.py': "# 单元测试"
},
'docs': None,
'README.md': "# 我的项目\n\n项目描述..."
}
create_project_structure('my_project', project_template)
这个工具可以轻松扩展支持更多功能:
- 从JSON/YAML文件加载模板
- 支持文件模板引擎(如Jinja2)
- 添加Git仓库初始化
- 支持交互式创建
5.2 文件同步工具实现
实现一个简单的双向文件同步工具,可以保持两个目录内容一致。
python复制from pathlib import Path
import shutil
import filecmp
class FileSyncer:
def __init__(self, src, dst):
self.src = Path(src).resolve()
self.dst = Path(dst).resolve()
def sync(self):
# 确保目标目录存在
self.dst.mkdir(parents=True, exist_ok=True)
# 构建源和目标文件映射
src_files = {p.relative_to(self.src): p for p in self.src.rglob('*') if p.is_file()}
dst_files = {p.relative_to(self.dst): p for p in self.dst.rglob('*') if p.is_file()}
# 找出需要同步的文件
to_copy_src = []
to_copy_dst = []
to_update = []
# 检查源有但目标没有的文件
for rel_path in src_files.keys() - dst_files.keys():
to_copy_src.append(rel_path)
# 检查目标有但源没有的文件
for rel_path in dst_files.keys() - src_files.keys():
to_copy_dst.append(rel_path)
# 检查两边都有的文件
for rel_path in src_files.keys() & dst_files.keys():
src_file = src_files[rel_path]
dst_file = dst_files[rel_path]
# 比较文件内容和元数据
if not filecmp.cmp(src_file, dst_file, shallow=False):
# 保留较新的版本
if src_file.stat().st_mtime > dst_file.stat().st_mtime:
to_update.append((rel_path, 'src'))
else:
to_update.append((rel_path, 'dst'))
# 执行同步
for rel_path in to_copy_src:
src = src_files[rel_path]
dst = self.dst / rel_path
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst)
print(f"复制: {src} -> {dst}")
for rel_path in to_copy_dst:
dst = dst_files[rel_path]
src = self.src / rel_path
src.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(dst, src)
print(f"复制: {dst} -> {src}")
for rel_path, direction in to_update:
if direction == 'src':
src = src_files[rel_path]
dst = self.dst / rel_path
shutil.copy2(src, dst)
print(f"更新: {src} -> {dst}")
else:
dst = dst_files[rel_path]
src = self.src / rel_path
shutil.copy2(dst, src)
print(f"更新: {dst} -> {src}")
print("同步完成")
# 使用示例
syncer = FileSyncer('dir_a', 'dir_b')
syncer.sync()
这个同步工具实现了基本的双向同步逻辑,可以扩展的功能包括:
- 添加忽略模式(如.git, __pycache__等)
- 支持文件哈希校验而不仅仅是修改时间
- 添加干运行模式(只显示将要执行的操作)
- 实现增量备份功能
5.3 文件内容搜索工具
实现一个支持正则表达式和多线程的文件内容搜索工具。
python复制import re
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
import argparse
def file_search(file_path, pattern):
"""在单个文件中搜索匹配的行"""
matches = []
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
for line_num, line in enumerate(f, 1):
if pattern.search(line):
matches.append((file_path, line_num, line.strip()))
except (IOError, UnicodeDecodeError) as e:
print(f"无法读取文件 {file_path}: {e}")
return matches
def search_in_directory(directory, regex, extensions=None, workers=4):
"""在目录中递归搜索匹配的文件内容"""
pattern = re.compile(regex)
directory = Path(directory)
files = []
# 收集所有待搜索文件
for ext in extensions or ['']:
files.extend(directory.rglob(f'*{ext}'))
# 多线程搜索
results = []
with ThreadPoolExecutor(max_workers=workers) as executor:
futures = [executor.submit(file_search, f, pattern) for f in files if f.is_file()]
for future in futures:
results.extend(future.result())
return sorted(results, key=lambda x: (x[0], x[1]))
def main():
parser = argparse.ArgumentParser(description='文件内容搜索工具')
parser.add_argument('directory', help='要搜索的目录')
parser.add_argument('pattern', help='搜索的正则表达式')
parser.add_argument('-e', '--extensions', nargs='+',
help='文件扩展名列表(如 .py .txt)')
parser.add_argument('-w', '--workers', type=int, default=4,
help='工作线程数')
args = parser.parse_args()
results = search_in_directory(
args.directory,
args.pattern,
args.extensions,
args.workers
)
# 输出结果
for file_path, line_num, line in results:
print(f"{file_path}:{line_num}: {line}")
if __name__ == '__main__':
main()
这个搜索工具支持:
- 正则表达式匹配
- 指定文件扩展名过滤
- 多线程加速搜索
- 友好的错误处理
使用方法示例:
code复制python search_tool.py /path/to/search "import.*shutil" -e .py .txt
5.4 文件系统监控与自动备份
结合watchdog和文件操作,实现一个监控目录变更并自动备份的工具。
python复制from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from pathlib import Path
import shutil
import time
from datetime import datetime
class BackupHandler(FileSystemEventHandler):
def __init__(self, source, backup_root):
self.source = Path(source).resolve()
self.backup_root = Path(backup_root).resolve()
self.backup_dir = self._create_backup_dir()
def _create_backup_dir(self):
"""创建带时间戳的备份目录"""
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_dir = self.backup_root / f"backup_{timestamp}"
backup_dir.mkdir(parents=True)
return backup_dir
def _backup_file(self, src_path):
"""备份单个文件"""
if not src_path.is_file():
return
rel_path = src_path.relative_to(self.source)
dst_path = self.backup_dir / rel_path
# 确保目标目录存在
dst_path.parent.mkdir(parents=True, exist_ok=True)
# 复制文件(保留元数据)
shutil.copy2(src_path, dst_path)
print(f"备份: {src_path} -> {dst_path}")
def on_modified(self, event):
if not event.is_directory:
src_path = Path(event.src_path)
self._backup_file(src_path)
def on_created(self, event):
if not event.is_directory:
src_path = Path(event.src_path)
self._backup_file(src_path)
def on_moved(self, event):
if not event.is_directory:
src_path = Path(event.src_path)
dest_path = Path(event.dest_path)
print(f"文件移动: {src_path} -> {dest_path}")
self._backup_file(dest_path)
def start_monitoring(source, backup_root):
event_handler = BackupHandler(source, backup_root)
observer = Observer()
observer.schedule(event_handler, source, recursive=True)
observer.start()
print(f"开始监控 {source},备份到 {backup_root}")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
if __name__ == '__main__':
start_monitoring('path/to/source', 'path/to/backups')
这个自动备份工具的特点:
- 实时监控文件变更(创建、修改、移动)
- 创建带时间戳的备份目录
- 保留原始目录结构
- 保持文件元数据
可以扩展的功能包括:
- 设置备份保留策略(如只保留最近7天的备份)
- 支持压缩备份
- 添加远程备份选项
- 实现变更摘要报告
6. 最佳实践与经验分享
6.1 文件操作的安全准则
在处理文件系统时,安全性常常被忽视,但却是极其重要的方面。
输入验证:
- 总是验证用户提供的文件路径
- 防止目录遍历攻击(如检查是否包含../)
- 使用resolve()获取绝对路径后再验证
python复制def safe_open(path):
"""安全地打开用户提供的文件路径"""
path = Path(path).resolve()
base_dir = Path('/safe/dir').resolve()
# 检查路径是否在允许的目录下
if base_dir not in path.parents:
raise ValueError("访问受限路径")
return path.open()
权限最小化:
- 以只读模式打开不需要修改的文件
- 使用最低必要的文件权限
- 考虑使用os.umask()限制默认权限
python复制# 设置严格的默认权限 (只允许用户读写)
os.umask(0o077)
# 创建文件时将只有用户有读写权限
Path('restricted.txt').touch()
原子性操作:
- 重要操作应该具有原子性,避免中间状态
- 使用临时文件+重命名模式进行安全写入
python复制def atomic_write(path, content):
"""原子性地写入文件内容"""
path = Path(path)
temp_path = path.with_suffix('.tmp')
try:
# 写入临时文件
temp_path.write_text(content)
# 原子性重命名
temp_path.replace(path)
except Exception as e:
# 清理临时文件
if temp_path.exists():
temp_path.unlink()
raise e
6.2 跨平台开发的注意事项
编写需要在不同操作系统上运行的代码时,需要特别注意平台差异。
路径分隔符:
- 总是使用Path对象或os.path.join()拼接路径
- 避免硬编码/或\分隔符
- 显示路径时可以使用as_posix()统一格式
python复制# 不好的做法
bad_path = 'dir/subdir/file.txt' # Unix风格
bad_path = 'dir\\subdir\\file.txt' # Windows风格
# 好的做法
good_path = Path('dir') / 'subdir' / 'file.txt'
文件系统特性差异:
- Windows有文件锁定机制(不能删除打开的文件)
- Unix有符号链接和硬链接的区别
- 文件名大小写敏感度不同
- 保留字符和非法字符不同
python复制def is_valid_filename(name):
"""检查文件名在不同平台上的有效性"""
# Windows保留字符
windows_invalid = set('<>:"/\\|?*')
# Unix保留字符
unix_invalid = set('/\0')
invalid_chars = windows_invalid | unix_invalid
return not any(char in invalid_chars for char in name)
行尾符处理:
- 文本模式打开文件时会自动转换行尾符
- 二进制模式则保留原始内容
- 可以使用newline=''控制行为
python复制# 写入统一的行尾符
with open('file.txt', 'w', newline='\n') as f:
f.write('统一的行尾符\n')
6.3 性能优化经验
文件操作常常成为性能瓶颈,特别是在处理大量小文件时。
批量操作:
- 减少单独的系统调用
- 使用shutil.copytree()而非逐个文件复制
- 考虑先收集所有操作再批量执行
python复制# 低效方式
for file in src_dir.iterdir():
shutil.copy2(file, dst_dir / file.name)
# 高效方式
shutil.copytree(src_dir, dst_dir)
内存映射文件:
- 对大文件使用内存映射可以显著提高性能
- 特别适合随机访问模式
python复制import mmap
def count_words_mmap(file_path):
"""使用内存映射统计大文件中的单词数"""
file_path = Path(file_path)
word_count = 0
with file_path.open('r') as f:
with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
word_count = len(re.findall(rb'\w+', mm))
return word_count
缓存策略:
- 对频繁读取的元数据(如文件大小)考虑缓存
- 使用functools.lru_cache装饰器简化缓存实现
python复制from functools