第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中文件操作的各個方面:
-
文件的打開與關閉:學習了
open()函數的各種參數和模式,以及正確關閉文件的重要性。 -
文件讀取方法:掌握了
read()、readline()、readlines()等方法,以及處理大文件的策略。 -
文件寫入操作:瞭解了
write()和writelines()方法,以及不同寫入模式的區別。 -
文件指針與定位:學習了使用
tell()和seek()方法進行文件隨機訪問。 -
with語句:掌握了上下文管理器的使用,這是處理文件操作的最佳實踐。
-
目錄操作:學習了使用
os模塊和pathlib模塊進行目錄和路徑操作。
最佳實踐建議¶
-
始終使用with語句:確保文件能夠正確關閉,即使發生異常也不例外。
-
指定編碼格式:處理文本文件時,明確指定編碼格式(推薦UTF-8)。
-
處理大文件時使用生成器:避免一次性加載整個文件到內存。
-
使用pathlib進行路徑操作:它提供了更現代、更直觀的API。
-
異常處理:在文件操作中適當使用try-except處理可能的異常。
通過掌握這些文件操作技能,你將能夠高效地處理各種文件相關的任務,爲後續的數據處理、配置管理等工作打下堅實的基礎。