Appearance
文件与文本处理
写脚本几乎绕不开文件——读配置、写报告、解析数据、备份内容。Python 标准库在这块准备得很全:pathlib 管路径、json 管 JSON、csv 管表格、re 管正则、datetime 管时间。
一、pathlib——路径和文件操作
pathlib.Path 用对象表示路径,比手工拼字符串稳得多——/ 当分隔符、自动处理跨平台差异、链式调用一行搞定好几件事。
python
from pathlib import Path
# 路径拼接:用 / 运算符,不用拼字符串
notes_dir = Path("notes")
article_path = notes_dir / "python" / "intro.md"
# 读整个文件
content = article_path.read_text(encoding="utf-8")
# 写整个文件
article_path.write_text("正文内容", encoding="utf-8")read_text 和 write_text 一行搞定读写,内部自动开关文件,不用手动 open close。
判断文件和目录:
python
path = Path("notes/article.txt")
path.exists() # 是否存在
path.is_file() # 是不是文件
path.is_dir() # 是不是目录创建目录:
python
output = Path("output/reports")
output.mkdir(parents=True, exist_ok=True)
# parents=True:父目录不存在时一起建
# exist_ok=True:目录已存在不报错mkdir 不加 exist_ok=True 时,目录已经存在会抛异常——这是写脚本时常见的一个坑,先建目录再写文件,跑第二次就报"目录已存在"。
1 按行读大文件
read_text 会把整个文件读进内存。文件几个 G 时用 open 逐行读:
python
with Path("notes/big_log.txt").open(encoding="utf-8") as f:
for line in f:
if "错误" in line:
print(line.strip())with 保证文件用完自动关闭。逐行读时内存里始终只有一行,不会因为文件大就爆内存。
2 追加写入
write_text 是覆盖写。要往文件末尾追加,用 open 的 "a" 模式:
python
log = Path("notes/changelog.txt")
with log.open("a", encoding="utf-8") as f:
f.write("2024-06-01 新增文件处理章节\n")"a" 是 append,每次写在文件末尾;"w" 是 write,每次覆盖整个文件。
二、JSON
配置和数据交换经常用 JSON。标准库 json 处理:
python
import json
from pathlib import Path
# JSON 字符串 → Python 对象
raw = '{"title": "Python 入门", "author": "张三"}'
article = json.loads(raw)
print(article["title"]) # Python 入门
# Python 对象 → JSON 字符串
text = json.dumps(article, ensure_ascii=False, indent=2)
print(text)json.loads 把 JSON 字符串解析成 Python 的字典/列表;json.dumps 反过来。ensure_ascii=False 让中文保持原样,不加这个参数,中文会被转成 张三 这种,人没法读。indent=2 让输出带缩进,好看。
读写 JSON 文件:
python
path = Path("notes/articles.json")
# 写
data = [
{"title": "Python 入门", "status": "已发布"},
{"title": "Go 并发", "status": "草稿"},
]
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
# 读
loaded = json.loads(path.read_text(encoding="utf-8"))
print(loaded[0]["title"]) # Python 入门json.loads 报 JSONDecodeError 说明格式不对——少了引号、多了逗号、括号没闭合。排查时把 JSON 内容贴到校验工具里看一眼,比盯着代码猜快。
三、CSV
表格数据导出成 CSV 很常见。标准库 csv 的 DictReader 和 DictWriter 按表头读写,不用记列下标:
python
import csv
from pathlib import Path
# 读:DictReader 把每行变成字典,键是表头
with Path("contacts.csv").open(encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["姓名"], row["电话"]) # 直接用表头名取值python
# 写:DictWriter 按字段名写
rows = [
{"姓名": "张三", "电话": "13800138000"},
{"姓名": "李四", "电话": "13900139000"},
]
with Path("contacts_out.csv").open("w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["姓名", "电话"])
writer.writeheader()
writer.writerows(rows)newline="" 是 CSV 的固定写法,不加的话 Windows 上行间会多出空行。
四、正则表达式
正则从非结构化文本里抽字段——日志行、命令输出、自由格式的文本。有结构化格式(JSON、CSV)时优先用对应解析器,正则留给没有固定结构的文本。
python
import re
# 从日志行提取时间、级别、内容
line = "2024-06-01 10:30:15 INFO 服务启动成功"
pattern = r"(\S+ \S+) (\w+) (.*)"
match = re.match(pattern, line)
if match:
time_str, level, message = match.groups()
print(time_str) # 2024-06-01 10:30:15
print(level) # INFO
print(message) # 服务启动成功re.match 从开头匹配,() 分组,match.groups() 取出各组。用 (?P<名字>...) 给分组起名,可读性更好:
python
pattern = r"(?P<time>\S+ \S+) (?P<level>\w+) (?P<message>.*)"
match = re.match(pattern, line)
print(match.group("level")) # INFO找出所有匹配(findall)和替换(sub):
python
# 找出文本里所有邮箱
emails = re.findall(r"[\w.]+@[\w.]+", text)
# 把密码、token 替换成星号
masked = re.sub(r"(password|token)=\S+", r"\1=***", "password=abc123 token=xyz")
print(masked) # password=*** token=***正则能跑通不等于准确——IP、邮箱、URL 这些格式要求严格时,表达式要按实际数据收紧,否则会漏匹配或误匹配。
五、datetime——时间和日期
时间处理在脚本里很常见——给文件名加日期、算两个时间点差多少天、格式化输出。标准库 datetime 管这些:
python
from datetime import datetime
# 当前时间
now = datetime.now()
print(now) # 2024-06-01 10:30:15.123456
# 格式化成字符串
print(now.strftime("%Y-%m-%d %H:%M")) # 2024-06-01 10:30
# 字符串解析成 datetime
dt = datetime.strptime("2024-06-01", "%Y-%m-%d")
print(dt) # 2024-06-01 00:00:00strftime(string format time)把时间对象格式化成字符串,strptime(string parse time)反过来。常用的格式符:%Y 年、%m 月、%d 日、%H 时、%M 分、%S 秒。
1 给文件名加日期
python
from datetime import datetime
from pathlib import Path
today = datetime.now().strftime("%Y-%m-%d")
backup_path = Path(f"backup/articles-{today}.json")
print(backup_path) # backup/articles-2024-06-01.json2 时间差
python
from datetime import datetime
created = datetime.strptime("2024-05-01", "%Y-%m-%d")
now = datetime.now()
delta = now - created
print(delta.days) # 相差多少天
print(delta.total_seconds()) # 相差多少秒两个 datetime 相减得到 timedelta 对象,.days 是天数,.total_seconds() 是总秒数。
3 时区
python
from datetime import datetime, timezone, timedelta
# 东八区
tz_beijing = timezone(timedelta(hours=8))
now_bj = datetime.now(tz_beijing)
print(now_bj) # 2024-06-01 10:30:15+08:00不指定时区时,datetime.now() 返回的是本地时间(跟系统时区一致)。跨时区场景(服务器在国外、日志要统一时间)要显式带时区,不然时间对不上。容器里这个坑特别常见——容器时区是 UTC,应用按本地时间记日志,跟宿主机差 8 小时。