還在手動復制粘貼到崩潰?這 20 個 Python 代碼讓你提前下班
本文旨在通過20個貼近日常工作需求的Python代碼案例,系統(tǒng)性地展示其在文件處理、數(shù)據分析、自動化交互及可視化等方面的核心應用。助大家快速上手并應用于實際工作場景。
一、準備工作與基礎
在開始探索具體案例之前,請確保已完成必要的環(huán)境配置。
1. 環(huán)境要求與庫安裝
Python 環(huán)境: 確保已安裝 Python 3.x 版本。
第三方庫: 本文部分案例依賴以下庫,請通過 pip 安裝:
pip install requests pandas openpyxl matplotlib
- requests: 用于執(zhí)行 HTTP 網絡請求,是 API 交互和網頁數(shù)據獲取的基礎。
- pandas: 提供高性能、易用的數(shù)據結構(如 DataFrame)和數(shù)據分析工具。
- openpyxl: pandas 讀寫 .xlsx 格式 Excel 文件所需的引擎。
- matplotlib: 強大的數(shù)據可視化庫,用于繪制各種靜態(tài)、動態(tài)、交互式的圖表。
二、文件與目錄管理
高效管理和操作各種格式的文件及目錄結構是自動化的基石。
1. 文本文件
案例 1: 讀取文本文件
場景: 讀取程序配置文件、日志記錄、純文本報告等。
示例數(shù)據 (config.ini):
[Server]
Address=192.168.1.100
Port=8080
[User]
Username=admin
代碼:
import os
file_path = 'config.ini' # 定義文件路徑
# 檢查文件是否存在
if not os.path.exists(file_path):
print(f"錯誤: 文件 '{file_path}' 未找到。")
else:
try:
# 使用 'with open' 確保文件資源被正確釋放
# 'r' 表示讀取模式, 'encoding='utf-8'' 支持中文等非ASCII字符
with open(file_path, 'r', encoding='utf-8') as f:
print(f"--- 文件 '{file_path}' 內容 ---")
# 逐行讀取并打印,使用 strip() 去除行尾的換行符和空白
for line in f:
print(line.strip())
print("--- 讀取完成 ---")
# # 或者一次性讀取所有行到列表
# f.seek(0) # 指針移回文件開頭
# lines = f.readlines()
# print("\n讀取為列表:", [l.strip() for l in lines])
# # 或者一次性讀取整個文件內容為字符串
# f.seek(0)
# content = f.read()
# print("\n讀取為字符串:\n", content)
except IOError as e:
print(f"讀取文件時發(fā)生 IO 錯誤: {e}")
except Exception as e:
print(f"讀取文件時發(fā)生未知錯誤: {e}")
注釋: with open() 是推薦的文件操作方式,自動處理關閉。encoding='utf-8' 是處理多語言文本的標準實踐。strip() 方法對于清理每行數(shù)據非常有用。增加了文件存在性檢查和更具體的 IOError 捕獲。
案例 2: 寫入文本文件
場景: 保存程序輸出、生成文本格式的報告、創(chuàng)建新的配置文件。
output_file_path = 'system_report.log' # 定義輸出文件路徑
report_content = [
"[INFO] 系統(tǒng)狀態(tài)報告",
f"報告生成時間: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", # 使用案例15的時間功能
"CPU 使用率: 15%",
"內存占用: 2.5 GB / 8.0 GB",
"[ERROR] 服務 'nginx' 狀態(tài)異常,請檢查。",
]
try:
# 'w' 表示寫入模式 (覆蓋寫入); 若要追加內容,使用 'a' (append)模式
with open(output_file_path, 'w', encoding='utf-8') as f:
# 遍歷內容列表
for line in report_content:
# write() 方法寫入字符串,需手動添加換行符 '\n'
f.write(line + '\n')
print(f"報告已成功寫入文件: '{output_file_path}'")
except IOError as e:
print(f"寫入文件時發(fā)生 IO 錯誤: {e}")
except Exception as e:
print(f"寫入文件時發(fā)生未知錯誤: {e}")
# (確保已導入: from datetime import datetime)
注釋: 寫入模式 'w' 會清空原文件內容。需要追加時用 'a'。write() 不會自動換行,必須顯式添加 \n。結合了日期時間模塊(需導入datetime)生成動態(tài)內容。
2. CSV 文件
案例 3: 讀取 CSV 文件 (Pandas)
場景: 處理由逗號(或其他分隔符)分隔的表格數(shù)據,常用于數(shù)據導入。
示例數(shù)據 (employees.csv):
ID,Name,Department,Salary,HireDate
101,Alice Smith,HR,65000,2022-08-15
102,Bob Johnson,Engineering,88000,2021-05-20
103,Charlie Brown,Sales,72000,2023-01-10
104,David Lee,Engineering,95000,2020-11-01
import pandas as pd
import os
csv_file_path = 'employees.csv'
if not os.path.exists(csv_file_path):
print(f"錯誤: CSV 文件 '{csv_file_path}' 未找到。")
else:
try:
# 使用 pandas 讀取 CSV,自動推斷數(shù)據類型,header=0表示第一行為表頭
df = pd.read_csv(csv_file_path, encoding='utf-8')
print(f"--- 從 '{csv_file_path}' 讀取的 DataFrame ---")
# display 完整 DataFrame (在 Jupyter 等環(huán)境中效果更佳)
# 在普通腳本中,print(df) 即可
# display(df) # 取消注釋以在支持的環(huán)境中使用
print(df)
print("\n--- DataFrame 基本信息 ---")
df.info() # 顯示列名、非空值數(shù)量、數(shù)據類型、內存占用
print("\n--- DataFrame 描述性統(tǒng)計 (數(shù)值列) ---")
print(df.describe()) # 計算均值、標準差、最小值、最大值等
print("\n--- 前 3 行數(shù)據 ---")
print(df.head(3)) # 顯示頭幾行
except pd.errors.EmptyDataError:
print(f"錯誤: CSV 文件 '{csv_file_path}' 為空。")
except pd.errors.ParserError:
print(f"錯誤: 解析 CSV 文件 '{csv_file_path}' 失敗,請檢查格式。")
except Exception as e:
print(f"使用 pandas 讀取 CSV 時發(fā)生錯誤: {e}")
注釋: pandas.read_csv() 是讀取 CSV 的首選方法,功能強大。df.info() 和 df.describe() 用于快速了解數(shù)據概況。增加了對空文件和解析錯誤的捕獲。
案例 4: 寫入 CSV 文件 (Pandas)
場景: 將數(shù)據分析結果、處理后的數(shù)據導出為 CSV 格式,便于共享或后續(xù)使用。
import pandas as pd
from datetime import date
# 假設我們有一些處理后的數(shù)據,存儲在 DataFrame 中
processed_data = {
'ProductID': ['P001', 'P002', 'P003'],
'Category': ['Electronics', 'Home Goods', 'Electronics'],
'Revenue': [1200.50, 85.00, 699.99],
'LastUpdate': [date(2024, 7, 26), date(2024, 7, 25), date(2024, 7, 26)]
}
df_to_write = pd.DataFrame(processed_data)
output_csv_path = 'processed_products.csv'
try:
# 將 DataFrame 寫入 CSV 文件
# index=False: 不將 DataFrame 的索引寫入文件列
# encoding='utf-8-sig': 使用帶 BOM 的 UTF-8,有助于 Excel 正確識別中文
df_to_write.to_csv(output_csv_path, index=False, encoding='utf-8-sig', date_format='%Y-%m-%d')
print(f"DataFrame 已成功寫入 CSV 文件: '{output_csv_path}'")
except Exception as e:
print(f"使用 pandas 寫入 CSV 時發(fā)生錯誤: {e}")
# (確保已導入: from datetime import date)
注釋: DataFrame.to_csv() 方法非常方便。index=False 是常用選項,避免引入無意義的索引列。encoding='utf-8-sig' 解決 Excel 打開 UTF-8 CSV 文件可能出現(xiàn)的中文亂碼問題。date_format 控制日期對象的輸出格式。
3. Excel 文件 (XLSX)
案例 5: 讀取 Excel 文件 (Pandas)
場景: 處理 .xlsx 格式的電子表格,這是商業(yè)環(huán)境中非常常見的數(shù)據格式。
示例數(shù)據 (sales_data.xlsx): (假設包含一個名為 Q4_Sales 的工作表,結構類似 CSV 示例)
import pandas as pd
import os
excel_file_path = 'sales_data.xlsx'
sheet_name_to_read = 'Q4_Sales' # 指定要讀取的工作表名稱
if not os.path.exists(excel_file_path):
print(f"錯誤: Excel 文件 '{excel_file_path}' 未找到。")
else:
try:
# 使用 pandas 讀取 Excel 文件,需指定 sheet_name
# engine='openpyxl' 是讀取 .xlsx 格式所必需的
df_excel = pd.read_excel(excel_file_path, sheet_name=sheet_name_to_read, engine='openpyxl')
print(f"--- 從 '{excel_file_path}' (Sheet: '{sheet_name_to_read}') 讀取的 DataFrame ---")
# display(df_excel) # 在支持的環(huán)境中使用
print(df_excel)
print("\n--- 數(shù)據類型 ---")
print(df_excel.dtypes)
except FileNotFoundError: # redundant check but good practice within try
print(f"錯誤: 文件 '{excel_file_path}' 未找到。")
except ValueError as e:
# Handles cases like sheet name not found
print(f"讀取 Excel 文件時發(fā)生值錯誤: {e}")
except ImportError:
print("錯誤: 需要安裝 'openpyxl' 庫來讀取 .xlsx 文件。請運行: pip install openpyxl")
except Exception as e:
print(f"讀取 Excel 文件時發(fā)生未知錯誤: {e}")
注釋: pandas.read_excel() 是核心函數(shù)。sheet_name 參數(shù)可以指定工作表名稱或索引 (0-based)。必須安裝 openpyxl 才能處理 .xlsx 文件。添加了對 ImportError 的捕獲提示用戶安裝庫。
案例 6: 寫入 Excel 文件 (Pandas)
場景: 將多個數(shù)據分析結果或報告分別存儲到同一個 Excel 文件的不同工作表中。
import pandas as pd
# 假設有兩個 DataFrame 需要寫入
df_summary = pd.DataFrame({'Metric': ['Total Sales', 'Avg Order Value'], 'Value': [150000, 75.50]})
df_details = pd.DataFrame({ # Reuse data from Case 3 for details example
'ID': [101, 102, 103, 104],
'Name': ['Alice Smith', 'Bob Johnson', 'Charlie Brown', 'David Lee'],
'Salary': [65000, 88000, 72000, 95000]
})
output_excel_path = 'financial_report.xlsx'
try:
# 使用 pd.ExcelWriter 作為上下文管理器,可以寫入多個 sheet
with pd.ExcelWriter(output_excel_path, engine='openpyxl') as writer:
# 將第一個 DataFrame 寫入名為 'Summary' 的 sheet
df_summary.to_excel(writer, sheet_name='Summary', index=False)
# 將第二個 DataFrame 寫入名為 'Employee Details' 的 sheet
df_details.to_excel(writer, sheet_name='Employee Details', index=False, startrow=1, startcol=0) # 示例:指定起始行列
# 可以添加更多 df.to_excel(...) 調用來寫入更多 sheet
print(f"數(shù)據已成功寫入 Excel 文件 '{output_excel_path}' 的多個 Sheet 中。")
except ImportError:
print("錯誤: 需要安裝 'openpyxl' 庫來寫入 .xlsx 文件。請運行: pip install openpyxl")
except Exception as e:
print(f"寫入 Excel 文件時發(fā)生錯誤: {e}")
注釋: pd.ExcelWriter 是寫入多個工作表的標準方法。在 with 塊內,對每個 DataFrame 調用 to_excel() 并指定不同的 sheet_name。index=False 同樣常用。startrow, startcol 等參數(shù)可用于更精細的布局控制。
4. 目錄與文件系統(tǒng)操作
案例 7: 列出目錄內容
場景: 需要程序化地獲取指定文件夾下的所有文件和子文件夾列表。
import os
target_dir = '.' # '.' 代表當前腳本所在的目錄, 可替換為其他路徑
# target_dir = '/path/to/your/directory'
if not os.path.isdir(target_dir):
print(f"錯誤: 目錄 '{target_dir}' 不存在或不是一個有效的目錄。")
else:
try:
print(f"--- '{target_dir}' 目錄內容列表 ---")
# os.listdir() 返回目錄中所有條目的名稱列表 (字符串)
items = os.listdir(target_dir)
for item_name in items:
# 使用 os.path.join() 安全地構建完整路徑,跨平臺兼容
full_path = os.path.join(target_dir, item_name)
# 判斷是文件還是目錄
if os.path.isfile(full_path):
# 獲取文件大小 (可選)
file_size = os.path.getsize(full_path)
print(f" [文件] {item_name} ({file_size} bytes)")
elif os.path.isdir(full_path):
print(f" [目錄] {item_name}/")
else:
print(f" [其他] {item_name}") # 如符號鏈接等
except OSError as e:
print(f"訪問目錄 '{target_dir}' 時發(fā)生 OS 錯誤: {e}")
except Exception as e:
print(f"列出目錄內容時發(fā)生未知錯誤: {e}")
注釋: os.listdir() 獲取基本列表。os.path.join() 構造路徑是最佳實踐。os.path.isfile() 和 os.path.isdir() 用于區(qū)分類型。增加了目錄有效性檢查和 OSError 捕獲。
案例 8: 創(chuàng)建新目錄
場景: 在執(zhí)行文件寫入操作前,確保所需的輸出目錄層級結構存在。
import os
# 定義要創(chuàng)建的目錄路徑,可以是多層嵌套的
new_directory_path = 'output/reports/2024/Q3'
try:
# os.makedirs() 會創(chuàng)建所有不存在的父目錄
# exist_ok=True: 如果目標目錄已存在,不會拋出 FileExistsError 異常
os.makedirs(new_directory_path, exist_ok=True)
print(f"目錄 '{new_directory_path}' 已成功創(chuàng)建或已存在。")
# # 如果只想創(chuàng)建單層目錄,且要求父目錄必須存在,使用 os.mkdir()
# # os.mkdir('single_level_folder') # 如果存在會報錯
except OSError as e:
print(f"創(chuàng)建目錄時發(fā)生 OS 錯誤: {e}")
except Exception as e:
print(f"創(chuàng)建目錄時發(fā)生未知錯誤: {e}")
注釋: os.makedirs() 是創(chuàng)建目錄(尤其是嵌套目錄)的推薦方法。exist_ok=True 參數(shù)極大提升了腳本的健壯性,避免因目錄已存在而中斷。
案例 9: 重命名或移動文件/目錄
場景: 批量調整文件名,或將文件/目錄遷移到新的位置。
import os
# 假設我們有一個文件需要處理
source_path = 'system_report.log' # 源文件 (來自案例 2)
# 目標1: 重命名 (在同一目錄下)
rename_target_path = 'system_report_final.log'
# 目標2: 移動并重命名 (到新目錄)
move_target_path = 'output/logs/system_report_archive.log' # 移動到 output/logs/ 目錄下
# --- 執(zhí)行重命名 ---
try:
if os.path.exists(source_path):
os.rename(source_path, rename_target_path)
print(f"文件 '{source_path}' 已成功重命名為 '{rename_target_path}'")
source_path = rename_target_path # 更新源路徑,以便后續(xù)移動操作
else:
print(f"警告: 源文件 '{source_path}' 不存在,跳過重命名。")
except OSError as e:
print(f"重命名文件時發(fā)生 OS 錯誤: {e}")
except Exception as e:
print(f"重命名文件時發(fā)生未知錯誤: {e}")
# --- 執(zhí)行移動 ---
try:
if os.path.exists(source_path):
# 移動前,確保目標目錄存在
target_dir = os.path.dirname(move_target_path)
os.makedirs(target_dir, exist_ok=True) # 使用案例 8 的方法
# os.rename() 也可以用于移動文件/目錄
os.rename(source_path, move_target_path)
print(f"文件 '{source_path}' 已成功移動到 '{move_target_path}'")
else:
print(f"警告: 源文件 '{source_path}' 不存在,跳過移動。")
except OSError as e:
print(f"移動文件時發(fā)生 OS 錯誤: {e}")
except Exception as e:
print(f"移動文件時發(fā)生未知錯誤: {e}")
注釋: os.rename(src, dst) 函數(shù)同時處理重命名和移動。移動操作前,使用 os.path.dirname() 獲取目標目錄,并用 os.makedirs() 確保其存在,這是健壯代碼的關鍵。增加了對源文件存在性的檢查。
案例 10: 檢查路徑是否存在
場景: 在執(zhí)行讀取、寫入或刪除等操作前,驗證目標文件或目錄是否確實存在。
import os
paths_to_check = [
'employees.csv', # 假設存在的文件 (來自案例 3)
'output/reports/2024', # 假設存在的目錄 (來自案例 8)
'non_existent_file.tmp', # 假設不存在的文件
'.' # 當前目錄 (一定存在)
]
print("--- 路徑存在性檢查 ---")
for path in paths_to_check:
exists = os.path.exists(path)
print(f"路徑: '{path}'")
if exists:
is_file = os.path.isfile(path)
is_dir = os.path.isdir(path)
path_type = "文件" if is_file else ("目錄" if is_dir else "其他類型")
print(f" 狀態(tài): 存在 (類型: {path_type})")
else:
print(f" 狀態(tài): 不存在")
注釋: os.path.exists() 是最通用的存在性檢查。結合 os.path.isfile() 和 os.path.isdir() 可以進一步判斷路徑的具體類型。
三、數(shù)據處理與分析
利用 Python 的強大庫對結構化和半結構化數(shù)據進行解析、轉換和洞察。
1. JSON 數(shù)據解析
案例 11: 解析 JSON 字符串與文件
場景: 處理來自 Web API 的響應、讀取 JSON 格式的配置文件或數(shù)據存儲。
示例數(shù)據 (字符串):
json_string = '{"name": "Alice", "age": 30, "city": "New York", "isStudent": false, "courses": ["Math", "Physics"]}'
示例數(shù)據 (product_info.json):
{
"productId": "XYZ-123",
"details": {
"name": "Wireless Mouse",
"brand": "Logi",
"price": 29.99
},
"inventory": {
"stock": 150,
"locations": ["Warehouse A", "Warehouse B"]
},
"tags": ["electronics", "computer accessories", "wireless"]
}
import json
import os
# --- 從字符串解析 JSON ---
print("--- 解析 JSON 字符串 ---")
try:
data_from_string = json.loads(json_string) # loads: load string
print("解析結果 (Python Dict):", data_from_string)
print("姓名:", data_from_string['name'])
print("第一個課程:", data_from_string['courses'][0])
except json.JSONDecodeError as e:
print(f"解析 JSON 字符串失敗: {e}")
except Exception as e:
print(f"處理 JSON 字符串時發(fā)生錯誤: {e}")
print("\n--- 從文件解析 JSON ---")
json_file_path = 'product_info.json'
if not os.path.exists(json_file_path):
print(f"錯誤: JSON 文件 '{json_file_path}' 未找到。")
else:
try:
with open(json_file_path, 'r', encoding='utf-8') as f:
data_from_file = json.load(f) # load: load from file object
print("解析結果 (Python Dict):")
# 使用 json.dumps 美化打印輸出
print(json.dumps(data_from_file, indent=4, ensure_ascii=False))
print("\n產品名稱:", data_from_file['details']['name'])
print("庫存地點:", data_from_file['inventory']['locations'])
except json.JSONDecodeError as e:
print(f"解析 JSON 文件 '{json_file_path}' 失敗: {e}")
except IOError as e:
print(f"讀取 JSON 文件時發(fā)生 IO 錯誤: {e}")
except Exception as e:
print(f"處理 JSON 文件時發(fā)生錯誤: {e}")
注釋: json.loads() 用于從字符串解析,json.load() 用于從文件對象解析。兩者都將 JSON 數(shù)據轉換為相應的 Python 對象(通常是字典和列表)。json.dumps() 可用于將 Python 對象序列化回格式化的 JSON 字符串,indent=4 實現(xiàn)美觀縮進,ensure_ascii=False 保證非 ASCII 字符(如中文)正確顯示。
2. 使用 Pandas 進行數(shù)據篩選
案例 12: 基于條件篩選 DataFrame 行
場景: 從龐大的數(shù)據集中(如加載自 CSV 或 Excel 的 DataFrame)提取滿足特定業(yè)務規(guī)則的子集。
示例數(shù)據: 使用案例 3 中的 employees.csv 加載的 df。
import pandas as pd
import os
csv_file_path = 'employees.csv' # 確保此文件存在且內容如案例3所示
if not os.path.exists(csv_file_path):
print(f"錯誤: CSV 文件 '{csv_file_path}' 未找到。")
else:
try:
df = pd.read_csv(csv_file_path, parse_dates=['HireDate']) # 讀取時解析日期列
print("--- 原始 DataFrame (部分) ---")
print(df.head())
# --- 條件篩選示例 ---
print("\n--- 篩選: 部門為 'Engineering' 的員工 ---")
# 使用布爾索引: df[<boolean_series>]
eng_dept = df[df['Department'] == 'Engineering']
print(eng_dept)
print("\n--- 篩選: 薪水大于 80000 的員工 ---")
high_salary = df[df['Salary'] > 80000]
print(high_salary)
print("\n--- 篩選: 組合條件 - 'Engineering' 部門且薪水大于 90000 ---")
# 使用 & (與) 連接條件,每個條件需用括號包裹
eng_high_salary = df[(df['Department'] == 'Engineering') & (df['Salary'] > 90000)]
print(eng_high_salary)
print("\n--- 篩選: 部門為 'HR' 或 'Sales' 的員工 ---")
# 使用 | (或) 連接條件
hr_or_sales = df[(df['Department'] == 'HR') | (df['Department'] == 'Sales')]
# 或者使用 .isin() 方法,更適合多個 '或' 條件
# hr_or_sales = df[df['Department'].isin(['HR', 'Sales'])]
print(hr_or_sales)
print("\n--- 篩選: 2022年之后入職的員工 ---")
# 利用日期類型進行比較 (前提是 HireDate 已被解析為 datetime 對象)
hired_after_2022 = df[df['HireDate'].dt.year >= 2022]
print(hired_after_2022)
except Exception as e:
print(f"使用 pandas 篩選數(shù)據時發(fā)生錯誤: {e}")
注釋: Pandas 的布爾索引是核心篩選機制。通過比較列與值生成布爾 Series (True/False)。使用 & (與) 和 | (或) 組合條件,注意運算符優(yōu)先級,務必用括號包裹各個條件。.isin() 對于多個“或”條件的檢查更簡潔。對于日期/時間列,可以利用 .dt 訪問器進行年、月、日等屬性的比較。parse_dates 參數(shù)在讀取時轉換日期列,非常重要。
3. 使用 Pandas 進行分組聚合
案例 13: GroupBy 與聚合計算
場景: 對數(shù)據按類別進行分組,并對每個組執(zhí)行統(tǒng)計計算(如求和、均值、計數(shù)等),常用于生成匯總報告。
示例數(shù)據: 使用案例 3 中的 employees.csv 加載的 df。
import pandas as pd
import os
csv_file_path = 'employees.csv'
if not os.path.exists(csv_file_path):
print(f"錯誤: CSV 文件 '{csv_file_path}' 未找到。")
else:
try:
df = pd.read_csv(csv_file_path)
print("--- 原始 DataFrame (部分) ---")
print(df.head())
# --- 分組聚合示例 ---
print("\n--- 按 'Department' 分組,計算各部門員工人數(shù) ---")
# .groupby('列名') 創(chuàng)建分組對象,.size() 計算每組大小 (行數(shù))
dept_counts = df.groupby('Department').size()
# .reset_index(name='...') 將結果轉回 DataFrame,并指定計數(shù)列的名稱
dept_counts_df = dept_counts.reset_index(name='EmployeeCount')
print(dept_counts_df)
print("\n--- 按 'Department' 分組,計算各部門平均薪水 ---")
# 選擇要聚合的列 ['Salary'],然后調用聚合函數(shù) .mean()
avg_salary_by_dept = df.groupby('Department')['Salary'].mean()
avg_salary_by_dept_df = avg_salary_by_dept.reset_index(name='AverageSalary')
# 可以格式化輸出
avg_salary_by_dept_df['AverageSalary'] = avg_salary_by_dept_df['AverageSalary'].map('{:,.2f}'.format)
print(avg_salary_by_dept_df)
print("\n--- 按 'Department' 分組,計算多項聚合統(tǒng)計 (人數(shù), 平均/最高薪水) ---")
# 使用 .agg() 方法可以同時計算多個聚合指標
agg_stats = df.groupby('Department').agg(
NumberOfEmployees=('ID', 'count'), # 對 ID 列計數(shù) (假設 ID 非空)
AverageSalary=('Salary', 'mean'), # 計算 Salary 列的平均值
MaxSalary=('Salary', 'max'), # 計算 Salary 列的最大值
MinHireDate=('HireDate', 'min') # 計算 HireDate 的最早日期 (需先轉為日期類型)
)
# .reset_index() 將分組鍵 (Department) 轉回列
agg_stats_df = agg_stats.reset_index()
# 重命名列 (可選,如果 agg 中未使用元組指定名稱)
# agg_stats_df.columns = ['Department', 'EmployeeCount', 'AvgSalary', 'MaxSalary']
print(agg_stats_df)
except Exception as e:
print(f"使用 pandas 分組聚合時發(fā)生錯誤: {e}")
注釋: df.groupby('分組列') 是分組操作的起點。后接選擇要聚合的列(如 ['Salary'])和聚合函數(shù)(如 .mean(), .sum(), .count(), .size(), .max(), .min() 等)。.agg() 方法提供了更靈活的多重聚合能力,可以使用元組 (列名, 聚合函數(shù)) 并直接命名結果列。.reset_index() 常用于將分組結果從 Series 或帶有多級索引的 DataFrame 轉換回標準的 DataFrame。
四、自動化與交互
利用 Python 與外部系統(tǒng)、API 和文本數(shù)據進行交互,實現(xiàn)任務自動化。
1. 網絡數(shù)據獲取 (HTTP GET)
案例 14: 發(fā)送 GET 請求獲取 API 數(shù)據
場景: 調用 Web API 獲取實時數(shù)據、服務信息或執(zhí)行查詢。
import requests
import json
# 使用 JSONPlaceholder 作為示例 API (提供虛擬數(shù)據)
api_endpoint_url = 'https://jsonplaceholder.typicode.com/users/1' # 獲取 ID 為 1 的用戶信息
print(f"--- 向 '{api_endpoint_url}' 發(fā)送 GET 請求 ---")
try:
# 發(fā)送 GET 請求,設置超時時間 (timeout 秒) 防止無限等待
response = requests.get(api_endpoint_url, timeout=10)
# 檢查 HTTP 狀態(tài)碼,對于非 2xx (成功) 的狀態(tài)碼拋出異常
response.raise_for_status()
# 請求成功,解析返回的 JSON 數(shù)據
user_data = response.json() # requests 自動處理 JSON 解析
print("--- API 響應成功 (狀態(tài)碼:", response.status_code, ") ---")
print("解析后的用戶數(shù)據 (Python Dict):")
# 美化打印 JSON
print(json.dumps(user_data, indent=4))
# 訪問具體數(shù)據
print(f"\n用戶名: {user_data.get('username')}") # 使用 .get() 訪問字典更安全,鍵不存在時返回 None
print(f"公司名稱: {user_data.get('company', {}).get('name')}") # 嵌套訪問也用 .get()
except requests.exceptions.Timeout:
print("錯誤: 請求超時。服務器未在指定時間內響應。")
except requests.exceptions.HTTPError as e:
# raise_for_status() 拋出的異常
print(f"錯誤: HTTP 請求失敗,狀態(tài)碼: {e.response.status_code}")
print(f" 響應內容: {e.response.text[:200]}...") # 打印部分響應內容幫助調試
except requests.exceptions.RequestException as e:
# 其他網絡相關錯誤 (如 DNS 解析失敗、連接錯誤)
print(f"錯誤: 網絡請求異常: {e}")
except json.JSONDecodeError:
# 如果響應不是有效的 JSON 格式
print("錯誤: 無法解析 API 返回的 JSON 數(shù)據。")
print(f" 原始響應文本 (部分): {response.text[:200]}...")
except Exception as e:
print(f"處理 API 請求時發(fā)生未知錯誤: {e}")
注釋: requests.get(url, timeout=...) 發(fā)送 GET 請求。response.raise_for_status() 是檢查請求是否成功的關鍵一步。response.json() 將 JSON 響應體直接轉換為 Python 字典或列表。必須做好異常處理,覆蓋超時、HTTP 錯誤、網絡問題和 JSON 解析錯誤等常見情況。使用 .get() 訪問字典層級可以避免因鍵不存在而引發(fā) KeyError。
2. 文本模式處理 (正則表達式)
案例 15: 使用 Regex 查找文本模式
場景: 從非結構化文本(如日志文件、郵件內容、網頁源碼)中精確提取符合特定格式的信息(郵箱、URL、IP 地址、特定 ID 等)。
示例數(shù)據:
log_data = """
INFO: Processing request ID: REQ-12345 from user@example.com at 2024-07-26 10:00:00.
WARN: Slow response time for session SID-ABCDEF. IP: 192.168.1.100.
ERROR: Failed login attempt for admin@test.net. Request ID: REQ-67890. See details at https://internal.corp/errors/123.
"""
import re
print("--- 使用正則表達式查找模式 ---")
# 1. 查找所有郵箱地址
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
emails = re.findall(email_pattern, log_data)
print("找到的郵箱地址:", emails)
# 2. 查找所有請求 ID (格式: REQ- followed by digits)
request_id_pattern = r'\bREQ-\d+\b'
request_ids = re.findall(request_id_pattern, log_data)
print("找到的請求 ID:", request_ids)
# 3. 查找所有 IP 地址 (簡易版 IPv4)
ip_pattern = r'\b(?:\d{1,3}\.){3}\d{1,3}\b' # 使用非捕獲組 (?:...)
ip_addresses = re.findall(ip_pattern, log_data)
print("找到的 IP 地址:", ip_addresses)
# 4. 查找第一個 URL
url_pattern = r'https?://[^\s/$.?#].[^\s]*' # 查找 http 或 https 開頭的 URL
first_url_match = re.search(url_pattern, log_data) # search() 查找第一個匹配項
if first_url_match:
print("找到的第一個 URL:", first_url_match.group(0)) # group(0) 或 group() 返回整個匹配
else:
print("未找到 URL。")
注釋: re.findall(pattern, string) 返回所有非重疊匹配項的列表。re.search(pattern, string) 返回第一個匹配的 Match 對象,若無匹配則返回 None。Match 對象通過 .group() 方法獲取匹配的文本。正則表達式語法 (r'...') 是關鍵,需要根據目標模式精心構造。\b 表示單詞邊界,對于精確匹配很有用。
案例 16: 使用 Regex 替換文本內容
場景: 清洗數(shù)據、脫敏(隱藏敏感信息如密碼、手機號)、統(tǒng)一文本格式(如日期格式)。
示例數(shù)據:
report_text = "User 'john.doe' (john.doe@company.com) accessed sensitive data. Phone: +1-123-456-7890. Password hint: 'johndoe123'."
import re
print("--- 使用正則表達式替換文本 ---")
print("原始文本:", report_text)
# 1. 替換郵箱地址為 [REDACTED_EMAIL]
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
redacted_text_email = re.sub(email_pattern, '[REDACTED_EMAIL]', report_text)
print("替換郵箱后:", redacted_text_email)
# 2. 替換電話號碼 (簡易模式) 為 [REDACTED_PHONE]
phone_pattern = r'\+?\d[\d -]{8,}\d' # 匹配包含數(shù)字、空格、連字符的電話號碼
redacted_text_phone = re.sub(phone_pattern, '[REDACTED_PHONE]', redacted_text_email) # 在上一步結果上繼續(xù)替換
print("替換電話后:", redacted_text_phone)
# 3. 替換密碼提示 (單引號內的內容) 為 [REDACTED_HINT]
# 使用捕獲組 '([^']+)' 來匹配單引號內的內容,但不替換單引號本身
hint_pattern = r"Password hint: '([^']+)'"
# 使用 re.sub 的 repl 參數(shù)為函數(shù),可以進行更復雜的替換邏輯
def replace_hint(match):
# match.group(1) 獲取第一個捕獲組的內容 (密碼提示)
hint_content = match.group(1)
return f"Password hint: '[REDACTED_{len(hint_content)}_CHARS]'" # 返回替換后的字符串
redacted_text_final = re.sub(hint_pattern, replace_hint, redacted_text_phone)
print("替換密碼提示后:", redacted_text_final)
注釋: re.sub(pattern, replacement, string) 是核心替換函數(shù)。replacement 可以是固定字符串,也可以是一個函數(shù)。當 replacement 是函數(shù)時,它接收一個 Match 對象作為參數(shù),其返回值將用于替換匹配到的文本。這使得可以基于匹配到的內容進行動態(tài)替換(如此處根據密碼提示長度生成替換文本)。
3. 日期與時間處理
案例 17: 獲取當前日期時間與格式化
場景: 為日志、報告、文件名添加時間戳,或進行基于當前時間的計算。
from datetime import datetime
print("--- 獲取與格式化當前日期時間 ---")
# 獲取當前本地日期和時間
now = datetime.now()
print(f"當前完整時間對象: {now}")
# 獲取 UTC 時間 (世界協(xié)調時間)
utc_now = datetime.utcnow()
print(f"當前 UTC 時間對象: {utc_now}")
# 獲取日期部分
today_date = now.date()
print(f"當前日期: {today_date}")
# 獲取時間部分
current_time = now.time()
print(f"當前時間: {current_time}")
# --- 格式化輸出 (strftime: object to string) ---
# 常用格式代碼:
# %Y: 4位年份 (2024)
# %m: 月份 (01-12)
# %d: 日期 (01-31)
# %H: 24小時制小時 (00-23)
# %M: 分鐘 (00-59)
# %S: 秒 (00-59)
# %f: 微秒 (000000-999999)
# %A: 星期幾全稱 (Monday)
# %B: 月份全稱 (July)
# %Y-%m-%d %H:%M:%S 是非常標準的格式
formatted_standard = now.strftime("%Y-%m-%d %H:%M:%S")
print(f"標準格式化: {formatted_standard}")
formatted_filename = now.strftime("%Y%m%d_%H%M%S") # 適合用作文件名
print(f"文件名適用格式: {formatted_filename}")
formatted_readable = now.strftime("%A, %B %d, %Y - %I:%M:%S %p") # %I: 12小時制, %p: AM/PM
print(f"易讀格式化: {formatted_readable}")
注釋: datetime.now() 獲取本地當前時間。datetime.utcnow() 獲取 UTC 時間。.date() 和 .time() 分別提取日期和時間部分。strftime() 方法是關鍵,它根據指定的格式代碼將 datetime 對象轉換為字符串。
案例 18: 計算時間差 (Timedelta)
場景: 計算任務執(zhí)行耗時、事件間隔、項目周期、判斷是否超時等。
from datetime import datetime, timedelta
import time
print("--- 計算時間差 ---")
# 記錄開始時間
start_time = datetime.now()
print(f"操作開始時間: {start_time.strftime('%H:%M:%S.%f')}")
# 模擬一個耗時操作 (例如,暫停1.2秒)
time.sleep(1.2)
# 記錄結束時間
end_time = datetime.now()
print(f"操作結束時間: {end_time.strftime('%H:%M:%S.%f')}")
# 兩個 datetime 對象相減得到一個 timedelta 對象
duration = end_time - start_time
print(f"\n操作耗時 (timedelta 對象): {duration}")
# 從 timedelta 對象獲取具體數(shù)值
total_seconds = duration.total_seconds() # 獲取總秒數(shù) (包含小數(shù))
print(f"總耗時秒數(shù): {total_seconds:.3f} s")
days = duration.days # 獲取完整的天數(shù)
seconds_part = duration.seconds # 獲取不足一天部分的秒數(shù) (0-86399)
microseconds_part = duration.microseconds # 獲取微秒部分 (0-999999)
print(f"耗時構成: {days} 天, {seconds_part} 秒, {microseconds_part} 微秒")
# --- 時間點加減 ---
# 創(chuàng)建一個 timedelta 對象,例如表示 3 天 5 小時
time_delta_example = timedelta(days=3, hours=5)
print(f"\n時間增量示例: {time_delta_example}")
# 計算未來時間
future_time = start_time + time_delta_example
print(f"從開始時間算起,3天5小時后是: {future_time.strftime('%Y-%m-%d %H:%M:%S')}")
# --- 字符串解析與比較 (strptime: string to object) ---
date_str1 = "2023-01-15 09:00:00"
date_str2 = "2024-07-26 18:30:00"
try:
dt1 = datetime.strptime(date_str1, "%Y-%m-%d %H:%M:%S")
dt2 = datetime.strptime(date_str2, "%Y-%m-%d %H:%M:%S")
diff_between_dates = dt2 - dt1
print(f"\n'{date_str2}' 與 '{date_str1}' 相差: {diff_between_dates}")
print(f" 即相差天數(shù): {diff_between_dates.days}")
except ValueError as e:
print(f"解析日期字符串失敗: {e}")
注釋: datetime 對象相減產生 timedelta 對象。timedelta 表示時間間隔,擁有 days, seconds, microseconds 屬性和 total_seconds() 方法。datetime 對象可以與 timedelta 對象進行加減運算。datetime.strptime() 用于將符合特定格式的字符串解析回 datetime 對象,其格式代碼與 strftime() 相同。
4. 自動化郵件通知 (SMTP)
案例 19: 發(fā)送簡單郵件 (配置說明)
場景: 在任務完成、發(fā)生異常或達到特定條件時,自動發(fā)送郵件通知相關人員。
注意: 此代碼為模板,必須根據您使用的郵箱服務商(如 QQ郵箱、163郵箱、Gmail、企業(yè)郵箱等)提供的 SMTP 服務器地址、端口號,并使用授權碼(而非郵箱登錄密碼)進行配置。直接運行會失敗。請務必開啟郵箱的 SMTP 服務。
import smtplib
import ssl # 用于安全的 SSL/TLS 連接
from email.mime.text import MIMEText # 構建郵件正文
from email.header import Header # 處理郵件頭中的非 ASCII 字符
# --- SMTP 服務器配置 (必須替換為實際值) ---
smtp_server = 'smtp.example.com' # 例如: smtp.qq.com, smtp.163.com, smtp.gmail.com
smtp_port = 465 # SSL 端口 (常用) 或 587 (TLS 常用)
sender_email = 'your_account@example.com' # 發(fā)件人郵箱賬號
sender_password = 'YOUR_APP_PASSWORD' # 郵箱授權碼 (不是登錄密碼!)
receiver_emails = ['recipient1@example.net', 'recipient2@example.org'] # 收件人列表
# -------------------------------------------
# --- 郵件內容 ---
mail_subject = '【自動化通知】Python 腳本執(zhí)行報告'
mail_body = f"""
尊敬的用戶,
本次 Python 自動化任務已于 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} 執(zhí)行完畢。
狀態(tài): 成功
處理記錄數(shù): 150
詳情請查閱附件或日志文件。
此郵件為系統(tǒng)自動發(fā)送,請勿回復。
""" # 可以使用 f-string 動態(tài)生成內容
# ----------------
# 構建郵件對象
message = MIMEText(mail_body, 'plain', 'utf-8') # 內容, 類型(plain/html), 編碼
message['From'] = Header(f"自動化監(jiān)控平臺 <{sender_email}>", 'utf-8') # 發(fā)件人顯示名
message['To'] = Header(", ".join(receiver_emails), 'utf-8') # 收件人列表顯示
message['Subject'] = Header(mail_subject, 'utf-8') # 郵件主題
print("--- 準備發(fā)送郵件 ---")
print(f"SMTP 服務器: {smtp_server}:{smtp_port}")
print(f"發(fā)件人: {sender_email}")
print(f"收件人: {', '.join(receiver_emails)}")
context = ssl.create_default_context() # 創(chuàng)建安全的 SSL 上下文
server = None # 初始化 server 變量
try:
if smtp_port == 465:
# 使用 SMTP_SSL (Implicit SSL/TLS)
print("正在建立 SSL 連接...")
server = smtplib.SMTP_SSL(smtp_server, smtp_port, cnotallow=context, timeout=15)
elif smtp_port == 587:
# 使用 SMTP + starttls (Explicit TLS)
print("正在建立普通連接...")
server = smtplib.SMTP(smtp_server, smtp_port, timeout=15)
print("升級到 TLS 加密連接...")
server.starttls(cnotallow=context)
else:
raise ValueError("不支持的 SMTP 端口配置。請使用 465 (SSL) 或 587 (TLS)。")
print("連接成功,正在登錄...")
# 登錄 SMTP 服務器
server.login(sender_email, sender_password)
print("登錄成功。")
# 發(fā)送郵件
print("正在發(fā)送郵件...")
# sendmail(發(fā)件人地址, 收件人地址列表, 郵件對象的字符串表示)
server.sendmail(sender_email, receiver_emails, message.as_string())
print("郵件已成功發(fā)送!")
except smtplib.SMTPAuthenticationError:
print("錯誤: SMTP 認證失?。≌垯z查發(fā)件人郵箱和授權碼是否正確,以及 SMTP 服務是否已開啟。")
except smtplib.SMTPConnectError:
print(f"錯誤: 無法連接到 SMTP 服務器 {smtp_server}:{smtp_port}。請檢查地址和端口。")
except smtplib.SMTPServerDisconnected:
print("錯誤: SMTP 服務器意外斷開連接。")
except socket.timeout: # 需要 import socket
print("錯誤: 連接或操作超時。")
except ssl.SSLError as e:
print(f"錯誤: SSL/TLS 握手失敗: {e}。請檢查端口和服務器配置。")
except Exception as e:
print(f"發(fā)送郵件時發(fā)生未知錯誤: {e}")
finally:
# 無論成功與否,都嘗試關閉連接
if server:
try:
server.quit()
print("已斷開與 SMTP 服務器的連接。")
except Exception as e:
print(f"關閉 SMTP 連接時出錯: {e}")
# (確保已導入: from datetime import datetime, import socket, import ssl)
注釋: 郵件發(fā)送涉及網絡和安全,是相對復的自動化任務。
- 正確配置 SMTP 服務器、端口、發(fā)件人郵箱。
- 使用授權碼而非登錄密碼。
- 啟用郵箱的 SMTP 服務。
- 根據端口選擇 smtplib.SMTP_SSL (465) 或 smtplib.SMTP + server.starttls() (587)。
- 使用 email.mime.text.MIMEText 構建郵件正文,email.header.Header 處理含中文的標題/發(fā)件人/收件人。
- 完善的異常處理至關重要。
- 切勿在代碼中硬編碼密碼/授權碼,應使用環(huán)境變量、配置文件或密鑰管理服務。
五、數(shù)據可視化
將數(shù)據分析結果以圖形方式呈現(xiàn),更直觀地傳遞信息和洞察。
1. 使用 Matplotlib 繪圖
案例 20: 繪制基礎圖表 (柱狀圖、直方圖)
場景: 將 Pandas 分析得到的匯總數(shù)據(如部門人數(shù)、平均工資)或原始數(shù)據分布(如工資分布)進行可視化展示。
依賴: matplotlib 庫,通常與 pandas 結合使用。
示例數(shù)據: 使用之前的案例 生成的 dept_counts_df, avg_salary_by_dept_df 和原始 df。
代碼:
import pandas as pd
import matplotlib.pyplot as plt
import os
# --- 準備數(shù)據 (假設已運行案例 19 的代碼得到 df, dept_counts_df, avg_salary_by_dept_df) ---
# 為保證獨立運行,這里重新加載并計算一次
csv_file_path = 'employees.csv'
if not os.path.exists(csv_file_path):
print(f"錯誤: 數(shù)據文件 '{csv_file_path}' 未找到。無法進行可視化。")
# exit() # 在腳本中可以考慮退出
else:
try:
df = pd.read_csv(csv_file_path)
dept_counts_df = df.groupby('Department').size().reset_index(name='EmployeeCount')
avg_salary_by_dept_df = df.groupby('Department')['Salary'].mean().reset_index(name='AverageSalary')
# --- 開始繪圖 ---
# 設置 Matplotlib 支持中文顯示 (選擇一種方式)
# 方式一: 指定支持中文的字體
plt.rcParams['font.sans-serif'] = ['SimHei'] # 例如: SimHei (黑體)
plt.rcParams['axes.unicode_minus'] = False # 解決負號顯示為方塊的問題
# 方式二: (如果安裝了 specific font)
# from matplotlib.font_manager import FontProperties
# font = FontProperties(fname='/path/to/your/chinese.ttf') # 指定字體文件路徑
# --- 1. 繪制部門人數(shù)柱狀圖 ---
plt.figure(figsize=(8, 5)) # 創(chuàng)建圖表畫布, 設置尺寸 (寬, 高) in inches
# plt.bar(x軸數(shù)據, y軸數(shù)據, ...)
plt.bar(dept_counts_df['Department'], dept_counts_df['EmployeeCount'], color='skyblue', alpha=0.8)
plt.title('各部門員工人數(shù)分布', fnotallow=14) # 圖表標題
plt.xlabel('部門 (Department)', fnotallow=12) # X 軸標簽
plt.ylabel('員工人數(shù) (Number of Employees)', fnotallow=12) # Y 軸標簽
plt.xticks(rotatinotallow=30, ha='right') # 旋轉 X 軸標簽以便閱讀
plt.grid(axis='y', linestyle='--', alpha=0.6) # 添加水平網格線
plt.tight_layout() # 自動調整子圖參數(shù),使之填充整個圖像區(qū)域,防止標簽重疊
# plt.savefig('department_counts.png') # 保存圖表到文件 (可選)
plt.show() # 顯示圖表窗口
# --- 2. 繪制各部門平均薪水條形圖 (水平) ---
plt.figure(figsize=(9, 5))
# plt.barh(y軸數(shù)據, x軸數(shù)據, ...) # 注意 barh 的參數(shù)順序
bars = plt.barh(avg_salary_by_dept_df['Department'], avg_salary_by_dept_df['AverageSalary'], color='lightcoral')
# 在條形圖上添加數(shù)值標簽
for bar in bars:
width = bar.get_width()
plt.text(width + 500, bar.get_y() + bar.get_height()/2, f'${width:,.0f}', # x, y, text
va='center', ha='left', fnotallow=10)
plt.title('各部門平均薪水對比', fnotallow=14)
plt.xlabel('平均薪水 (Average Salary)', fnotallow=12)
plt.ylabel('部門 (Department)', fnotallow=12)
plt.gca().xaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}')) # 格式化 X 軸刻度為美元
plt.tight_layout()
# plt.savefig('average_salary_by_dept.png')
plt.show()
# --- 3. 繪制全體員工薪水分布直方圖 ---
plt.figure(figsize=(8, 5))
# plt.hist(數(shù)據列, bins=柱子數(shù)量/邊界, ...)
plt.hist(df['Salary'], bins=6, color='lightgreen', edgecolor='black', alpha=0.7)
plt.title('員工薪水分布直方圖', fnotallow=14)
plt.xlabel('薪水區(qū)間 (Salary Range)', fnotallow=12)
plt.ylabel('頻數(shù) (Frequency)', fnotallow=12)
plt.gca().xaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x:,.0f}'))
plt.grid(axis='y', linestyle=':', alpha=0.5)
plt.tight_layout()
# plt.savefig('salary_distribution_hist.png')
plt.show()
except ImportError:
print("錯誤: 需要安裝 matplotlib 庫來進行可視化。請運行: pip install matplotlib")
except Exception as e:
print(f"繪制圖表時發(fā)生錯誤: {e}")
注釋:
- matplotlib.pyplot (通常別名為 plt) 是主要的繪圖接口。
- plt.figure() 創(chuàng)建畫布。
- plt.bar() (垂直柱狀圖)。
- plt.barh() (水平條形圖)。
- plt.hist() (直方圖) 是常用繪圖函數(shù)。
- plt.title(), plt.xlabel(), plt.ylabel() 設置標題和軸標簽。
- plt.xticks()/plt.yticks() 控制刻度。
- plt.grid() 添加網格線。
- plt.tight_layout() 自動優(yōu)化布局。
- plt.show() 顯示圖表,
- plt.savefig() 保存圖表到文件。
- 中文顯示需要額外配置字體 (plt.rcParams)。
- 在條形圖/柱狀圖上添加數(shù)值標簽可以增強可讀性。
六、總結
本文通過 20個貼近實際工作場景的 Python 案例,系統(tǒng)性地展示了其在 文件與目錄管理、數(shù)據處理與分析、自動化與交互 以及 數(shù)據可視化 等方面的強大能力和廣泛應用。
掌握這些基礎模塊是邁向更高級自動化的第一步。持續(xù)學習和實踐,將 Python 的能力融入您的日常工作流,定能發(fā)掘出更多提升效率、創(chuàng)造價值的可能性。另外,大家在使用代碼時注意代碼格式(因文章排版原因,部分代碼需注意左對齊。)