第6章:实战项目——综合应用:文件管理器
章节介绍
想象一下,你正在编写一个脚本,用来整理电脑里杂乱无章的下载文件夹。一开始,你写了几行代码来列出文件,又写了几行来移动文件。很快,这个脚本变得又长又乱,重复的代码到处都是,想改点什么都得小心翼翼。
这恰恰是函数和模块化设计要解决的问题。与其把所有操作都堆砌在一起,不如把它们拆解成一个个独立、可复用的功能单元,这就是函数。而将相关的函数组织到一起,形成清晰的代码结构,便是模块化。
例如,在文件管理器项目中,一个最基本的操作是检查某个路径是否存在。我们可以在代码里直接写 os.path.exists(path),但如果这个检查逻辑在多处使用,或者未来想增加一些额外的验证,修改起来会很麻烦。更好的做法是把它封装成一个函数:
def path_exists(path: str) -> bool:
"""
检查文件或目录路径是否存在。
Args:
path (str): 要检查的路径。
Returns:
bool: 如果路径存在则返回 True,否则返回 False。
"""
import os
return os.path.exists(path)
现在,path_exists 成为了一个清晰的自描述工具。在代码的任何地方,你只需要关心“检查这个路径是否存在”这个意图,而不用记着具体的 os.path 调用。如果有一天需要修改检测逻辑,也只需改动这一个地方。
这就是函数的核心价值:封装细节,提供简洁的接口。再看另一个例子,获取文件信息。文件信息可能包括大小、修改时间、权限等,涉及多个系统调用。我们可以把它们打包:
def get_file_info(file_path: str) -> dict:
"""
获取指定文件的详细信息。
Args:
file_path (str): 目标文件的路径。
Returns:
dict: 包含文件信息的字典。键包括:
'name' (str): 文件名,
'size' (int): 大小(字节),
'modify_time' (float): 最后修改时间(时间戳),
'access_time' (float): 最后访问时间(时间戳),
'is_dir' (bool): 是否为目录,
'permissions' (int): 权限位(八进制数字)。
如果文件不存在,返回一个仅包含 'name' 和 'error' 键的字典。
"""
import os
info = {'name': os.path.basename(file_path)}
try:
stat_result = os.stat(file_path)
info['size'] = stat_result.st_size
info['modify_time'] = stat_result.st_mtime
info['access_time'] = stat_result.st_atime
info['is_dir'] = os.path.isdir(file_path)
info['permissions'] = stat_result.st_mode & 0o777 # 提取权限位
except FileNotFoundError:
info['error'] = f"文件 '{file_path}' 不存在。"
except OSError as e:
info['error'] = f"获取文件信息时出错: {e}"
return info
调用这个函数,你得到一个包含所有信息的字典,而不必关心底层是如何分别获取大小和时间的。它隐藏了复杂性,让主程序逻辑保持简洁。
当项目逐渐变大,函数越来越多时,我们就需要进行模块化。模块化意味着将代码按功能划分到不同的文件中。例如,所有和路径处理相关的函数,如 get_current_directory, normalize_path,可以放在一个叫 path_utils.py 的文件里。所有文件操作,如 copy_file, move_file, delete_file,可以放在 file_operations.py 里。而像 format_file_size, format_timestamp 这样的格式化工具,则可以归入 utils.py。
这样做有什么好处?假设你要实现一个搜索功能,需要在目录树中查找特定文件,然后显示其详细信息。你的代码可能会这样组织:
# 在主程序中
from file_operations import search_files, get_file_info
from utils import format_file_size, format_timestamp
results = search_files('/some/path', '*.txt')
for file_path in results:
info = get_file_info(file_path)
print(f"{file_path} - {format_file_size(info['size'])} - {format_timestamp(info['mtime'])}")
看,主程序的逻辑变得非常清晰:搜索、获取信息、格式化、展示。所有的底层实现细节都被隔离在各自的模块中。这不仅仅是代码看起来更整洁,它带来了实际的好处:易于维护(bug通常被限制在单个模块内)、便于协作(多人可以同时开发不同模块)、利于复用(utils.py 模块可以轻松搬到下一个项目中)。
文件管理器这个项目,完美地展示了从需求到函数,再从函数到模块的自然演进过程。我们首先识别出核心操作(列出、复制、移动、删除等),为每个操作设计出函数接口。然后,我们发现这些函数之间存在逻辑分组(路径操作、文件操作、信息工具),于是将它们分别放入不同的模块,最终形成一个结构清晰、易于扩展的应用程序骨架。
通过这种模块化的设计,你的文件管理器将不再是一堆杂乱无章的代码,而是一个由精心设计的构件组装而成的可靠工具。
核心概念
理解如何将复杂任务拆解为清晰、可管理的代码块,是编写任何实用程序的第一步。函数就是实现这种拆解的核心工具,而模块化则是将这些工具组织起来的优雅方式。让我们以一个文件管理器所需的功能为例,看看它们是如何工作的。
为什么需要函数呢?想象一下,你想知道当前在哪个文件夹里。在代码里,你可能需要写好几行来与操作系统交互。但如果把这个需求提炼成一个明确的任务——“获取当前工作目录”,并把它包装成一个函数,比如叫 get_current_directory,事情就变得简单了。你不再需要关心背后的复杂逻辑,只需要知道调用这个函数就能得到答案。
def get_current_directory() -> str:
"""
获取当前工作目录的绝对路径。
Returns:
str: 当前目录的绝对路径字符串。
"""
import os
return os.getcwd()
这就是函数的第一个好处:封装。它把一段实现特定功能的代码隐藏在一个名字后面。一个典型的函数,就像我们看到的 copy_file,由几个关键部分组成:
def copy_file(source: str, destination: str, overwrite: bool = False) -> bool:
"""
将源文件复制到目标路径。
Args:
source (str): 源文件的路径。
destination (str): 目标文件或目录的路径。如果是目录,则文件将复制到该目录下,保持原名。
overwrite (bool, optional): 如果目标文件已存在,是否覆盖。默认为 False。
Returns:
bool: 复制成功返回 True,否则返回 False。
"""
import os
import shutil
source = normalize_path(source)
destination = normalize_path(destination)
# 检查源文件
if not is_file(source):
print(f"错误:源文件 '{source}' 不存在或不是一个文件。")
return False
# 如果目标是目录,则在目录下使用源文件名
if is_directory(destination):
dest_file = os.path.join(destination, os.path.basename(source))
else:
dest_file = destination
# 检查目标文件是否存在
if path_exists(dest_file) and not overwrite:
print(f"错误:目标文件 '{dest_file}' 已存在且未启用覆盖选项。")
return False
try:
shutil.copy2(source, dest_file) # copy2 会保留元数据(如修改时间)
print(f"成功:文件已从 '{source}' 复制到 '{dest_file}'。")
return True
except PermissionError:
print(f"错误:没有足够的权限进行复制。")
except OSError as e:
print(f"错误:复制文件时发生未知错误: {e}")
return False
这里,def copy_file(source: str, destination: str, overwrite: bool = False) -> bool 是函数的签名。
def是定义函数的关键字。copy_file是函数名,最好能直观描述其功能。(source: str, destination: str, overwrite: bool = False)是参数列表,它是函数与外界沟通的接口。调用者通过提供source(源路径)和destination(目标路径)来告诉函数操作的对象。overwrite: bool = False是一个带默认值的参数,这使得调用更灵活,你可以只传前两个参数。-> bool是返回类型注解,提示这个函数执行后会返回一个布尔值,表示成功或失败。- 函数内部的代码是它的具体实现,完成了复制的所有步骤。
- 最后的
return语句将结果传递出去。
通过这种方式,一个复杂的复制操作被简化为一行清晰的调用:success = copy_file("报告.txt", "备份/报告.txt")。这带来了另外两个巨大优势:复用和易于维护。假如你需要在程序里十几个地方复制文件,你只需要调用同一个函数,而不是把同样的代码写十几遍。当未来需要修改复制逻辑(比如增加进度显示)时,你也只需在一个地方(函数内部)修改,所有调用它的地方都会自动生效。
看看 normalize_path 函数,它在处理用户输入的路径时非常有用,无论用户输入的是 ./docs、~/downloads 还是 C:\\Users\\Name\\docs,这个函数都能将其转换为标准格式。你会发现在检查路径、列出文件内容之前,都可能需要先调用它来“净化”输入。
def normalize_path(path: str) -> str:
"""
将给定的路径字符串转换为绝对路径。
处理相对路径、`~`(用户主目录)和多余的分隔符。
Args:
path (str): 输入的路径字符串。
Returns:
str: 标准化后的绝对路径。
"""
import os
# 展开用户主目录符号 `~`
expanded_path = os.path.expanduser(path)
# 将相对路径转换为绝对路径(基于当前工作目录)
absolute_path = os.path.abspath(expanded_path)
# 标准化路径,移除多余的`..`、`.`和分隔符
normalized_path = os.path.normpath(absolute_path)
return normalized_path
当这样的函数越来越多,比如管理文件的、获取信息的、格式化显示的,我们就需要一种方式将它们归类整理,这就是模块化。你可以把所有这些与文件操作相关的函数都放在一个叫 file_utils.py 的文件里。这个 .py 文件就是一个模块。
假设我们有一个主程序 main.py,现在想使用这些功能。我们不需要把成百上千行代码都塞进一个文件,而是通过导入模块来使用:
# main.py
import file_utils
current_path = file_utils.get_current_directory()
contents = file_utils.list_directory_contents(current_path)
或者,为了调用时更简洁:
from file_utils import copy_file, delete_file
copy_file("a.txt", "b.txt")
模块化让项目的结构一目了然。file_utils 模块负责所有底层文件操作,main.py 负责用户界面和逻辑流程。它们各司其职,互不干扰。如果你想为这个文件管理器添加一个网络备份功能,完全可以新建一个 network_backup.py 模块,并在其中复用 file_utils 里的函数来读取本地文件。
那么,如何设计好的函数呢?一些简单的原则很有帮助:
- 单一职责:一个函数最好只做好一件事。
is_file只判断是否是文件,get_file_info只收集信息,职责清晰。 - 明确的命名:函数名应该像
delete_directory这样,看名字就能猜出它做什么。 - 利用返回值:用返回值(如
True/False)或抛出来报告执行结果和状态,而不是只在内部打印信息。 - 善用参数默认值:像
confirm: bool = True这样,为常用选项提供默认值,让常用调用更简洁,同时又保留灵活性。
最后,看看 format_file_size 和 format_timestamp 这两个函数,它们不直接操作文件,而是负责格式化呈现。将它们单独作为函数,而不是直接写在显示代码里,意味着你可以在命令行界面、图形界面甚至生成网页报告时都使用同一套格式化规则,保证了整个程序风格的一致性。
def format_file_size(size_in_bytes: int) -> str:
"""
将文件大小(字节)格式化为更易读的单位(如 KB, MB, GB)。
Args:
size_in_bytes (int): 文件大小,单位为字节。
Returns:
str: 格式化后的字符串,包含单位和最多两位小数。
"""
if size_in_bytes < 0:
return "无效大小"
units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
unit_index = 0
size = float(size_in_bytes)
# 循环除以1024,直到大小小于1024或达到最大单位
while size >= 1024 and unit_index < len(units) - 1:
size /= 1024.0
unit_index += 1
# 格式化输出:如果单位是'B',则显示整数,否则显示两位小数
if unit_index == 0:
return f"{int(size)} {units[unit_index]}"
else:
return f"{size:.2f} {units[unit_index]}"
def format_timestamp(timestamp: float, format_str: str = "%Y-%m-%d %H:%M:%S") -> str:
"""
将Unix时间戳转换为本地时间的可读字符串。
Args:
timestamp (float): Unix时间戳(秒数)。
format_str (str, optional): 时间格式字符串。默认为 "%Y-%m-%d %H:%M:%S"。
Returns:
str: 格式化后的日期时间字符串。如果时间戳无效,返回空字符串。
"""
import time
from datetime import datetime
try:
# 使用 datetime 模块进行更稳健的转换
dt_object = datetime.fromtimestamp(timestamp)
return dt_object.strftime(format_str)
except (ValueError, OSError):
# 处理无效的时间戳(如负数或过大值)
return ""
将大问题分解为函数,再将相关函数组织成模块,就像为文件管理器搭建起了坚实而灵活的骨架。每个部分都易于理解、测试和修改,这是构建可维护、可扩展应用程序的基石。
实践应用
当你开始构思一个文件管理器时,面对从列出文件到复制删除的各种操作,直接编写一大段代码会很快变得难以管理和维护。这时,Python的函数与模块化思想就成了将复杂任务分解、组织的利器。
想一想,如果你需要频繁地获取当前工作目录,每次都写 os.getcwd() 虽然可以,但代码中会散落着对这个具体实现的依赖。更好的做法是,将“获取当前目录”这个意图明确地定义为一个函数。例如,我们可以这样设计:
def get_current_directory() -> str:
"""
获取当前工作目录的绝对路径。
Returns:
str: 当前目录的绝对路径字符串。
"""
import os
return os.getcwd()
这个函数封装了底层细节。当你在程序的其他地方需要当前路径时,只需调用 get_current_directory(),代码的意图一目了然,而且如果未来需要改变获取目录的方式(比如从配置中读取),你只需要修改这一个函数。
函数的核心价值在于“封装”和“复用”。再看一个更复杂的例子,列出目录内容。直接使用 os.listdir() 返回的列表可能包含凌乱的隐藏文件,并且缺乏详细信息。通过定义一个功能更聚焦的函数,我们可以统一处理这些逻辑:
def list_directory_contents(dir_path: str, show_hidden: bool = False) -> list:
"""
获取指定目录下的所有文件和子目录的名称列表。
Args:
dir_path (str): 要列出内容的目录路径。
show_hidden (bool, optional): 是否显示以 '.' 开头的隐藏文件。默认为 False。
Returns:
list: 目录中条目名称的列表。如果目录不存在或不可读,返回空列表。
Raises:
PermissionError: 如果没有目录的读取权限。
"""
import os
entries = []
try:
# 获取目录下所有条目
all_entries = os.listdir(dir_path)
for entry in all_entries:
# 根据参数决定是否过滤隐藏文件
if not show_hidden and entry.startswith('.'):
continue
entries.append(entry)
except FileNotFoundError:
# 目录不存在,返回空列表
print(f"警告:目录 '{dir_path}' 不存在。")
except PermissionError:
print(f"错误:没有权限读取目录 '{dir_path}'。")
raise # 可以选择重新抛出异常或处理
return entries
现在,当你需要列出目录时,可以通过 show_hidden 参数轻松控制是否显示隐藏文件。这个函数成为了一个可靠的工具,你可以在项目中任何需要列出目录的地方使用它,保证了行为的一致性。
随着功能的增加,你会发现类似的函数越来越多:复制文件、移动文件、获取文件信息……如果它们都堆在同一个文件里,代码会变得冗长且难以阅读。这时,就需要模块化来管理了。
模块化是将相关的函数(以及类、变量)组织到独立文件中的过程。我们可以创建一个名为 file_operations.py 的模块,专门存放所有文件操作相关的函数。在这个模块里,你可能会集合以下函数:
def copy_file(source: str, destination: str, overwrite: bool = False) -> bool:
"""
将源文件复制到目标路径。
Args:
source (str): 源文件的路径。
destination (str): 目标文件或目录的路径。如果是目录,则文件将复制到该目录下,保持原名。
overwrite (bool, optional): 如果目标文件已存在,是否覆盖。默认为 False。
Returns:
bool: 复制成功返回 True,否则返回 False。
"""
import os
import shutil
source = normalize_path(source)
destination = normalize_path(destination)
# 检查源文件
if not is_file(source):
print(f"错误:源文件 '{source}' 不存在或不是一个文件。")
return False
# 如果目标是目录,则在目录下使用源文件名
if is_directory(destination):
dest_file = os.path.join(destination, os.path.basename(source))
else:
dest_file = destination
# 检查目标文件是否存在
if path_exists(dest_file) and not overwrite:
print(f"错误:目标文件 '{dest_file}' 已存在且未启用覆盖选项。")
return False
try:
shutil.copy2(source, dest_file) # copy2 会保留元数据(如修改时间)
print(f"成功:文件已从 '{source}' 复制到 '{dest_file}'。")
return True
except PermissionError:
print(f"错误:没有足够的权限进行复制。")
except OSError as e:
print(f"错误:复制文件时发生未知错误: {e}")
return False
def move_file(source: str, destination: str, overwrite: bool = False) -> bool:
"""
移动或重命名文件。功能类似于 `os.rename` 和 `shutil.move`。
Args:
source (str): 源文件的路径。
destination (str): 目标文件或目录的路径。
overwrite (bool, optional): 是否覆盖已存在的目标文件。默认为 False。
Returns:
bool: 移动成功返回 True,否则返回 False。
"""
import os
import shutil
source = normalize_path(source)
destination = normalize_path(destination)
if not path_exists(source):
print(f"错误:源路径 '{source}' 不存在。")
return False
# 如果目标是目录,则在目录下使用源文件名
if is_directory(destination):
dest_path = os.path.join(destination, os.path.basename(source))
else:
dest_path = destination
# 检查目标是否存在
if path_exists(dest_path) and not overwrite:
print(f"错误:目标路径 '{dest_path}' 已存在且未启用覆盖选项。")
return False
try:
# 如果目标已存在且允许覆盖,先尝试删除(shutil.move在某些情况下不直接覆盖)
if path_exists(dest_path) and overwrite:
if is_directory(dest_path):
shutil.rmtree(dest_path)
else:
os.remove(dest_path)
shutil.move(source, dest_path)
print(f"成功:文件/目录已从 '{source}' 移动到 '{dest_path}'。")
return True
except PermissionError:
print(f"错误:没有足够的权限进行移动。")
except OSError as e:
print(f"错误:移动文件/目录时发生错误: {e}")
return False
def delete_file(file_path: str, confirm: bool = True) -> bool:
"""
删除一个文件。
Args:
file_path (str): 要删除的文件路径。
confirm (bool, optional): 是否在删除前请求用户确认。默认为 True。
Returns:
bool: 删除成功返回 True,否则返回 False。
"""
import os
file_path = normalize_path(file_path)
if not path_exists(file_path):
print(f"警告:文件 '{file_path}' 不存在。")
return False
if not is_file(file_path):
print(f"错误:'{file_path}' 不是一个文件。请使用删除目录的函数。")
return False
if confirm:
user_input = input(f"确定要删除文件 '{file_path}' 吗?(y/N): ").strip().lower()
if user_input != 'y' and user_input != 'yes':
print("取消删除。")
return False
try:
os.remove(file_path)
print(f"成功:文件 '{file_path}' 已被删除。")
return True
except PermissionError:
print(f"错误:没有权限删除文件 '{file_path}'。")
except OSError as e:
print(f"错误:删除文件时发生未知错误: {e}")
return False
def get_file_info(file_path: str) -> dict:
"""
获取指定文件的详细信息。
Args:
file_path (str): 目标文件的路径。
Returns:
dict: 包含文件信息的字典。键包括:
'name' (str): 文件名,
'size' (int): 大小(字节),
'modify_time' (float): 最后修改时间(时间戳),
'access_time' (float): 最后访问时间(时间戳),
'is_dir' (bool): 是否为目录,
'permissions' (int): 权限位(八进制数字)。
如果文件不存在,返回一个仅包含 'name' 和 'error' 键的字典。
"""
import os
info = {'name': os.path.basename(file_path)}
try:
stat_result = os.stat(file_path)
info['size'] = stat_result.st_size
info['modify_time'] = stat_result.st_mtime
info['access_time'] = stat_result.st_atime
info['is_dir'] = os.path.isdir(file_path)
info['permissions'] = stat_result.st_mode & 0o777 # 提取权限位
except FileNotFoundError:
info['error'] = f"文件 '{file_path}' 不存在。"
except OSError as e:
info['error'] = f"获取文件信息时出错: {e}"
return info
这样,file_operations.py 就成了一个功能明确的“工具箱”。在你的主程序中,不再需要关心 shutil.copy2 或 os.remove 这些底层调用,只需通过一行清晰的导入语句来使用它们:from file_operations import copy_file, move_file。
模块化的优势立刻显现:代码结构清晰,不同功能各司其职;这个文件操作模块可以被其他项目轻松复用;团队成员可以分工负责不同的模块。
让我们看一个简单的应用场景。假设你的文件管理器主程序 main.py 需要实现一个“展示当前目录详情”的功能,它可能会这样利用已有的函数和模块:
# main.py
import file_ops # 假设我们的模块名为 file_ops.py
def display_current_directory_details():
current_dir = file_ops.get_current_directory()
print(f"当前目录: {current_dir}")
contents = file_ops.list_directory_contents(current_dir, show_hidden=False)
print(f"\n包含 {len(contents)} 个项目:")
for item in contents:
item_path = os.path.join(current_dir, item)
if file_ops.is_file(item_path):
info = file_ops.get_file_info(item_path)
size_str = file_ops.format_file_size(info['size'])
print(f" [文件] {item} - 大小: {size_str}")
elif file_ops.is_directory(item_path):
print(f" [目录] {item}")
在这个例子中,main.py 的逻辑专注于流程控制(获取目录、循环、打印),而所有具体的文件操作细节都委托给了 file_ops 模块中的函数。这种分离使得主程序简洁、易读,也使得文件操作的功能易于单独测试和调试。
所以,从编写独立的函数来封装单一功能,到将这些函数组织成逻辑模块,你实际上是在构建一套属于自己的、高层次的API。文件管理器这个项目,正是这些模块化组件在用户交互逻辑调度下的一个综合应用。通过这种方式构建程序,复杂度被有效地控制,代码也更具可维护性和扩展性。
章节总结
让我们把这些文件管理的操作放到一边,先聊聊构建它们的基本单元:函数,以及如何用模块化的思维来组织它们。你刚刚看到的那些 get_current_directory、copy_file 等功能,都是一个个函数。它们是Python编程中避免重复劳动、让代码清晰可用的关键。
想象一下,如果没有函数,每次你想知道当前在哪个文件夹,都得写一遍获取路径的代码;每次删除文件,都要重新处理确认逻辑。这很快就会变得一团糟。函数的作用,就是把一段完成特定任务的代码“打包”起来,给它起个名字,以后想用的时候,叫一声这个名字就行了。
一个函数就像一个小机器。比如 `
def get_current_directory() -> str:
"""
获取当前工作目录的绝对路径。
Returns:
str: 当前目录的绝对路径字符串。
"""
import os
return os.getcwd()
这台机器,你按下开关(调用它),它就会运转并吐出一个结果(返回当前目录的字符串)。它不需要你提供任何原料(参数)。而另一台机器,比如
def delete_file(file_path: str, confirm: bool = True) -> bool:
"""
删除一个文件。
Args:
file_path (str): 要删除的文件路径。
confirm (bool, optional): 是否在删除前请求用户确认。默认为 True。
Returns:
bool: 删除成功返回 True,否则返回 False。
"""
import os
file_path = normalize_path(file_path)
if not path_exists(file_path):
print(f"警告:文件 '{file_path}' 不存在。")
return False
if not is_file(file_path):
print(f"错误:'{file_path}' 不是一个文件。请使用删除目录的函数。")
return False
if confirm:
user_input = input(f"确定要删除文件 '{file_path}' 吗?(y/N): ").strip().lower()
if user_input != 'y' and user_input != 'yes':
print("取消删除。")
return False
try:
os.remove(file_path)
print(f"成功:文件 '{file_path}' 已被删除。")
return True
except PermissionError:
print(f"错误:没有权限删除文件 '{file_path}'。")
except OSError as e:
print(f"错误:删除文件时发生未知错误: {e}")
return False
,就需要你提供原料(file_path 这个参数),并且还有一个确认开关(confirm` 参数)让你决定是否要二次确认。
仔细看这些函数的“说明书”(函数签名),你会发现它们通常包含几个部分:def 关键字、函数名、括号里的参数、一个箭头 -> 指明它最后会返回什么类型的结果。函数内部的黑箱逻辑我们暂时不管,但它的输入和输出接口是清晰定义的。这种定义方式让代码的使用者和编写者达成了默契:我给你什么样的数据,你保证还我什么样的结果。
参数让函数变得灵活。看看 `
def list_directory_contents(dir_path: str, show_hidden: bool = False) -> list:
"""
获取指定目录下的所有文件和子目录的名称列表。
Args:
dir_path (str): 要列出内容的目录路径。
show_hidden (bool, optional): 是否显示以 '.' 开头的隐藏文件。默认为 False。
Returns:
list: 目录中条目名称的列表。如果目录不存在或不可读,返回空列表。
Raises:
PermissionError: 如果没有目录的读取权限。
"""
import os
entries = []
try:
# 获取目录下所有条目
all_entries = os.listdir(dir_path)
for entry in all_entries:
# 根据参数决定是否过滤隐藏文件
if not show_hidden and entry.startswith('.'):
continue
entries.append(entry)
except FileNotFoundError:
# 目录不存在,返回空列表
print(f"警告:目录 '{dir_path}' 不存在。")
except PermissionError:
print(f"错误:没有权限读取目录 '{dir_path}'。")
raise # 可以选择重新抛出异常或处理
return entries
,它除了必须的 dir_path,还有一个 show_hidden参数,并且默认是False。这意味着你可以用 list_directory_contents(“/some/path”)只列出普通文件,也可以通过list_directory_contents(“/some/path”, True)` 来显示隐藏文件。默认参数是个很贴心的设计,它覆盖了最常用的场景,同时在需要时又能提供更精细的控制。
好了,现在我们有十几个、甚至几十个这样好用的函数了。如果把它们全都写在一个巨大的Python文件里,找起来会不会像在一个塞满东西却没有任何文件夹的硬盘里找文件一样痛苦?这就是“模块化”要解决的问题。
模块化,简单说就是“分而治之”。我们把相关的函数分组,分别放在不同的 .py 文件里,这些文件就是“模块”。例如,我们可以创建:
path_utils.py:里面放所有处理路径的函数,像 `
def normalize_path(path: str) -> str:
"""
将给定的路径字符串转换为绝对路径。
处理相对路径、`~`(用户主目录)和多余的分隔符。
Args:
path (str): 输入的路径字符串。
Returns:
str: 标准化后的绝对路径。
"""
import os
# 展开用户主目录符号 `~`
expanded_path = os.path.expanduser(path)
# 将相对路径转换为绝对路径(基于当前工作目录)
absolute_path = os.path.abspath(expanded_path)
# 标准化路径,移除多余的`..`、`.`和分隔符
normalized_path = os.path.normpath(absolute_path)
return normalized_path
、
def path_exists(path: str) -> bool:
"""
检查文件或目录路径是否存在。
Args:
path (str): 要检查的路径。
Returns:
bool: 如果路径存在则返回 True,否则返回 False。
"""
import os
return os.path.exists(path)
、
def is_directory(path: str) -> bool:
"""
检查路径是否指向一个存在的目录。
Args:
path (str): 要检查的路径。
Returns:
bool: 如果路径存在且是一个目录则返回 True,否则返回 False。
"""
import os
return os.path.isdir(path)
`。
file_ops.py:专门负责文件的核心操作,比如 `
def copy_file(source: str, destination: str, overwrite: bool = False) -> bool:
"""
将源文件复制到目标路径。
Args:
source (str): 源文件的路径。
destination (str): 目标文件或目录的路径。如果是目录,则文件将复制到该目录下,保持原名。
overwrite (bool, optional): 如果目标文件已存在,是否覆盖。默认为 False。
Returns:
bool: 复制成功返回 True,否则返回 False。
"""
import os
import shutil
source = normalize_path(source)
destination = normalize_path(destination)
# 检查源文件
if not is_file(source):
print(f"错误:源文件 '{source}' 不存在或不是一个文件。")
return False
# 如果目标是目录,则在目录下使用源文件名
if is_directory(destination):
dest_file = os.path.join(destination, os.path.basename(source))
else:
dest_file = destination
# 检查目标文件是否存在
if path_exists(dest_file) and not overwrite:
print(f"错误:目标文件 '{dest_file}' 已存在且未启用覆盖选项。")
return False
try:
shutil.copy2(source, dest_file) # copy2 会保留元数据(如修改时间)
print(f"成功:文件已从 '{source}' 复制到 '{dest_file}'。")
return True
except PermissionError:
print(f"错误:没有足够的权限进行复制。")
except OSError as e:
print(f"错误:复制文件时发生未知错误: {e}")
return False
、
def move_file(source: str, destination: str, overwrite: bool = False) -> bool:
"""
移动或重命名文件。功能类似于 `os.rename` 和 `shutil.move`。
Args:
source (str): 源文件的路径。
destination (str): 目标文件或目录的路径。
overwrite (bool, optional): 是否覆盖已存在的目标文件。默认为 False。
Returns:
bool: 移动成功返回 True,否则返回 False。
"""
import os
import shutil
source = normalize_path(source)
destination = normalize_path(destination)
if not path_exists(source):
print(f"错误:源路径 '{source}' 不存在。")
return False
# 如果目标是目录,则在目录下使用源文件名
if is_directory(destination):
dest_path = os.path.join(destination, os.path.basename(source))
else:
dest_path = destination
# 检查目标是否存在
if path_exists(dest_path) and not overwrite:
print(f"错误:目标路径 '{dest_path}' 已存在且未启用覆盖选项。")
return False
try:
# 如果目标已存在且允许覆盖,先尝试删除(shutil.move在某些情况下不直接覆盖)
if path_exists(dest_path) and overwrite:
if is_directory(dest_path):
shutil.rmtree(dest_path)
else:
os.remove(dest_path)
shutil.move(source, dest_path)
print(f"成功:文件/目录已从 '{source}' 移动到 '{dest_path}'。")
return True
except PermissionError:
print(f"错误:没有足够的权限进行移动。")
except OSError as e:
print(f"错误:移动文件/目录时发生错误: {e}")
return False
、
def delete_file(file_path: str, confirm: bool = True) -> bool:
"""
删除一个文件。
Args:
file_path (str): 要删除的文件路径。
confirm (bool, optional): 是否在删除前请求用户确认。默认为 True。
Returns:
bool: 删除成功返回 True,否则返回 False。
"""
import os
file_path = normalize_path(file_path)
if not path_exists(file_path):
print(f"警告:文件 '{file_path}' 不存在。")
return False
if not is_file(file_path):
print(f"错误:'{file_path}' 不是一个文件。请使用删除目录的函数。")
return False
if confirm:
user_input = input(f"确定要删除文件 '{file_path}' 吗?(y/N): ").strip().lower()
if user_input != 'y' and user_input != 'yes':
print("取消删除。")
return False
try:
os.remove(file_path)
print(f"成功:文件 '{file_path}' 已被删除。")
return True
except PermissionError:
print(f"错误:没有权限删除文件 '{file_path}'。")
except OSError as e:
print(f"错误:删除文件时发生未知错误: {e}")
return False
`。
directory_ops.py:管理目录的创建 `
def create_directory(dir_path: str, parents: bool = False) -> bool:
"""
创建一个新目录。
Args:
dir_path (str): 要创建的目录路径。
parents (bool, optional): 如果为 True,则创建所有必需的父目录(类似于 `mkdir -p`)。
默认为 False。
Returns:
bool: 目录创建成功或已存在则返回 True,否则返回 False。
"""
import os
dir_path = normalize_path(dir_path)
if path_exists(dir_path):
if is_directory(dir_path):
print(f"提示:目录 '{dir_path}' 已存在。")
return True
else:
print(f"错误:路径 '{dir_path}' 已存在,但不是目录。")
return False
try:
if parents:
os.makedirs(dir_path, exist_ok=True) # exist_ok=True 确保父目录存在时不报错
else:
os.mkdir(dir_path)
print(f"成功:目录 '{dir_path}' 已创建。")
return True
except FileNotFoundError:
print(f"错误:无法创建目录,因为父目录不存在。请尝试设置 `parents=True`。")
except PermissionError:
print(f"错误:没有权限在目标位置创建目录。")
except OSError as e:
print(f"错误:创建目录时发生未知错误: {e}")
return False
、删除
def delete_directory(dir_path: str, recursive: bool = False, confirm: bool = True) -> bool:
"""
删除一个目录。
Args:
dir_path (str): 要删除的目录路径。
recursive (bool, optional): 如果为 True,则递归删除目录内的所有内容。默认为 False。
confirm (bool, optional): 是否在删除前请求用户确认。当 recursive 为 True 时尤其重要。默认为 True。
Returns:
bool: 删除成功返回 True,否则返回 False。
"""
import os
import shutil
dir_path = normalize_path(dir_path)
if not path_exists(dir_path):
print(f"警告:目录 '{dir_path}' 不存在。")
return False
if not is_directory(dir_path):
print(f"错误:'{dir_path}' 不是一个目录。")
return False
# 安全检查:非递归删除时,目录必须为空
if not recursive:
try:
if os.listdir(dir_path): # 如果目录非空
print(f"错误:目录 '{dir_path}' 非空。请使用 `recursive=True` 来删除非空目录。")
return False
except OSError as e:
print(f"错误:无法读取目录内容: {e}")
return False
warning_msg = f"确定要删除目录 '{dir_path}'"
if recursive:
warning_msg += " 及其所有内容"
warning_msg += " 吗?此操作不可撤销!(y/N): "
if confirm:
user_input = input(warning_msg).strip().lower()
if user_input != 'y' and user_input != 'yes':
print("取消删除。")
return False
try:
if recursive:
shutil.rmtree(dir_path)
else:
os.rmdir(dir_path)
print(f"成功:目录 '{dir_path}' 已被删除。")
return True
except PermissionError:
print(f"错误:没有权限删除目录 '{dir_path}' 或其内容。")
except OSError as e:
print(f"错误:删除目录时发生错误: {e}")
return False
和内容列表
def list_directory_contents(dir_path: str, show_hidden: bool = False) -> list:
"""
获取指定目录下的所有文件和子目录的名称列表。
Args:
dir_path (str): 要列出内容的目录路径。
show_hidden (bool, optional): 是否显示以 '.' 开头的隐藏文件。默认为 False。
Returns:
list: 目录中条目名称的列表。如果目录不存在或不可读,返回空列表。
Raises:
PermissionError: 如果没有目录的读取权限。
"""
import os
entries = []
try:
# 获取目录下所有条目
all_entries = os.listdir(dir_path)
for entry in all_entries:
# 根据参数决定是否过滤隐藏文件
if not show_hidden and entry.startswith('.'):
continue
entries.append(entry)
except FileNotFoundError:
# 目录不存在,返回空列表
print(f"警告:目录 '{dir_path}' 不存在。")
except PermissionError:
print(f"错误:没有权限读取目录 '{dir_path}'。")
raise # 可以选择重新抛出异常或处理
return entries
`。
info_formatters.py:存放那些格式化显示信息的函数,如 `
def format_file_size(size_in_bytes: int) -> str:
"""
将文件大小(字节)格式化为更易读的单位(如 KB, MB, GB)。
Args:
size_in_bytes (int): 文件大小,单位为字节。
Returns:
str: 格式化后的字符串,包含单位和最多两位小数。
"""
if size_in_bytes < 0:
return "无效大小"
units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
unit_index = 0
size = float(size_in_bytes)
# 循环除以1024,直到大小小于1024或达到最大单位
while size >= 1024 and unit_index < len(units) - 1:
size /= 1024.0
unit_index += 1
# 格式化输出:如果单位是'B',则显示整数,否则显示两位小数
if unit_index == 0:
return f"{int(size)} {units[unit_index]}"
else:
return f"{size:.2f} {units[unit_index]}"
和
def format_timestamp(timestamp: float, format_str: str = "%Y-%m-%d %H:%M:%S") -> str:
"""
将Unix时间戳转换为本地时间的可读字符串。
Args:
timestamp (float): Unix时间戳(秒数)。
format_str (str, optional): 时间格式字符串。默认为 "%Y-%m-%d %H:%M:%S"。
Returns:
str: 格式化后的日期时间字符串。如果时间戳无效,返回空字符串。
"""
import time
from datetime import datetime
try:
# 使用 datetime 模块进行更稳健的转换
dt_object = datetime.fromtimestamp(timestamp)
return dt_object.strftime(format_str)
except (ValueError, OSError):
# 处理无效的时间戳(如负数或过大值)
return ""
`。
怎么使用另一个模块里的函数呢?用 import 语句。在你的主程序文件里,你可以这样写:
import path_utils
import file_ops
current_path = path_utils.get_current_directory() # 注意:需要通过模块名调用
if path_utils.is_file("old.txt"):
file_ops.copy_file("old.txt", "backup.txt")
或者,直接导入你想用的具体函数:
from path_utils import get_current_directory, is_file
from file_ops import copy_file
current_path = get_current_directory() # 现在可以直接使用了
这样做的好处太多了。代码结构一目了然,像有了文件夹一样整洁。团队协作时,每个人可以负责不同的模块。当你想修复一个路径相关的bug时,你很清楚应该去 path_utils.py 里找。而且,这些模块可以在其他项目里重复使用,今天写的文件操作工具,明天也许就能用在另一个脚本里。
所以,回顾我们构建文件管理器的过程,其实就是模块化思维的实践:先拆解需求(需要列出文件、复制、删除、获取信息……),为每个任务编写独立的函数,再把关联紧密的函数组织到同一个模块中。最后,一个 main.py 或者交互式的命令行界面,通过组合调用这些模块中的函数,就把所有功能串联成了一个完整的应用。下次你开始任何新项目时,都可以试试这样思考:我的主要任务可以分解成哪些独立的、可重复使用的小功能?
1342

被折叠的 条评论
为什么被折叠?



