第6章 文件操作

文件操作是Python編程中最基礎也是最重要的技能之一。無論是讀取配置文件、處理數據文件,還是保存程序運行結果,我們都需要與文件系統進行交互。本章將詳細介紹Python中文件操作的各種方法和最佳實踐。

本系列文章所使用到的示例源碼:Python從入門到精通示例代碼

6.1 文件的打開與關閉

文件操作的基本概念

文件系統簡介

文件系統是操作系統用來管理文件和目錄的方式。在Python中,我們可以通過內置函數和模塊來操作文件系統中的文件。文件操作的基本流程包括:打開文件 → 讀取/寫入數據 → 關閉文件。

文件路徑

文件路徑分爲兩種類型:
- 絕對路徑:從根目錄開始的完整路徑,如 C:\Users\username\documents\file.txt(Windows)或 /home/username/documents/file.txt(Linux/Mac)
- 相對路徑:相對於當前工作目錄的路徑,如 ./data/file.txt../config/settings.txt

文件類型

  • 文本文件:包含可讀字符的文件,如 .txt.py.csv
  • 二進制文件:包含二進制數據的文件,如圖片、音頻、視頻、可執行文件等

open()函數詳解

open() 函數是Python中打開文件的標準方法,其基本語法如下:

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

基本用法示例

# 創建一個測試文件
with open('test_files/sample.txt', 'w', encoding='utf-8') as f:
    f.write('Hello, World!\nThis is line 2.\nThis is line 3.\n這是中文內容。\n文件操作示例。')

# 打開文件進行讀取
file = open('test_files/sample.txt', 'r', encoding='utf-8')
content = file.read()
print(content)
file.close()

輸出結果:

Hello, World!
This is line 2.
This is line 3.
這是中文內容。
文件操作示例。

打開模式參數詳解

模式 描述 文件指針位置 文件不存在時
‘r’ 只讀模式(默認) 文件開頭 拋出異常
‘w’ 寫入模式 文件開頭 創建新文件
‘a’ 追加模式 文件末尾 創建新文件
‘x’ 獨佔創建模式 文件開頭 文件存在時拋出異常
‘b’ 二進制模式 - 與其他模式組合使用
‘t’ 文本模式(默認) - 與其他模式組合使用
’+’ 讀寫模式 - 與其他模式組合使用
# 不同模式的示例

# 寫入模式 - 會覆蓋原文件內容
with open('test_files/write_test.txt', 'w', encoding='utf-8') as f:
    f.write('這是寫入模式的內容')

# 追加模式 - 在文件末尾添加內容
with open('test_files/write_test.txt', 'a', encoding='utf-8') as f:
    f.write('\n這是追加的內容')

# 讀取並顯示結果
with open('test_files/write_test.txt', 'r', encoding='utf-8') as f:
    print(f.read())

編碼參數

在處理文本文件時,指定正確的編碼格式非常重要:

# 使用UTF-8編碼(推薦)
with open('test_files/chinese.txt', 'w', encoding='utf-8') as f:
    f.write('你好,世界!\nHello, World!')

# 讀取時也要指定相同的編碼
with open('test_files/chinese.txt', 'r', encoding='utf-8') as f:
    content = f.read()
    print(content)

文件對象的屬性

文件對象具有多個有用的屬性:

file = open('test_files/sample.txt', 'r', encoding='utf-8')

print(f"文件名: {file.name}")
print(f"打開模式: {file.mode}")
print(f"編碼格式: {file.encoding}")
print(f"是否已關閉: {file.closed}")

file.close()
print(f"關閉後是否已關閉: {file.closed}")

輸出結果:

文件名: test_files/sample.txt
文件模式: r
編碼: utf-8
是否關閉: False
with語句外,文件是否關閉: True

close()方法的重要性

手動關閉文件是一個好習慣,它可以:
- 釋放系統資源
- 確保數據被寫入磁盤
- 避免文件鎖定問題

# 不推薦的方式 - 容易忘記關閉文件
file = open('test_files/sample.txt', 'r')
content = file.read()
# 如果這裏發生異常,文件可能不會被關閉
file.close()

# 推薦的方式 - 使用try-finally確保文件被關閉
try:
    file = open('test_files/sample.txt', 'r')
    content = file.read()
finally:
    file.close()

6.2 文件的讀取方法

文本文件讀取

Python提供了多種讀取文件的方法,每種方法都有其適用場景。

read()方法

read() 方法可以讀取整個文件或指定數量的字符:

# 讀取整個文件
with open('test_files/sample.txt', 'r', encoding='utf-8') as f:
    content = f.read()
    print("文件內容:")
    print(repr(content))

輸出結果:

文件內容: 'Hello, World!\nThis is line 2.\nThis is line 3.\n這是中文內容。\n文件操作示例。'

readline()方法

readline() 方法每次讀取一行:

with open('test_files/sample.txt', 'r', encoding='utf-8') as f:
    print("逐行讀取:")
    for i, line in enumerate(f, 1):
        print(f"第{i}行: {repr(line)}")

輸出結果:

逐行讀取:
1: 'Hello, World!\n'
2: 'This is line 2.\n'
3: 'This is line 3.\n'
4: '這是中文內容。\n'
5: '文件操作示例。'

readlines()方法

readlines() 方法讀取所有行並返回列表:

with open('test_files/sample.txt', 'r', encoding='utf-8') as f:
    lines = f.readlines()
    print("所有行:")
    print(lines)

輸出結果:

所有行: ['Hello, World!\n', 'This is line 2.\n', 'This is line 3.\n', '這是中文內容。\n', '文件操作示例。']

逐行讀取的最佳實踐

對於大文件,直接遍歷文件對象是最內存高效的方法:

# 創建一個較大的測試文件
with open('test_files/large_file.txt', 'w', encoding='utf-8') as f:
    for i in range(1000):
        f.write(f"這是第{i+1}行內容\n")

# 內存高效的逐行讀取
print("內存高效的逐行讀取(只顯示前5行和後5行):")
with open('test_files/large_file.txt', 'r', encoding='utf-8') as f:
    for line_num, line in enumerate(f, 1):
        if line_num <= 5 or line_num > 995:
            print(f"第{line_num}行: {line.strip()}")
        elif line_num == 6:
            print("... (省略中間行) ...")

二進制文件讀取

對於二進制文件,需要使用 ‘rb’ 模式:

# 創建一個簡單的二進制文件
binary_data = bytes([0x48, 0x65, 0x6C, 0x6C, 0x6F])  # "Hello" 的ASCII碼
with open('test_files/binary_file.bin', 'wb') as f:
    f.write(binary_data)

# 讀取二進制文件
with open('test_files/binary_file.bin', 'rb') as f:
    data = f.read()
    print(f"二進制數據: {data}")
    print(f"轉換爲字符串: {data.decode('ascii')}")
    print(f"十六進制表示: {data.hex()}")

大文件處理策略

對於非常大的文件,分塊讀取是更好的選擇:

def read_large_file_in_chunks(filename, chunk_size=1024):
    """
    分塊讀取大文件

    Args:
        filename: 文件名
        chunk_size: 每次讀取的字節數
    """
    with open(filename, 'r', encoding='utf-8') as f:
        chunk_num = 1
        while True:
            chunk = f.read(chunk_size)
            if not chunk:
                break
            print(f"塊 {chunk_num}: {len(chunk)} 個字符")
            # 這裏可以處理chunk數據
            chunk_num += 1
            if chunk_num > 3:  # 只顯示前3塊作爲示例
                print("...")
                break

# 使用分塊讀取
print("\n分塊讀取大文件:")
read_large_file_in_chunks('test_files/large_file.txt', chunk_size=100)

6.3 文件的寫入操作

文本文件寫入

write()方法

write() 方法用於寫入字符串,返回寫入的字符數:

# 基本寫入操作
with open('test_files/write_demo.txt', 'w', encoding='utf-8') as f:
    chars_written = f.write('Hello, World!')
    print(f"寫入了 {chars_written} 個字符")

    # 寫入多行
    f.write('\n第二行內容')
    f.write('\n第三行內容')

# 驗證寫入結果
with open('test_files/write_demo.txt', 'r', encoding='utf-8') as f:
    print("寫入的內容:")
    print(f.read())

writelines()方法

writelines() 方法用於寫入字符串列表:

lines = ['第一行\n', '第二行\n', '第三行\n']

with open('test_files/writelines_demo.txt', 'w', encoding='utf-8') as f:
    f.writelines(lines)

# 注意:writelines不會自動添加換行符
lines_without_newline = ['行1', '行2', '行3']
with open('test_files/no_newline.txt', 'w', encoding='utf-8') as f:
    f.writelines(lines_without_newline)

print("writelines結果(有換行符):")
with open('test_files/writelines_demo.txt', 'r', encoding='utf-8') as f:
    print(repr(f.read()))

print("\nwritelines結果(無換行符):")
with open('test_files/no_newline.txt', 'r', encoding='utf-8') as f:
    print(repr(f.read()))

寫入模式的區別

# 創建初始文件
with open('test_files/mode_test.txt', 'w', encoding='utf-8') as f:
    f.write('原始內容')

print("原始內容:")
with open('test_files/mode_test.txt', 'r', encoding='utf-8') as f:
    print(f.read())

# 'w'模式:覆蓋寫入
with open('test_files/mode_test.txt', 'w', encoding='utf-8') as f:
    f.write('覆蓋後的內容')

print("\n'w'模式後的內容:")
with open('test_files/mode_test.txt', 'r', encoding='utf-8') as f:
    print(f.read())

# 'a'模式:追加寫入
with open('test_files/mode_test.txt', 'a', encoding='utf-8') as f:
    f.write('\n追加的內容')

print("\n'a'模式後的內容:")
with open('test_files/mode_test.txt', 'r', encoding='utf-8') as f:
    print(f.read())

# 'x'模式:獨佔創建(文件不存在時創建,存在時報錯)
try:
    with open('test_files/exclusive_file.txt', 'x', encoding='utf-8') as f:
        f.write('獨佔創建的文件')
    print("\n成功創建獨佔文件")
except FileExistsError:
    print("\n文件已存在,無法使用'x'模式")

二進制文件寫入

# 寫入二進制數據
binary_data = b'\x89PNG\r\n\x1a\n'  # PNG文件頭的一部分

with open('test_files/binary_write.bin', 'wb') as f:
    bytes_written = f.write(binary_data)
    print(f"寫入了 {bytes_written} 個字節")

# 讀取並驗證
with open('test_files/binary_write.bin', 'rb') as f:
    data = f.read()
    print(f"讀取的二進制數據: {data}")
    print(f"十六進制表示: {data.hex()}")

緩衝區與flush()

import time

# 演示緩衝區的作用
print("演示緩衝區機制:")
with open('test_files/buffer_demo.txt', 'w', encoding='utf-8') as f:
    f.write('第一行\n')
    print("寫入第一行,但可能還在緩衝區中")
    time.sleep(1)

    f.write('第二行\n')
    f.flush()  # 強制刷新緩衝區
    print("寫入第二行並刷新緩衝區")
    time.sleep(1)

    f.write('第三行\n')
    print("寫入第三行,文件關閉時會自動刷新")

print("\n最終文件內容:")
with open('test_files/buffer_demo.txt', 'r', encoding='utf-8') as f:
    print(f.read())

6.4 文件指針與定位

文件指針的概念

文件指針是一個標記,指示當前讀取或寫入的位置。每次讀取或寫入操作後,文件指針會自動移動。

tell()方法

tell() 方法返回當前文件指針的位置:

# 演示tell()方法
with open('test_files/numbers.txt', 'r') as f:
    print(f"初始位置: {f.tell()}")

    data = f.read(5)
    print(f"讀取5個字符: {repr(data)}")
    print(f"當前位置: {f.tell()}")

輸出結果:

初始位置: 0
讀取5個字符: '01234'
當前位置: 5

seek()方法

seek() 方法用於移動文件指針:

with open('test_files/numbers.txt', 'r') as f:
    # seek(offset, whence)
    # whence: 0=從文件開頭, 1=從當前位置, 2=從文件末尾

    print(f"初始位置: {f.tell()}")
    data = f.read(5)
    print(f"讀取5個字符: {repr(data)}")
    print(f"當前位置: {f.tell()}")

    # 移動到文件開頭
    f.seek(0)
    print(f"重置後位置: {f.tell()}")
    print(f"重新讀取: {repr(f.read(10))}")

輸出結果:

初始位置: 0
讀取5個字符: '01234'
當前位置: 5
重置後位置: 0
重新讀取: '0123456789'

文件的隨機訪問

# 創建一個結構化的數據文件
data_records = [
    "Record001:Alice:25\n",
    "Record002:Bob:30\n",
    "Record003:Charlie:35\n",
    "Record004:David:40\n"
]

with open('test_files/records.txt', 'w', encoding='utf-8') as f:
    f.writelines(data_records)

# 隨機訪問特定記錄
def read_record(filename, record_num):
    """
    讀取指定編號的記錄

    Args:
        filename: 文件名
        record_num: 記錄編號(從1開始)
    """
    record_length = len(data_records[0])  # 假設每條記錄長度相同

    with open(filename, 'r', encoding='utf-8') as f:
        # 計算目標記錄的位置
        position = (record_num - 1) * record_length
        f.seek(position)
        record = f.read(record_length).strip()
        return record

# 測試隨機訪問
print("隨機訪問記錄:")
for i in [3, 1, 4, 2]:
    record = read_record('test_files/records.txt', i)
    print(f"記錄{i}: {record}")

文本模式與二進制模式的區別

# 在文本模式下,seek()的行爲可能不同
with open('test_files/text_vs_binary.txt', 'w', encoding='utf-8') as f:
    f.write('Hello\nWorld\n中文測試')

print("文本模式下的指針操作:")
with open('test_files/text_vs_binary.txt', 'r', encoding='utf-8') as f:
    print(f"開始位置: {f.tell()}")
    print(f"讀取5個字符: '{f.read(5)}'")
    print(f"當前位置: {f.tell()}")

print("\n二進制模式下的指針操作:")
with open('test_files/text_vs_binary.txt', 'rb') as f:
    print(f"開始位置: {f.tell()}")
    print(f"讀取5個字節: {f.read(5)}")
    print(f"當前位置: {f.tell()}")

6.5 with語句與上下文管理

with語句的概念

with 語句是Python中處理上下文管理的推薦方式。它確保資源在使用後能夠正確釋放,即使在發生異常的情況下也是如此。

上下文管理器

上下文管理器是支持上下文管理協議的對象,它定義了在 with 語句中使用的運行時上下文。

# 傳統方式 vs with語句
print("傳統方式(不推薦):")
try:
    f = open('test_files/sample.txt', 'r', encoding='utf-8')
    content = f.read()
    print("文件內容:", content[:20] + "...")
finally:
    f.close()
    print("文件已關閉")

print("\nwith語句方式(推薦):")
with open('test_files/sample.txt', 'r', encoding='utf-8') as f:
    content = f.read()
    print("文件內容:", content[:20] + "...")
    print(f"在with塊中,文件是否關閉: {f.closed}")
print(f"離開with塊後,文件是否關閉: {f.closed}")

輸出結果:

傳統方式(不推薦):
文件內容: Hello, World!
This i...
文件已關閉

with語句方式(推薦):
文件內容: Hello, World!
This i...
在with塊中,文件是否關閉: False
離開with塊後,文件是否關閉: True

with語句的語法

# 基本語法
with open('test_files/with_demo.txt', 'w', encoding='utf-8') as file:
    file.write('使用with語句寫入的內容')
    # 文件會在離開with塊時自動關閉

# 驗證文件內容
with open('test_files/with_demo.txt', 'r', encoding='utf-8') as file:
    print("文件內容:", file.read())

with語句的優勢

1. 自動資源管理

# 即使發生異常,文件也會被正確關閉
try:
    with open('test_files/exception_test.txt', 'w', encoding='utf-8') as f:
        f.write('開始寫入')
        # 模擬異常
        raise ValueError("模擬異常")
        f.write('這行不會被執行')
except ValueError as e:
    print(f"捕獲異常: {e}")
    print(f"文件是否已關閉: {f.closed}")

2. 代碼簡潔

# 讀取多個文件並比較內容
files_to_compare = ['test_files/sample.txt', 'test_files/write_demo.txt']

for filename in files_to_compare:
    with open(filename, 'r', encoding='utf-8') as f:
        content = f.read()
        print(f"{filename}: {len(content)} 個字符")

同時操作多個文件

方法1:嵌套with語句

with open('test_files/source.txt', 'w', encoding='utf-8') as source:
    source.write('源文件內容\n第二行\n第三行')

with open('test_files/source.txt', 'r', encoding='utf-8') as source:
    with open('test_files/destination.txt', 'w', encoding='utf-8') as dest:
        # 複製文件內容
        for line in source:
            dest.write(line.upper())  # 轉換爲大寫

print("複製並轉換後的內容:")
with open('test_files/destination.txt', 'r', encoding='utf-8') as f:
    print(f.read())

方法2:逗號分隔語法

# 同時打開多個文件
with open('test_files/file1.txt', 'w', encoding='utf-8') as f1, \
     open('test_files/file2.txt', 'w', encoding='utf-8') as f2:
    f1.write('文件1的內容')
    f2.write('文件2的內容')
    print("同時寫入兩個文件")

# 驗證結果
with open('test_files/file1.txt', 'r', encoding='utf-8') as f1, \
     open('test_files/file2.txt', 'r', encoding='utf-8') as f2:
    print(f"文件1: {f1.read()}")
    print(f"文件2: {f2.read()}")

自定義上下文管理器簡介

class FileManager:
    """
    自定義文件管理器上下文管理器
    """
    def __init__(self, filename, mode, encoding='utf-8'):
        self.filename = filename
        self.mode = mode
        self.encoding = encoding
        self.file = None

    def __enter__(self):
        """進入with塊時調用"""
        print(f"打開文件: {self.filename}")
        self.file = open(self.filename, self.mode, encoding=self.encoding)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        """離開with塊時調用"""
        if self.file:
            self.file.close()
            print(f"關閉文件: {self.filename}")
        if exc_type:
            print(f"處理異常: {exc_type.__name__}: {exc_val}")
        return False  # 不抑制異常

# 使用自定義上下文管理器
with FileManager('test_files/custom_manager.txt', 'w') as f:
    f.write('使用自定義上下文管理器')

6.6 目錄操作

os模塊簡介

os 模塊提供了與操作系統交互的功能,包括文件和目錄操作。

import os

print("當前Python版本的os模塊功能演示")
print(f"操作系統類型: {os.name}")
print(f"當前工作目錄: {os.getcwd()}")

輸出結果:

當前Python版本的os模塊功能演示
操作系統類型: nt
當前工作目錄: C:\Users\yeyupiaoling/Python從入門到精通\test_files

目錄相關操作

import os

# 獲取當前工作目錄
current_dir = os.getcwd()
print(f"當前工作目錄: {current_dir}")

# 列出目錄內容
print("目錄內容:")
for item in os.listdir('.'):
    print(f"  {item}")

# 創建目錄
test_dir = 'test_subdir'
if not os.path.exists(test_dir):
    os.mkdir(test_dir)
    print(f"創建目錄: {test_dir}")
else:
    print(f"目錄已存在: {test_dir}")

print(f"檢查目錄是否存在: {os.path.exists(test_dir)}")
print(f"是否爲目錄: {os.path.isdir(test_dir)}")

輸出結果:

當前工作目錄: C:\Users\yeyupiaoling/Python從入門到精通\test_files
目錄內容: ['binary_sample.bin', 'numbers.txt', 'output.txt', 'sample.txt', 'test_dir_ops.py', 'test_file_ops.py']
創建目錄: test_subdir
檢查目錄是否存在: True
是否爲目錄: True

路徑操作

import os.path

# 路徑拼接
file_path = os.path.join('test_subdir', 'test.txt')
print(f"拼接路徑: {file_path}")
print(f"目錄名: {os.path.dirname(file_path)}")
print(f"文件名: {os.path.basename(file_path)}")
print(f"分離路徑: {os.path.split(file_path)}")
print(f"分離擴展名: {os.path.splitext(file_path)}")

輸出結果:

拼接路徑: test_subdir\test.txt
目錄名: test_subdir
文件名: test.txt
分離路徑: ('test_subdir', 'test.txt')
分離擴展名: ('test_subdir\\test', '.txt')

pathlib模塊(Python 3.4+)

pathlib 提供了面向對象的路徑操作方式:

from pathlib import Path

# 創建Path對象
path = Path('.')
print(f"當前路徑: {path.absolute()}")
print(f"路徑存在: {path.exists()}")
print(f"是否爲目錄: {path.is_dir()}")

# 創建文件
test_file = Path('test_subdir') / 'pathlib_test.txt'
test_file.write_text('Hello from pathlib!', encoding='utf-8')
print(f"創建文件: {test_file}")
print(f"文件內容: {test_file.read_text(encoding='utf-8')}")

輸出結果:

當前路徑: C:\Users\yeyupiaoling/Python從入門到精通\test_files
路徑存在: True
是否爲目錄: True
創建文件: test_subdir\pathlib_test.txt
文件內容: Hello from pathlib!

文件和目錄的遍歷

使用os.walk()函數

from pathlib import Path

print("當前目錄下的所有文件:")
for item in Path('.').iterdir():
    if item.is_file():
        print(f"  文件: {item.name}")
    elif item.is_dir():
        print(f"  目錄: {item.name}/")

輸出結果:

當前目錄下的所有文件:
  文件: binary_sample.bin
  文件: numbers.txt
  文件: output.txt
  文件: sample.txt
  文件: test_dir_ops.py
  文件: test_file_ops.py
  目錄: test_subdir/

使用glob模塊

import glob

# 創建一些測試文件
test_extensions = ['.txt', '.py', '.md']
for ext in test_extensions:
    filename = f'test_files/demo{ext}'
    with open(filename, 'w') as f:
        f.write(f'這是一個{ext}文件')

print("\n使用glob模塊查找文件:")

# 查找所有.txt文件
txt_files = glob.glob('test_files/*.txt')
print(f"所有.txt文件: {txt_files}")

# 查找所有文件
all_files = glob.glob('test_files/*')
print(f"所有文件: {len(all_files)} 個")

# 遞歸查找
all_txt_recursive = glob.glob('test_files/**/*.txt', recursive=True)
print(f"遞歸查找所有.txt文件: {all_txt_recursive}")

# 使用pathlib的glob
from pathlib import Path
path = Path('test_files')
py_files = list(path.glob('*.py'))
print(f"使用pathlib查找.py文件: {py_files}")

實際應用示例

def organize_files_by_extension(directory):
    """
    按文件擴展名組織文件

    Args:
        directory: 要組織的目錄路徑
    """
    from collections import defaultdict
    import os

    file_groups = defaultdict(list)

    # 遍歷目錄中的所有文件
    for filename in os.listdir(directory):
        filepath = os.path.join(directory, filename)
        if os.path.isfile(filepath):
            _, ext = os.path.splitext(filename)
            ext = ext.lower() or 'no_extension'
            file_groups[ext].append(filename)

    # 顯示結果
    print(f"\n目錄 '{directory}' 中的文件分類:")
    for ext, files in file_groups.items():
        print(f"{ext}: {len(files)} 個文件")
        for file in files[:3]:  # 只顯示前3個
            print(f"  - {file}")
        if len(files) > 3:
            print(f"  ... 還有 {len(files) - 3} 個文件")

# 使用文件組織功能
organize_files_by_extension('test_files')

總結

本章詳細介紹了Python中文件操作的各個方面:

  1. 文件的打開與關閉:學習了open()函數的各種參數和模式,以及正確關閉文件的重要性。

  2. 文件讀取方法:掌握了read()readline()readlines()等方法,以及處理大文件的策略。

  3. 文件寫入操作:瞭解了write()writelines()方法,以及不同寫入模式的區別。

  4. 文件指針與定位:學習了使用tell()seek()方法進行文件隨機訪問。

  5. with語句:掌握了上下文管理器的使用,這是處理文件操作的最佳實踐。

  6. 目錄操作:學習了使用os模塊和pathlib模塊進行目錄和路徑操作。

最佳實踐建議

  1. 始終使用with語句:確保文件能夠正確關閉,即使發生異常也不例外。

  2. 指定編碼格式:處理文本文件時,明確指定編碼格式(推薦UTF-8)。

  3. 處理大文件時使用生成器:避免一次性加載整個文件到內存。

  4. 使用pathlib進行路徑操作:它提供了更現代、更直觀的API。

  5. 異常處理:在文件操作中適當使用try-except處理可能的異常。

通過掌握這些文件操作技能,你將能夠高效地處理各種文件相關的任務,爲後續的數據處理、配置管理等工作打下堅實的基礎。

小夜