介绍
在这个 Lab 中,你将学习如何使用现代的 pathlib 模块来处理 Python 中的文件和目录路径。虽然较旧的 os.path 模块仍然很常见,但 pathlib 提供了一种面向对象的方法,使路径操作更加直观和易读。你将学习如何以一种适用于 Windows、macOS 和 Linux 等不同操作系统的方式来创建、检查、验证和操作路径、文件和目录。
完成本 Lab 后,你将熟练掌握以下技能:
- 正确地连接路径组件。
- 创建和管理目录。
- 区分绝对路径和相对路径。
- 验证路径的存在性和类型。
- 执行常见的文件操作,如复制、移动和删除。
- 遍历目录树以查找文件和文件夹。
基本路径操作
在这一步,你将学习使用 pathlib 模块进行路径操作的基础知识。这包括连接路径组件、获取当前工作目录以及创建新目录。
pathlib 模块提供了一种优雅的、跨平台的方式来处理文件系统路径。在 Windows 上,路径使用反斜杠 (\);而在类 Unix 系统(Linux, macOS)上,它们使用正斜杠 (/)。使用 pathlib 可以确保你的代码在任何平台上都能正常工作,无需手动调整。
注意:如果你不熟悉使用 WebIDE(如 VS Code),请先在 这个 Lab 学习。
首先,我们来看看如何连接路径组件。/ 运算符是 joinpath() 的一个直观的快捷方式。
- 从左侧的文件浏览器中打开
main.py文件。 - 将
main.py的内容替换为以下代码,以连接路径组件并打印结果。
from pathlib import Path
## 使用 / 运算符连接路径组件
my_path = Path('usr') / 'bin' / 'spam'
print(my_path)
- 从终端运行脚本:
python3 main.py
你将看到以下输出,它演示了 pathlib 如何正确地形成一个类 Unix 风格的路径:
usr/bin/spam
接下来,我们使用 Path.cwd() 来获取当前工作目录。
- 将
main.py的内容替换为以下代码(这将替换掉之前的代码):
from pathlib import Path
## 获取当前工作目录
cwd = Path.cwd()
print(cwd)
- 再次运行脚本:
python3 main.py
输出将显示当前工作目录,在此环境中是 /home/labex/project:
/home/labex/project
最后,我们来创建一个新目录。如果你尝试在父目录不存在的情况下创建目录,会引发 FileNotFoundError。为避免这种情况,请使用 mkdir(parents=True),它会创建所有必需的父目录。
- 将
main.py的内容替换为以下代码,以创建一个嵌套的目录结构:
from pathlib import Path
## 获取当前工作目录
cwd = Path.cwd()
## 定义新嵌套目录的路径
new_dirs = cwd / 'delicious' / 'walnut' / 'waffles'
## 创建目录,包括任何缺失的父目录
new_dirs.mkdir(parents=True, exist_ok=True)
print(f"Successfully created: {new_dirs}")
exist_ok=True参数可以防止目录已存在时引发错误。这意味着你可以多次运行脚本而不会出错。
- 运行脚本:
python3 main.py
脚本将创建嵌套的目录并打印确认信息:
Successfully created: /home/labex/project/delicious/walnut/waffles
你可以通过在终端中运行 ls -R delicious 来验证目录是否创建成功。
绝对路径与相对路径
在这一步,你将学习区分和使用绝对路径与相对路径。
- 绝对路径 (absolute path) 从文件系统的根目录开始(例如,Linux 上的
/或 Windows 上的C:\)。 - 相对路径 (relative path) 是从当前工作目录定义的。
pathlib 提供了检查路径是否为绝对路径、将路径解析为其绝对形式以及从起始点查找相对路径的方法。
首先,我们使用 is_absolute() 来检查路径是否为绝对路径。
- 将
main.py的内容替换为以下代码:
from pathlib import Path
## 检查路径是否为绝对路径
print(f"Path('/') is absolute: {Path('/').is_absolute()}")
print(f"Path('..') is absolute: {Path('..').is_absolute()}")
- 运行脚本:
python3 main.py
输出确认 / 是一个绝对路径,而 ..(父目录)是相对路径。
Path('/') is absolute: True
Path('..') is absolute: False
接下来,你可以使用 resolve() 将相对路径转换为其完整的绝对路径形式。这对于将 .(当前目录)或 ..(父目录)等相对路径转换为其完整、明确的形式非常有用。
- 将
main.py的内容替换为以下代码:
from pathlib import Path
## 将相对路径解析为其绝对形式
## '..' 指向当前工作目录的父目录
resolved_path = Path('..').resolve()
print(f"Current working directory: {Path.cwd()}")
print(f"Resolved path of '..': {resolved_path}")
- 执行脚本:
python3 main.py
输出将显示当前目录及其父目录的解析后的绝对路径。
Current working directory: /home/labex/project
Resolved path of '..': /home/labex
最后,你可以使用 relative_to() 从一个路径计算到另一个路径的相对路径。
- 将
main.py的内容替换为以下代码,以计算相对路径:
from pathlib import Path
## 从一个起始点获取相对路径
## 这显示了相对于根目录 '/' 的 '/etc/passwd' 的路径
relative_path = Path('/etc/passwd').relative_to('/')
print(f"Path to '/etc/passwd' relative to '/': {relative_path}")
- 运行脚本:
python3 main.py
输出显示相对于根目录的路径:
Path to '/etc/passwd' relative to '/': etc/passwd
路径验证与检查
在这一步,你将学习如何检查路径的属性,例如它是否存在,以及它是一个文件还是一个目录。你还将学习如何检索文件元数据,比如文件大小。
一个常见的任务是在尝试访问文件或目录之前验证其是否存在。exists() 方法非常适合这个目的。
- 打开
main.py并将其内容替换为以下代码。这将检查当前目录 (.)、一个文件 (setup.py) 和一个不存在的文件是否存在。
from pathlib import Path
## 检查路径是否存在
print(f"Path('.') exists: {Path('.').exists()}")
print(f"Path('setup.py') exists: {Path('setup.py').exists()}")
print(f"Path('nonexistentfile') exists: {Path('nonexistentfile').exists()}")
- 运行脚本:
python3 main.py
输出将确认哪些路径存在:
Path('.') exists: True
Path('setup.py') exists: True
Path('nonexistentfile') exists: False
你也可以更具体地使用 is_file() 检查路径是否指向一个文件,或使用 is_dir() 检查是否指向一个目录。
- 将
main.py的内容替换为以下代码以执行这些检查:
from pathlib import Path
## 检查路径是否为文件
print(f"Path('setup.py').is_file(): {Path('setup.py').is_file()}")
print(f"Path('/home').is_file(): {Path('/home').is_file()}")
## 检查路径是否为目录
print(f"Path('/').is_dir(): {Path('/').is_dir()}")
print(f"Path('setup.py').is_dir(): {Path('setup.py').is_dir()}")
- 执行脚本:
python3 main.py
输出将正确识别每种路径的类型:
Path('setup.py').is_file(): True
Path('/home').is_file(): False
Path('/').is_dir(): True
Path('setup.py').is_dir(): False
最后,要获取详细的文件信息,例如文件大小,你可以使用 stat() 方法。它返回一个包含各种属性的对象,其中包括以字节为单位的文件大小 st_size。
- 将
main.py的内容替换为以下代码,以获取系统文件的大小:
from pathlib import Path
import os
## 定义文件路径
file_path = Path('/bin/python3')
## 获取文件统计信息
stat_info = file_path.stat()
## 打印以字节为单位的大小
print(f"File: {file_path}")
print(f"Size: {stat_info.st_size} bytes")
- 运行脚本:
python3 main.py
输出将显示 Python 可执行文件的大小(以字节为单位)。确切大小可能会有所不同。
File: /bin/python3
Size: 10024 bytes
文件和文件夹管理
在这一步,你将学习如何使用 shutil 模块结合 pathlib 来执行常见的文件系统操作,例如复制、移动、重命名和删除文件和文件夹。
shutil 模块为文件操作提供了高级函数。我们从复制文件开始。shutil.copy() 将单个文件复制到新的位置。
- 从左侧的文件浏览器中打开
manage_files.py文件。 - 将
manage_files.py的内容替换为以下代码。这将把spam.txt复制到/tmp/delicious目录中。
import shutil
from pathlib import Path
## 定义源和目标
source_file = Path('/tmp/spam.txt')
destination_dir = Path('/tmp/delicious')
## 复制文件
shutil.copy(source_file, destination_dir)
print(f"Copied '{source_file}' to '{destination_dir}'")
## 你也可以在复制时重命名文件
shutil.copy('/tmp/eggs.txt', '/tmp/delicious/eggs2.txt')
print("Copied and renamed 'eggs.txt' to 'eggs2.txt'")
- 运行脚本:
python3 manage_files.py
要复制整个目录树,请使用 shutil.copytree()。
- 将以下行添加到
manage_files.py的末尾(追加到现有代码之后):
## 复制整个目录树
shutil.copytree('/tmp/bacon', '/tmp/bacon_backup')
print("Copied directory '/tmp/bacon' to '/tmp/bacon_backup'")
注意: 如果你多次运行此脚本,
shutil.copytree()将会失败,因为目标目录已经存在。这是预期的行为。目前,只需运行一次即可。
- 再次运行脚本以查看效果。
接下来,我们使用 shutil.move() 来移动和重命名文件。此函数可以移动文件到不同的目录或重命名它。
- 用以下代码替换
manage_files.py的全部内容,以移动bacon.txt:
import shutil
from pathlib import Path
## 如果目标目录不存在,则创建它
Path('/tmp/eggs').mkdir(parents=True, exist_ok=True)
## 移动文件到目录(仅当源文件存在时)
bacon_source = Path('/tmp/bacon.txt')
if bacon_source.exists():
shutil.move('/tmp/bacon.txt', '/tmp/eggs')
print("Moved '/tmp/bacon.txt' to '/tmp/eggs'")
else:
print("'/tmp/bacon.txt' not found (may have been moved already)")
## 移动并重命名文件
## 首先,我们重新创建要移动的文件
Path('/tmp/bacon.txt').touch()
shutil.move('/tmp/bacon.txt', '/tmp/eggs/new_bacon.txt')
print("Moved and renamed '/tmp/bacon.txt' to '/tmp/eggs/new_bacon.txt'")
注意: 代码在移动文件之前会检查源文件是否存在,因此你可以安全地多次运行此脚本。第二部分将始终创建新文件并将其移动。
- 运行脚本。
最后,我们来看一下删除文件和文件夹的操作。
Path.unlink()删除单个文件。Path.rmdir()删除空目录。shutil.rmtree()删除目录及其所有内容。
- 将
manage_files.py的全部内容替换为以下内容:
from pathlib import Path
import shutil
## 创建一个要删除的文件
file_to_delete = Path('/tmp/deleteme.txt')
file_to_delete.touch()
print(f"Created '{file_to_delete}'")
## 删除文件
file_to_delete.unlink()
print(f"Deleted '{file_to_delete}'")
## 创建一个要删除的目录
dir_to_delete = Path('/tmp/temp_dir')
dir_to_delete.mkdir(exist_ok=True)
## rmdir() 只对空目录有效
dir_to_delete.rmdir()
print(f"Deleted empty directory '{dir_to_delete}'")
## 使用 shutil.rmtree() 删除目录及其所有内容
## 注意:如果目录不存在,这将失败,但对于此示例来说没问题
if Path('/tmp/bacon_backup').exists():
shutil.rmtree('/tmp/bacon_backup')
print("Deleted directory '/tmp/bacon_backup' and all its contents")
else:
print("Directory '/tmp/bacon_backup' does not exist (may have been deleted already)")
注意: 如果你多次运行此脚本,某些操作可能会失败,因为文件或目录已被删除。这是正常的,它表明这些操作不是幂等的(不能安全地重复执行)。
- 运行脚本以执行删除操作。
目录遍历
在这一步,你将学习如何列出目录的内容并递归地遍历目录树。
要列出目录的内容,你可以使用 iterdir() 方法,它会返回一个包含路径中所有项(文件和目录)的迭代器。
- 首先,我们设置一个示例目录结构。在终端中运行此命令:
mkdir -p ~/project/data/level1 && touch ~/project/data/file0.txt ~/project/data/level1/file1.txt
- 现在,打开
main.py并用以下代码替换其内容,以列出/usr/bin目录的内容。
from pathlib import Path
## 列出目录内容
print("Contents of '/usr/bin':")
count = 0
for f in Path('/usr/bin').iterdir():
if count < 5: ## 仅打印前 5 个项以保持输出简短
print(f)
count += 1
else:
break
- 运行脚本:
python3 main.py
输出将显示 /usr/bin 中前几个文件和目录。确切的列表可能会有所不同。
Contents of '/usr/bin':
/usr/bin/[
/usr/bin/addpart
/usr/bin/apt
/usr/bin/apt-cache
/usr/bin/apt-cdrom
你可以将 iterdir() 与 stat() 结合使用,以计算目录中所有文件的大小总和。
- 将
main.py的内容替换为以下代码。请注意,此代码仅累加直接子项的大小,不会递归到子目录中。
from pathlib import Path
total_size = 0
directory = Path('/usr/bin')
for sub_path in directory.iterdir():
if sub_path.is_file(): ## 确保只累加文件的大小
total_size += sub_path.stat().st_size
print(f"Total size of files in '{directory}': {total_size} bytes")
- 执行脚本。它将打印出直接位于
/usr/bin中所有文件的大小总和。
对于更复杂的任务,你可能需要“遍历”(walk)整个目录树,访问所有子目录和文件。rglob() 方法非常适合此任务。它会递归地查找所有匹配特定模式的路径。使用 * 作为模式可以匹配所有内容。
- 首先,为本示例创建一个更复杂的目录结构。在终端中运行此命令:
mkdir -p /tmp/delicious/cats /tmp/delicious/walnut/waffles
touch /tmp/delicious/spam.txt /tmp/delicious/cats/catnames.txt /tmp/delicious/walnut/waffles/butter.txt
注意:你可以安全地多次运行这些命令。如果目录已存在,
mkdir -p命令不会失败;如果文件已存在,touch会更新文件时间戳。
- 将
main.py的内容替换为以下代码,以遍历/tmp/delicious目录:
from pathlib import Path
p = Path('/tmp/delicious')
print(f"Walking directory tree for: {p}")
for i in p.rglob('*'):
print(i)
- 运行脚本:
python3 main.py
输出将列出 /tmp/delicious 及其子目录中的每一个文件和文件夹,演示了 rglob() 如何遍历整个树。
Walking directory tree for: /tmp/delicious
/tmp/delicious/spam.txt
/tmp/delicious/eggs2.txt
/tmp/delicious/cats
/tmp/delicious/walnut
/tmp/delicious/cats/catnames.txt
/tmp/delicious/walnut/waffles
/tmp/delicious/walnut/waffles/butter.txt
总结
恭喜你完成了本次实验!你已经学会了如何使用现代的 pathlib 模块在 Python 中有效地管理文件和目录路径。
在本次实验中,你练习了:
- 使用
Path对象和/运算符以跨平台的方式创建和连接路径。 - 获取当前工作目录,并使用
mkdir(parents=True)创建嵌套目录。 - 使用
is_absolute()、resolve()和relative_to()区分绝对路径和相对路径,并在它们之间进行转换。 - 使用
exists()、is_file()和is_dir()验证路径,并使用stat()检查文件元数据。 - 使用
shutil模块和pathlib方法执行复制、移动和删除等基本文件管理任务。 - 使用
iterdir()列出目录内容,并使用rglob()递归查找项,从而遍历目录结构。
这些技能对于编写与文件系统交互的健壮且可维护的 Python 应用程序至关重要。



