[Python教程系列-06] 文件读写操作:处理持久化数据

49 阅读18分钟

引言

在编程中,数据的持久化存储是一个非常重要的概念。无论是在程序运行过程中保存用户设置、记录日志信息,还是处理大量数据,我们都需要将数据保存到文件中,以便在程序重启后仍然可以访问这些数据。

Python提供了强大而灵活的文件操作功能,支持文本文件和二进制文件的读写操作。通过内置的open()函数和文件对象的各种方法,我们可以轻松地处理各种类型的文件。

在本章中,我们将深入学习Python的文件操作机制,包括文件的打开和关闭、不同的文件模式、文本文件和二进制文件的处理、文件对象的方法、目录操作等内容。通过实际的例子,你将学会如何高效地处理各种文件操作任务。

学习目标

完成本章学习后,你将能够:

  1. 理解文件操作的基本概念和重要性
  2. 掌握open()函数的使用方法和各种文件模式
  3. 熟练进行文本文件的读写操作
  4. 理解二进制文件的处理方法
  5. 掌握文件对象的各种方法和属性
  6. 学会使用with语句进行安全的文件操作
  7. 理解文件编码和处理中文文本
  8. 掌握目录操作和文件系统管理
  9. 编写高效、安全的文件处理程序

核心知识点讲解

文件操作基础

在Python中,文件操作主要包括打开文件、读取文件内容、写入文件内容和关闭文件等步骤。

open()函数

open()函数是Python中用于打开文件的主要函数:

file_object = open(filename, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

主要参数说明:

  • filename:要打开的文件名
  • mode:文件打开模式
  • encoding:文件编码格式
  • errors:错误处理方案

文件模式

常见的文件打开模式:

模式描述
'r'只读模式(默认)
'w'写入模式,会覆盖已有文件
'a'追加模式
'x'独占创建模式
'b'二进制模式
't'文本模式(默认)
'+'读写模式

组合使用示例:

  • 'rb':二进制只读模式
  • 'w+':读写模式,会覆盖文件
  • 'a+':追加和读取模式

文本文件操作

读取文本文件

# 方法1:使用read()读取整个文件
try:
    with open('example.txt', 'r', encoding='utf-8') as file:
        content = file.read()
        print(content)
except FileNotFoundError:
    print("文件未找到")
except Exception as e:
    print(f"读取文件时出错: {e}")

# 方法2:使用readlines()按行读取
try:
    with open('example.txt', 'r', encoding='utf-8') as file:
        lines = file.readlines()
        for line in lines:
            print(line.strip())  # strip()去除行尾换行符
except FileNotFoundError:
    print("文件未找到")

# 方法3:逐行读取(内存友好)
try:
    with open('example.txt', 'r', encoding='utf-8') as file:
        for line in file:
            print(line.strip())
except FileNotFoundError:
    print("文件未找到")

写入文本文件

# 方法1:使用write()写入
try:
    with open('output.txt', 'w', encoding='utf-8') as file:
        file.write("Hello, World!\n")
        file.write("这是第二行内容。\n")
except Exception as e:
    print(f"写入文件时出错: {e}")

# 方法2:使用writelines()写入多行
lines = ["第一行\n", "第二行\n", "第三行\n"]
try:
    with open('output.txt', 'w', encoding='utf-8') as file:
        file.writelines(lines)
except Exception as e:
    print(f"写入文件时出错: {e}")

# 方法3:追加模式写入
try:
    with open('output.txt', 'a', encoding='utf-8') as file:
        file.write("这是追加的内容。\n")
except Exception as e:
    print(f"追加文件时出错: {e}")

二进制文件操作

二进制文件操作主要用于处理图片、音频、视频等非文本文件。

读取二进制文件

# 读取图片文件
try:
    with open('image.jpg', 'rb') as file:
        binary_data = file.read()
        print(f"文件大小: {len(binary_data)} 字节")
except FileNotFoundError:
    print("文件未找到")
except Exception as e:
    print(f"读取二进制文件时出错: {e}")

写入二进制文件

# 写入二进制数据
binary_data = b'\x89PNG\r\n\x1a\n'  # PNG文件头
try:
    with open('output.bin', 'wb') as file:
        file.write(binary_data)
except Exception as e:
    print(f"写入二进制文件时出错: {e}")

复制二进制文件

def copy_binary_file(source, destination):
    """复制二进制文件"""
    try:
        with open(source, 'rb') as src_file:
            with open(destination, 'wb') as dst_file:
                # 分块读取和写入,适用于大文件
                chunk_size = 8192  # 8KB块大小
                while True:
                    chunk = src_file.read(chunk_size)
                    if not chunk:
                        break
                    dst_file.write(chunk)
        print(f"文件复制成功: {source} -> {destination}")
    except Exception as e:
        print(f"复制文件时出错: {e}")

# 使用示例
# copy_binary_file('source.jpg', 'destination.jpg')

文件对象方法

文件对象提供了许多有用的方法:

try:
    with open('example.txt', 'r+', encoding='utf-8') as file:
        # 读取方法
        content = file.read()        # 读取整个文件
        file.seek(0)                 # 移动到文件开头
        first_line = file.readline() # 读取一行
        file.seek(0)                 # 重新移动到文件开头
        all_lines = file.readlines() # 读取所有行
        
        # 写入方法
        file.write("新内容\n")        # 写入内容
        file.writelines(["行1\n", "行2\n"])  # 写入多行
        
        # 文件位置相关
        position = file.tell()       # 获取当前位置
        file.seek(0)                 # 移动到指定位置
        file.seek(0, 2)              # 移动到文件末尾(0偏移,2表示从末尾开始)
        
        # 其他方法
        file.flush()                 # 刷新缓冲区
        is_closed = file.closed      # 检查文件是否已关闭
        file_mode = file.mode        # 获取文件模式
        file_name = file.name        # 获取文件名
        
except Exception as e:
    print(f"文件操作时出错: {e}")

文件编码处理

处理不同编码的文件是实际开发中的常见需求:

# 处理UTF-8编码文件
try:
    with open('utf8_file.txt', 'r', encoding='utf-8') as file:
        content = file.read()
        print(content)
except UnicodeDecodeError as e:
    print(f"编码错误: {e}")

# 处理GBK编码文件(常见于Windows中文系统)
try:
    with open('gbk_file.txt', 'r', encoding='gbk') as file:
        content = file.read()
        print(content)
except UnicodeDecodeError as e:
    print(f"编码错误: {e}")

# 自动检测编码(需要安装chardet库)
# pip install chardet
try:
    import chardet
    
    # 检测文件编码
    with open('unknown_encoding.txt', 'rb') as file:
        raw_data = file.read()
        encoding_info = chardet.detect(raw_data)
        encoding = encoding_info['encoding']
        print(f"检测到的编码: {encoding}")
        
    # 使用检测到的编码读取文件
    with open('unknown_encoding.txt', 'r', encoding=encoding) as file:
        content = file.read()
        print(content)
        
except ImportError:
    print("请安装chardet库: pip install chardet")
except Exception as e:
    print(f"处理文件时出错: {e}")

目录和文件系统操作

Python的osos.path模块提供了丰富的目录和文件系统操作功能:

import os
import shutil
from pathlib import Path

# 获取当前工作目录
current_dir = os.getcwd()
print(f"当前工作目录: {current_dir}")

# 列出目录内容
try:
    files = os.listdir('.')
    print("当前目录下的文件和目录:")
    for item in files:
        print(f"  {item}")
except Exception as e:
    print(f"列出目录内容时出错: {e}")

# 创建目录
try:
    os.mkdir('new_directory')
    print("目录创建成功")
except FileExistsError:
    print("目录已存在")
except Exception as e:
    print(f"创建目录时出错: {e}")

# 递归创建目录
try:
    os.makedirs('parent/child/grandchild', exist_ok=True)
    print("递归目录创建成功")
except Exception as e:
    print(f"递归创建目录时出错: {e}")

# 检查文件或目录是否存在
if os.path.exists('example.txt'):
    print("文件存在")
else:
    print("文件不存在")

if os.path.isdir('new_directory'):
    print("这是一个目录")
elif os.path.isfile('example.txt'):
    print("这是一个文件")

# 获取文件信息
if os.path.exists('example.txt'):
    stat_info = os.stat('example.txt')
    print(f"文件大小: {stat_info.st_size} 字节")
    print(f"修改时间: {stat_info.st_mtime}")

# 删除文件和目录
try:
    os.remove('unwanted_file.txt')
    print("文件删除成功")
except FileNotFoundError:
    print("文件不存在")
except Exception as e:
    print(f"删除文件时出错: {e}")

try:
    os.rmdir('empty_directory')
    print("空目录删除成功")
except OSError as e:
    print(f"删除目录时出错: {e}")

# 使用shutil进行高级操作
try:
    shutil.copy('source.txt', 'destination.txt')  # 复制文件
    shutil.move('old_name.txt', 'new_name.txt')   # 移动/重命名文件
    shutil.rmtree('directory_to_delete')          # 删除非空目录
    print("高级文件操作完成")
except Exception as e:
    print(f"高级文件操作时出错: {e}")

# 使用pathlib(Python 3.4+推荐方式)
path = Path('example.txt')

# 检查路径是否存在
if path.exists():
    print("路径存在")
    
# 检查是否为文件
if path.is_file():
    print("这是一个文件")
    
# 检查是否为目录
if path.is_dir():
    print("这是一个目录")
    
# 获取文件扩展名
print(f"文件扩展名: {path.suffix}")

# 获取文件名(不含扩展名)
print(f"文件名: {path.stem}")

# 获取父目录
print(f"父目录: {path.parent}")

临时文件和目录

有时我们需要创建临时文件进行操作:

import tempfile
import os

# 创建临时文件
with tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.txt') as temp_file:
    temp_file.write("这是临时文件的内容")
    temp_filename = temp_file.name
    print(f"临时文件名: {temp_filename}")

# 读取临时文件
try:
    with open(temp_filename, 'r', encoding='utf-8') as file:
        content = file.read()
        print(f"临时文件内容: {content}")
finally:
    # 清理临时文件
    os.unlink(temp_filename)
    print("临时文件已删除")

# 创建临时目录
with tempfile.TemporaryDirectory() as temp_dir:
    print(f"临时目录: {temp_dir}")
    
    # 在临时目录中创建文件
    temp_file_path = os.path.join(temp_dir, 'temp_file.txt')
    with open(temp_file_path, 'w', encoding='utf-8') as file:
        file.write("临时目录中的文件")
    
    # 读取文件
    with open(temp_file_path, 'r', encoding='utf-8') as file:
        content = file.read()
        print(f"文件内容: {content}")
        
# 临时目录在此处自动删除
print("临时目录已自动删除")

CSV文件处理

CSV(Comma-Separated Values)是一种常见的数据存储格式:

import csv

# 写入CSV文件
data = [
    ['姓名', '年龄', '城市'],
    ['张三', '25', '北京'],
    ['李四', '30', '上海'],
    ['王五', '28', '广州']
]

try:
    with open('data.csv', 'w', newline='', encoding='utf-8') as csvfile:
        writer = csv.writer(csvfile)
        writer.writerows(data)
    print("CSV文件写入成功")
except Exception as e:
    print(f"写入CSV文件时出错: {e}")

# 读取CSV文件
try:
    with open('data.csv', 'r', encoding='utf-8') as csvfile:
        reader = csv.reader(csvfile)
        for row in reader:
            print(row)
except Exception as e:
    print(f"读取CSV文件时出错: {e}")

# 使用DictReader和DictWriter
# 写入字典格式的CSV
dict_data = [
    {'姓名': '张三', '年龄': '25', '城市': '北京'},
    {'姓名': '李四', '年龄': '30', '城市': '上海'},
    {'姓名': '王五', '年龄': '28', '城市': '广州'}
]

try:
    with open('dict_data.csv', 'w', newline='', encoding='utf-8') as csvfile:
        fieldnames = ['姓名', '年龄', '城市']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        
        writer.writeheader()  # 写入表头
        writer.writerows(dict_data)
    print("字典格式CSV文件写入成功")
except Exception as e:
    print(f"写入字典格式CSV文件时出错: {e}")

# 读取字典格式的CSV
try:
    with open('dict_data.csv', 'r', encoding='utf-8') as csvfile:
        reader = csv.DictReader(csvfile)
        for row in reader:
            print(f"姓名: {row['姓名']}, 年龄: {row['年龄']}, 城市: {row['城市']}")
except Exception as e:
    print(f"读取字典格式CSV文件时出错: {e}")

代码示例与实战

示例1:日志文件处理器

import os
import datetime
from typing import List, Optional

class LogFileProcessor:
    """日志文件处理器"""
    
    def __init__(self, log_directory: str = "logs"):
        self.log_directory = log_directory
        self._ensure_log_directory()
    
    def _ensure_log_directory(self):
        """确保日志目录存在"""
        if not os.path.exists(self.log_directory):
            os.makedirs(self.log_directory)
    
    def write_log(self, message: str, level: str = "INFO") -> bool:
        """写入日志"""
        try:
            # 生成日志文件名(按天分割)
            today = datetime.datetime.now().strftime("%Y-%m-%d")
            log_filename = os.path.join(self.log_directory, f"log_{today}.txt")
            
            # 获取当前时间
            timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            
            # 格式化日志行
            log_entry = f"[{timestamp}] [{level}] {message}\n"
            
            # 追加写入日志文件
            with open(log_filename, 'a', encoding='utf-8') as log_file:
                log_file.write(log_entry)
            
            return True
        except Exception as e:
            print(f"写入日志时出错: {e}")
            return False
    
    def read_logs(self, date: Optional[str] = None) -> List[str]:
        """读取日志"""
        try:
            if date is None:
                date = datetime.datetime.now().strftime("%Y-%m-%d")
            
            log_filename = os.path.join(self.log_directory, f"log_{date}.txt")
            
            if not os.path.exists(log_filename):
                return []
            
            with open(log_filename, 'r', encoding='utf-8') as log_file:
                return log_file.readlines()
        except Exception as e:
            print(f"读取日志时出错: {e}")
            return []
    
    def search_logs(self, keyword: str, date: Optional[str] = None) -> List[str]:
        """搜索日志"""
        logs = self.read_logs(date)
        matching_logs = []
        
        for log_line in logs:
            if keyword.lower() in log_line.lower():
                matching_logs.append(log_line.strip())
        
        return matching_logs
    
    def get_log_files(self) -> List[str]:
        """获取所有日志文件"""
        try:
            files = os.listdir(self.log_directory)
            log_files = [f for f in files if f.startswith("log_") and f.endswith(".txt")]
            return sorted(log_files)
        except Exception as e:
            print(f"获取日志文件列表时出错: {e}")
            return []
    
    def clear_old_logs(self, days_to_keep: int = 7) -> int:
        """清理旧日志文件"""
        try:
            import datetime
            
            now = datetime.datetime.now()
            deleted_count = 0
            
            log_files = self.get_log_files()
            
            for log_file in log_files:
                # 解析文件名中的日期
                try:
                    date_str = log_file[4:-4]  # 去掉"log_"前缀和".txt"后缀
                    file_date = datetime.datetime.strptime(date_str, "%Y-%m-%d")
                    
                    # 计算文件年龄
                    age = (now - file_date).days
                    
                    # 如果文件太旧,删除它
                    if age > days_to_keep:
                        file_path = os.path.join(self.log_directory, log_file)
                        os.remove(file_path)
                        deleted_count += 1
                        print(f"已删除旧日志文件: {log_file}")
                except ValueError:
                    # 如果无法解析日期,跳过该文件
                    continue
            
            return deleted_count
        except Exception as e:
            print(f"清理旧日志时出错: {e}")
            return 0

def main():
    # 创建日志处理器
    logger = LogFileProcessor()
    
    # 写入一些测试日志
    logger.write_log("应用程序启动", "INFO")
    logger.write_log("用户登录成功", "INFO")
    logger.write_log("数据库连接失败", "ERROR")
    logger.write_log("处理用户请求", "DEBUG")
    logger.write_log("用户登出", "INFO")
    logger.write_log("内存不足警告", "WARNING")
    
    # 读取今天的日志
    print("=== 今日日志 ===")
    today_logs = logger.read_logs()
    for log in today_logs:
        print(log.strip())
    
    # 搜索包含特定关键词的日志
    print("\n=== 错误日志 ===")
    error_logs = logger.search_logs("error")
    for log in error_logs:
        print(log)
    
    # 显示所有日志文件
    print("\n=== 日志文件列表 ===")
    log_files = logger.get_log_files()
    for log_file in log_files:
        print(log_file)
    
    # 清理3天前的日志(这里只是演示,实际使用时要小心)
    # deleted = logger.clear_old_logs(3)
    # print(f"\n已清理 {deleted} 个旧日志文件")

if __name__ == "__main__":
    main()

示例2:配置文件管理器

import json
import os
from typing import Any, Dict, Optional

class ConfigManager:
    """配置文件管理器"""
    
    def __init__(self, config_file: str = "config.json"):
        self.config_file = config_file
        self.config = {}
        self.load_config()
    
    def load_config(self) -> bool:
        """加载配置文件"""
        try:
            if os.path.exists(self.config_file):
                with open(self.config_file, 'r', encoding='utf-8') as file:
                    self.config = json.load(file)
                print(f"配置文件加载成功: {self.config_file}")
                return True
            else:
                print(f"配置文件不存在,使用默认配置: {self.config_file}")
                self.config = self.get_default_config()
                return False
        except json.JSONDecodeError as e:
            print(f"配置文件格式错误: {e}")
            self.config = self.get_default_config()
            return False
        except Exception as e:
            print(f"加载配置文件时出错: {e}")
            self.config = self.get_default_config()
            return False
    
    def save_config(self) -> bool:
        """保存配置文件"""
        try:
            # 确保目录存在
            directory = os.path.dirname(self.config_file)
            if directory and not os.path.exists(directory):
                os.makedirs(directory)
            
            with open(self.config_file, 'w', encoding='utf-8') as file:
                json.dump(self.config, file, ensure_ascii=False, indent=2)
            print(f"配置文件保存成功: {self.config_file}")
            return True
        except Exception as e:
            print(f"保存配置文件时出错: {e}")
            return False
    
    def get_default_config(self) -> Dict[str, Any]:
        """获取默认配置"""
        return {
            "app_name": "MyApplication",
            "version": "1.0.0",
            "debug": False,
            "database": {
                "host": "localhost",
                "port": 5432,
                "name": "myapp_db"
            },
            "logging": {
                "level": "INFO",
                "file": "app.log"
            },
            "features": {
                "enable_cache": True,
                "max_connections": 100
            }
        }
    
    def get(self, key: str, default: Any = None) -> Any:
        """获取配置项"""
        keys = key.split('.')
        value = self.config
        
        try:
            for k in keys:
                value = value[k]
            return value
        except (KeyError, TypeError):
            return default
    
    def set(self, key: str, value: Any) -> None:
        """设置配置项"""
        keys = key.split('.')
        config = self.config
        
        # 导航到倒数第二层
        for k in keys[:-1]:
            if k not in config:
                config[k] = {}
            config = config[k]
        
        # 设置最后一层的值
        config[keys[-1]] = value
    
    def update(self, updates: Dict[str, Any]) -> None:
        """批量更新配置"""
        for key, value in updates.items():
            self.set(key, value)
    
    def reset_to_default(self) -> None:
        """重置为默认配置"""
        self.config = self.get_default_config()
    
    def validate_config(self) -> bool:
        """验证配置"""
        required_keys = ["app_name", "version"]
        
        for key in required_keys:
            if key not in self.config:
                print(f"缺少必需的配置项: {key}")
                return False
        
        return True
    
    def display_config(self) -> None:
        """显示当前配置"""
        print("=== 当前配置 ===")
        print(json.dumps(self.config, ensure_ascii=False, indent=2))

def main():
    # 创建配置管理器
    config_manager = ConfigManager("app_config.json")
    
    # 显示当前配置
    config_manager.display_config()
    
    # 修改一些配置项
    config_manager.set("app_name", "超级应用")
    config_manager.set("database.host", "192.168.1.100")
    config_manager.set("logging.level", "DEBUG")
    
    # 批量更新配置
    updates = {
        "debug": True,
        "features.max_connections": 200
    }
    config_manager.update(updates)
    
    # 获取配置项
    app_name = config_manager.get("app_name")
    db_host = config_manager.get("database.host")
    debug_mode = config_manager.get("debug", False)
    
    print(f"\n应用名称: {app_name}")
    print(f"数据库主机: {db_host}")
    print(f"调试模式: {debug_mode}")
    
    # 验证配置
    if config_manager.validate_config():
        print("\n配置验证通过")
    else:
        print("\n配置验证失败")
    
    # 保存配置
    if config_manager.save_config():
        print("配置已保存")
    else:
        print("配置保存失败")
    
    # 重新加载配置以验证保存是否成功
    print("\n=== 重新加载配置 ===")
    config_manager.load_config()
    config_manager.display_config()

if __name__ == "__main__":
    main()

示例3:文件批量处理工具

import os
import shutil
import hashlib
from typing import List, Tuple, Optional
from pathlib import Path

class BatchFileProcessor:
    """批量文件处理器"""
    
    def __init__(self):
        self.processed_files = []
        self.errors = []
    
    def find_files(self, directory: str, extension: Optional[str] = None, 
                   recursive: bool = True) -> List[str]:
        """查找文件"""
        try:
            path = Path(directory)
            pattern = f"*.{extension}" if extension else "*"
            
            if recursive:
                files = list(path.rglob(pattern))
            else:
                files = list(path.glob(pattern))
            
            # 过滤掉目录,只保留文件
            files = [str(f) for f in files if f.is_file()]
            
            return sorted(files)
        except Exception as e:
            print(f"查找文件时出错: {e}")
            return []
    
    def calculate_file_hash(self, filepath: str, algorithm: str = "md5") -> Optional[str]:
        """计算文件哈希值"""
        try:
            hash_obj = hashlib.new(algorithm)
            
            with open(filepath, 'rb') as file:
                # 分块读取文件以节省内存
                for chunk in iter(lambda: file.read(4096), b""):
                    hash_obj.update(chunk)
            
            return hash_obj.hexdigest()
        except Exception as e:
            print(f"计算文件哈希时出错 {filepath}: {e}")
            return None
    
    def copy_files(self, source_files: List[str], destination_dir: str, 
                   preserve_structure: bool = False) -> Tuple[int, int]:
        """复制文件"""
        success_count = 0
        error_count = 0
        
        # 确保目标目录存在
        Path(destination_dir).mkdir(parents=True, exist_ok=True)
        
        for source_file in source_files:
            try:
                source_path = Path(source_file)
                
                if preserve_structure:
                    # 保持目录结构
                    relative_path = source_path.relative_to(source_path.parent.parent)
                    dest_path = Path(destination_dir) / relative_path
                else:
                    # 扁平化复制
                    dest_path = Path(destination_dir) / source_path.name
                
                # 确保目标文件的目录存在
                dest_path.parent.mkdir(parents=True, exist_ok=True)
                
                shutil.copy2(source_file, dest_path)
                print(f"已复制: {source_file} -> {dest_path}")
                success_count += 1
                
            except Exception as e:
                print(f"复制文件失败 {source_file}: {e}")
                error_count += 1
                self.errors.append(f"复制失败 {source_file}: {e}")
        
        return success_count, error_count
    
    def rename_files(self, file_mapping: Dict[str, str]) -> Tuple[int, int]:
        """重命名文件"""
        success_count = 0
        error_count = 0
        
        for old_name, new_name in file_mapping.items():
            try:
                old_path = Path(old_name)
                new_path = old_path.parent / new_name
                
                old_path.rename(new_path)
                print(f"已重命名: {old_name} -> {new_name}")
                success_count += 1
                
            except Exception as e:
                print(f"重命名文件失败 {old_name}: {e}")
                error_count += 1
                self.errors.append(f"重命名失败 {old_name}: {e}")
        
        return success_count, error_count
    
    def remove_duplicates(self, files: List[str], dry_run: bool = True) -> Tuple[int, int]:
        """移除重复文件"""
        file_hashes = {}
        duplicates = []
        
        print("正在计算文件哈希值...")
        for filepath in files:
            file_hash = self.calculate_file_hash(filepath)
            if file_hash:
                if file_hash in file_hashes:
                    duplicates.append((filepath, file_hashes[file_hash]))
                else:
                    file_hashes[file_hash] = filepath
        
        duplicate_count = len(duplicates)
        removed_count = 0
        
        print(f"发现 {duplicate_count} 个重复文件")
        
        if dry_run:
            print("=== 重复文件列表(预览模式)===")
            for dup, orig in duplicates:
                print(f"  重复: {dup}")
                print(f"  原始: {orig}")
                print()
        else:
            print("正在移除重复文件...")
            for duplicate_file, _ in duplicates:
                try:
                    os.remove(duplicate_file)
                    print(f"已删除重复文件: {duplicate_file}")
                    removed_count += 1
                except Exception as e:
                    print(f"删除文件失败 {duplicate_file}: {e}")
                    self.errors.append(f"删除失败 {duplicate_file}: {e}")
        
        return duplicate_count, removed_count
    
    def get_file_info(self, filepath: str) -> Optional[Dict[str, Any]]:
        """获取文件信息"""
        try:
            path = Path(filepath)
            stat = path.stat()
            
            return {
                "name": path.name,
                "size": stat.st_size,
                "created": stat.st_ctime,
                "modified": stat.st_mtime,
                "is_file": path.is_file(),
                "is_dir": path.is_dir(),
                "extension": path.suffix,
                "hash": self.calculate_file_hash(filepath)
            }
        except Exception as e:
            print(f"获取文件信息失败 {filepath}: {e}")
            return None
    
    def batch_process(self, directory: str, operations: List[str], 
                     **kwargs) -> Dict[str, Any]:
        """批处理操作"""
        results = {
            "processed_files": 0,
            "success_count": 0,
            "error_count": 0,
            "details": {}
        }
        
        # 查找文件
        extension = kwargs.get("extension")
        files = self.find_files(directory, extension)
        results["processed_files"] = len(files)
        
        print(f"找到 {len(files)} 个文件")
        
        # 执行操作
        for operation in operations:
            if operation == "copy":
                dest_dir = kwargs.get("destination")
                if dest_dir:
                    success, error = self.copy_files(files, dest_dir)
                    results["success_count"] += success
                    results["error_count"] += error
                    results["details"]["copy"] = {"success": success, "error": error}
            
            elif operation == "remove_duplicates":
                dry_run = kwargs.get("dry_run", True)
                dup_count, removed_count = self.remove_duplicates(files, dry_run)
                results["details"]["remove_duplicates"] = {
                    "duplicates_found": dup_count,
                    "removed": removed_count
                }
        
        return results

def main():
    processor = BatchFileProcessor()
    
    # 创建测试目录和文件
    test_dir = "test_files"
    Path(test_dir).mkdir(exist_ok=True)
    
    # 创建一些测试文件
    test_files = [
        "document1.txt",
        "document2.txt",
        "image1.jpg",
        "image2.jpg",
        "data1.csv",
        "data2.csv"
    ]
    
    for filename in test_files:
        filepath = Path(test_dir) / filename
        with open(filepath, 'w', encoding='utf-8') as f:
            f.write(f"这是测试文件 {filename} 的内容")
    
    # 查找文件
    print("=== 查找文件 ===")
    txt_files = processor.find_files(test_dir, "txt")
    print(f"找到 {len(txt_files)} 个txt文件:")
    for file in txt_files:
        print(f"  {file}")
    
    # 计算文件哈希
    print("\n=== 文件哈希 ===")
    for file in txt_files[:2]:  # 只计算前两个文件的哈希
        file_hash = processor.calculate_file_hash(file)
        print(f"{file}: {file_hash}")
    
    # 获取文件信息
    print("\n=== 文件信息 ===")
    if txt_files:
        file_info = processor.get_file_info(txt_files[0])
        if file_info:
            print(f"文件名: {file_info['name']}")
            print(f"大小: {file_info['size']} 字节")
            print(f"扩展名: {file_info['extension']}")
            print(f"哈希: {file_info['hash']}")
    
    # 批量复制文件
    print("\n=== 批量复制文件 ===")
    copy_results = processor.batch_process(
        directory=test_dir,
        operations=["copy"],
        extension="txt",
        destination="copied_files"
    )
    print(f"复制结果: {copy_results}")
    
    # 清理测试文件
    try:
        shutil.rmtree(test_dir)
        shutil.rmtree("copied_files")
        print("\n测试文件已清理")
    except Exception as e:
        print(f"清理测试文件时出错: {e}")

if __name__ == "__main__":
    main()

小结与回顾

在本章中,我们深入学习了Python的文件操作机制:

  1. 文件操作基础

    • 掌握了open()函数的使用方法和各种文件模式
    • 理解了文本文件和二进制文件的区别
  2. 文本文件处理

    • 学会了读取和写入文本文件的各种方法
    • 掌握了逐行处理大文件的技术
  3. 二进制文件处理

    • 理解了二进制文件的操作方法
    • 学会了处理图片、音频等非文本文件
  4. 文件系统操作

    • 掌握了目录操作和文件管理
    • 学会了使用osshutilpathlib模块
  5. 高级文件处理

    • 了解了临时文件的使用
    • 掌握了CSV文件的处理方法

通过实际的代码示例,我们不仅掌握了理论知识,还学会了如何在实际项目中应用文件操作技术。文件操作是程序持久化存储数据的基础,掌握这些技能对于开发实用的应用程序非常重要。

在下一章中,我们将学习模块与包管理,帮助我们更好地组织和复用代码。

练习与挑战

基础练习

  1. 编写一个程序,读取文本文件并统计其中的单词数量和行数。
  2. 创建一个程序,合并多个文本文件到一个输出文件中。
  3. 实现一个简单的文件加密/解密工具,使用基本的字符替换算法。
  4. 编写一个程序,递归遍历目录并列出所有文件及其大小。

进阶挑战

  1. 设计一个完整的备份工具,支持增量备份、压缩和定时备份功能。
  2. 创建一个文件同步工具,能够在两个目录之间同步文件变化。
  3. 实现一个日志分析工具,能够解析和分析Web服务器日志文件。
  4. 编写一个文档管理系统,支持文档的分类、搜索和版本控制。

思考题

  1. 在处理大文件时,为什么要避免一次性读取整个文件到内存中?
  2. 什么时候应该使用二进制模式而不是文本模式打开文件?
  3. 如何安全地处理文件编码问题,特别是在跨平台开发中?
  4. with语句在文件操作中有什么优势?为什么不直接使用open()和close()?

扩展阅读

  1. Python官方文档 - 文件和目录访问 - 官方文档中关于文件系统的详细介绍
  2. Python官方文档 - open()函数 - open()函数的详细说明
  3. Python官方文档 - pathlib模块 - 现代化的路径操作模块
  4. Python官方文档 - shutil模块 - 高级文件操作模块
  5. 《流畅的Python》- 深入理解Python文件操作的经典书籍
  6. Real Python - Reading and Writing Files - 关于Python文件操作的详细教程
  7. Real Python - Working with Files - 更深入的文件操作教程

通过本章的学习,你应该已经掌握了Python文件操作的核心概念和使用方法。这些知识将帮助你处理各种文件操作任务,为开发实用的应用程序奠定基础。在下一章中,我们将学习模块与包管理,帮助我们更好地组织和复用代码。