从“python 文件”这个标题说起,它其实比字面上要宽泛得多。我见过太多人第一次接触Python,第一个动作就是建一个.py文件,然后开始写print("hello");也有不少人卡在“文件读不进去”“Excel导出只有65536行”“打包exe失败”这类跟文件打交道的问题上。这篇文章就围绕Python文件操作的方方面面,从最基础的文件读写,到环境变量、打包发布,再到真实场景里的批处理脚本,把我踩过的坑和验证过的方案一次说清楚。适合刚入门的新手,也适合已经在写脚本但总被文件编码、路径坑到的人。
1. 内容整体设计与思路拆解
1.1 为什么文件操作是Python的第一道门槛
Python这两年能火遍各个领域,一个重要原因就是它处理文件特别顺手。爬虫爬下来的数据要写进文件,数据分析要读CSV、Excel,运维要批量重命名日志,开发要解析配置文件,甚至打包成exe可执行文件本质上也是在处理文件。可以说,Python跟文件的关系就像切菜刀跟厨师的关系——你绕不开,而且越早掌握,后面越省心。
我见过很多初学者,一开始觉得文件操作不就是open()然后read()嘛,结果一写就崩。编码不对、路径写错、文件被占用、读完没关闭、跨平台换行符不一样……这些问题单独看都不难,但串在一起就特别打击信心。所以我这篇内容的整体思路是:先把最核心的读写模型讲透,再往文件形态的上下游延伸——生成文件的数据处理、运行文件的工程环境、打包文件的可执行交付,最后用一套实战脚本收尾。
1.2 方案选型:用标准库还是第三方库
很多教程一上来就叫人装pandas,我反倒觉得没必要。处理简单文本、配置文件、批量复制重命名,Python标准库就够了,os、shutil、glob、csv、json这些模块都是自带,不需要装任何东西。等数据量大了、要处理Excel样式了,再上openpyxl、pandas这类第三方库,这才是合理的演进路线。
选型逻辑很简单:文件操作的第一原则是“你的项目需要跑多久、跑在哪里”。如果你只想把日志里的关键字抽出来,用open()配合循环就完事了。要是你需要对几万行的Excel做汇总透视,再考虑pandas。如果一上来就引一堆依赖,回头打包exe的时候文件体积直接奔着150MB去了,那才是给自己找麻烦。
1.3 从文件到工程:一条完整的学习路径
我给新手的建议是,不要只盯着“读写”两个字。一个完整的Python文件项目,通常要经历这四个阶段:
- 阶段一:读写单文件,搞清楚路径、编码、关闭。
- 阶段二:批量操作多文件,用
glob和shutil搞定自动化。 - 阶段三:处理结构化数据文件,比如CSV、Excel、JSON。
- 阶段四:把脚本变成一个环境独立、可执行、可交付的产物,也就是虚拟环境和exe打包。
这篇文章的结构就是按这条路来的。每一步我都会把“为什么这么干”讲清楚,不讲废话。
2. 核心细节解析与实操要点
2.1 路径处理:反斜杠、绝对路径和相对路径的坑
先解决一个最基础但最容易翻车的问题:路径。Windows上路径长这样:C:\Users\me\data\file.txt。但Python字符串里\是转义符,所以直接写open('C:\Users\me\data.txt')大概率报错,因为\U被当成Unicode转义。
解决办法有三个层次:
- 用原始字符串:
r'C:\Users\me\data.txt' - 把反斜杠换成斜杠:
'C:/Users/me/data.txt',Windows和Linux都能识别 - 用
pathlib模块(我推荐这个)
from pathlib import Path data_dir = Path(r"C:/Users/me/data") file_path = data_dir / "file.txt" # 用 / 拼接,跨平台安全为什么推荐pathlib?因为它把路径当成对象处理,exists()、read_text()、write_text()都是方法,不用反复去import os再拼字符串。Python 3.6以后自带,完全够用。
还有个概念必须分清楚:当前工作目录。脚本所在的目录不一定是运行命令时的目录。你在命令行敲python C:\project\run.py,但当前目录是C:\project外的某个位置,Python的相对路径就是基于那个“当前位置”算的,不是基于脚本位置。如果你不确定,最好在脚本开头加上:
import os os.chdir(os.path.dirname(os.path.abspath(__file__)))这句会把当前工作目录切到脚本所在目录。当然,如果你的脚本将来要被别人用包管理器安装执行,可能更希望用绝对的pathlib逻辑。但开发阶段,这个习惯能救你很多次。
2.2 文本文件读写:用with而不是open后close
我们最常用的读文件方式,很多老手也会写:
f = open("data.txt", "r", encoding="utf-8") content = f.read() f.close()这样写有个隐患:如果f.read()中间抛了异常,close()就不会执行,文件句柄一直被占着。在Windows上,文件可能被锁定,后续你想删除或重命名它就报“另一个程序正在使用此文件”。
正确姿势是上下文管理器:
with open("data.txt", "r", encoding="utf-8") as f: content = f.read() # 无论有没有异常,with块结束后文件都会自动关闭在with块里,你也可以一行一行读:
with open("access.log", "r", encoding="utf-8") as f: for line in f: print(line.strip())文件对象本身是迭代器,直接for line in f不会一次性把整个文件加载进内存,适合处理大日志文件。这是新手最容易忽略的点:read()是全文读入,readlines()也是,如果文件几个GB,内存直接爆掉。
写文件同样要用with,并且注意模式:
"w"覆盖写,文件不存在会创建"a"追加写,文件指针在末尾"x"独占创建,文件已存在会报错"+"可读可写,比如"r+"
一个小小的实用技巧:如果你不确定文件是否存在,用"x"模式可以安全地避免覆盖已有文件。
2.3 编码问题:utf-8不是唯一的答案
Python 3里,open()默认编码是locale.getpreferredencoding(),在Windows中文系统上通常是gbk。这就产生一个经典报错:
UnicodeDecodeError: 'gbk' codec can't decode byte 0x... in position ...: illegal multibyte sequence原因就是文件本身是utf-8编码,但你用gbk去解码。所以我的建议是:任何文件读写,都显式指定encoding="utf-8",并且保证文件本身真是utf-8。如果你接手的是老系统导出的GBK文件,那就反过来用encoding="gbk",或者更保险一点:
with open("data.csv", "r", encoding="gbk", errors="ignore") as f: content = f.read()errors="ignore"会静默忽略非法字节,代价是可能丢内容,所以只适合你想先看看情况的时候。真正要处理多来源文件,用chardet或charset-normalizer库自动检测编码:
pip install charset-normalizerfrom charset_normalizer import from_path best = from_path("data.csv").best() print(best.encoding)编码是文件操作里最隐蔽的坑,尤其当你把代码从一个平台拉到另一个平台(比如Windows写的脚本放到Linux服务器上跑),必须显式指定,不能指望系统默认。
2.4 二进制文件:图片、压缩包和专用格式
文本文件之外,还有一大类二进制文件。读取二进制要用"rb"模式,写用"wb"。二进制文件不能按行读,通常是一次性读入或分段读取:
with open("image.png", "rb") as f: data = f.read() # bytes对象复制二进制文件最稳妥的方式是分块复制,避免大文件全载入内存:
with open("source.zip", "rb") as src, open("target.zip", "wb") as dst: while chunk := src.read(4096): dst.write(chunk)这里用到了海象运算符:=,意思是“读取一块,如果非空就写出去”。4096字节是经典的缓冲区大小,你也可以用shutil.copyfile()一行搞定,标准库已经帮你封装好了:
import shutil shutil.copyfile("source.zip", "target.zip")如果只是复制文件,shutil永远是首选,不要自己手写循环。只有需要读取文件内容做二次处理(比如给图片加缩放、解析压缩包里的某些字节)时,才用二进制读写。
3. 实操过程与核心环节实现
3.1 安装Python与环境变量:跨平台的实际操作
网上搜索“python安装教程”的热度一直不减,很多问题其实不是Python本身的问题,而是环境变量没配好。Windows安装Python时,第一步就有一个勾选项“Add Python to PATH”,这个一定要勾上。如果忘了勾,安装完成后python命令会提示“不是内部或外部命令”。
这时候可以手动配:右键“此电脑” → 属性 → 高级系统设置 → 环境变量,在“系统变量”的Path里加入Python安装目录和它的Scripts子目录。安装目录通常是C:\Users\你的用户名\AppData\Local\Programs\Python\Python311\和同一路径下的Scripts\。
在Linux或者macOS上,一般系统自带Python,但咱自己开发建议用python3命令来区分系统其他脚本。如果你需要管理多个Python版本,推荐用pyenv(Linux/macOS)或Windows上的pylauncher。在Windows里,py -3.10可以指定使用3.10版本解释器。
装完Python,紧接着要会创建虚拟环境。为什么要虚拟环境?因为你不同的项目可能依赖不同版本的三方库,比如项目A需要pandas 1.5,项目B需要pandas 2.0,装在一起就打架。虚拟环境就是给每个项目单独开一个“文件柜”。
# Windows python -m venv venv venv\Scripts\activate # Linux/macOS python3 -m venv venv source venv/bin/activate激活后命令提示符会多一个(venv)前缀,这时候你pip install的东西都只在这个环境里生效。退出用deactivate。
这里有个高频搜索词“npm : 无法加载文件 ... npm.ps1,因为在此系统上禁止运行脚本”,其实跟Python关系不大,但经常在同一个终端里出现。这是PowerShell的执行策略限制,不是npm或Python坏了。解决办法有两种:
- 当前会话临时放开:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process - 永久放开(注意安全):管理员身份执行
Set-ExecutionPolicy RemoteSigned
后面这个策略的意思是,本机脚本可以运行,从网络下载的脚本需要签名。国内很多用Windows做开发的都被这个坑过,知道原因后其实不难。
3.2 VSCode配置Python环境的正确姿势
VSCode是目前最流行的Python编辑器,但很多人装完插件却没法跑,核心问题还是环境没选对。你需要做几步:
- 安装官方Python扩展(微软出的那个)。
- 按
Ctrl+Shift+P,输入“Python: Select Interpreter”,选择你虚拟环境里的python.exe。 - 配置好
.vscode/launch.json,一般第一次按F5运行时会提示你选环境,选“Python File”就行。
我见过很多同学在VSCode里写代码写得好好的,一运行就报“ModuleNotFoundError: No module named 'pandas'”。原因往往是:VSCode里右下角选择的解释器是全局Python,不是你的虚拟环境。而你在终端里明明pip install pandas成功。解决办法就是确认解释器路径和环境一致。
还有一个很冷门但很实用的设置:VSCode的集成终端默认使用PowerShell,如果执行策略禁止脚本,那么你激活虚拟环境的activate.ps1也会报错。你可以把VSCode默认终端切换成Command Prompt,或者干脆按上面说的设置执行策略。切换方法:Ctrl+Shift+P→ “Terminal: Select Default Profile” → 选“Command Prompt”。
3.3 用PyInstaller把Python脚本打包成exe文件
“python转exe文件”也是搜索热词。很多人写了个小工具,想发给没有Python环境的同事用,就希望打包成一个双击就能跑的文件。最成熟的方案是PyInstaller。
安装很简单:
pip install pyinstaller打包单文件:
pyinstaller -F your_script.py-F表示生成单个exe文件,好处是分发方便,坏处是启动慢(因为运行时需要解压)。如果不加-F,会生成一个目录,里面包含exe和依赖的dll、pyd,启动更快,但分发时要整个目录打包。
打包的时候要注意几点:
- 不要直接打包命令行输入交互的程序,你同事双击exe会看到黑框,建议用
input()也好、GUI也好,先想清楚交互方式。 - 程序里如果引用了图片、配置文件等资源文件,默认不会自动被你打包进去。要用
--add-data参数:
pyinstaller -F --add-data "assets;assets" your_script.pyWindows上用;分隔源和目标,Linux/macOS用:。
- 如果程序用了
pathlib读取相对路径,打包后的exe工作目录通常是exe所在目录,不是程序原来的目录。最好在代码里显式获取exe运行时的目录:
import sys import os from pathlib import Path if getattr(sys, 'frozen', False): base_dir = Path(sys.executable).resolve().parent else: base_dir = Path(__file__).resolve().parentsys.frozen是PyInstaller运行时的标志,用它判断是在源码环境还是exe环境,然后分别定位基础目录。我踩过这个坑,打包出来exe在别的文件夹里双击,结果找不到同目录的data.csv,最后才反应过来是工作目录切换的问题。
PyInstaller还有一个常见坑:杀毒软件误报。因为Python的exe启动时会释放临时文件,有些杀毒软件会认为是可疑行为。这个没有特别好的根治办法,通常可以换个打包参数,或者用UPX压缩,但误报依然存在。所以如果是正式分发给很多人的工具,还是要做好代码签名,这个就比较深了。
3.4 结构化文件:CSV、Excel和65536行问题
接下来是很多数据分析场景绕不开的:CSV和Excel文件。先说CSV,它本质是文本文件,所以也可以用open读,但用csv模块更安全,因为它处理逗号、引号、换行都很规矩:
import csv with open("data.csv", "r", encoding="utf-8", newline="") as f: reader = csv.DictReader(f) for row in reader: print(row["name"], row["age"])注意newline=""这个参数,特别是在Windows上,如果省略,写CSV的时候可能会多出空行,这是Python文档明确建议的。
Excel文件就麻烦一点,xls是老格式,需要xlrd来读,但新版本xlrd只支持xls,不支持xlsx;读取xlsx推荐用openpyxl或者直接用pandas。而写Excel时,如果用到xlwt或者某些旧库,会遇到一个经典限制:最多只能写65536行。这个数字大家很熟,因为老版Excel 2003的.xls格式,工作表上限就是65536行。
如果你用pandas.to_excel()默认引擎,它写的是xlsx,没有这个限制,行数上限是1048576。但如果你用了ExcelWriter并指定了engine="xlwt",就会撞上65536。解决办法很简单:改用engine="openpyxl"或者直接输出为.xlsx。
import pandas as pd df.to_excel("output.xlsx", index=False, engine="openpyxl")这段代码的意思是:把DataFrame写入Excel文件,不写索引。engine指定为openpyxl,就不会出现行数截断。如果你真的需要写超过一百万行,建议导出成CSV,那对行数没有任何限制,Excel也能打开一部分。
处理Excel还要留意单元格样式。如果只是写入数据,pandas加openpyxl够了。如果还要设置字体、合并单元格、加筛选,那就直接用openpyxl操作:
from openpyxl import Workbook from openpyxl.styles import Font wb = Workbook() ws = wb.active ws["A1"] = "标题" ws["A1"].font = Font(bold=True) ws.append(["姓名", "分数"]) ws.append(["张三", 88]) wb.save("report.xlsx")我从实际项目中的体会是:大批量数据处理交给pandas,精细样式调整交给openpyxl,两者可以配合使用,但是别混在同一个循环里反复读写Excel,性能很差。宁可先把数据整理好,最后一次写入。
3.5 批量文件处理:重命名、移动和备份
文件操作最有爽感的场景是批处理。比如你手里有几百个文件名带乱码的图片,或者日志按日期命名但格式不统一,手工改能改到怀疑人生。用Python写一个脚本只需要几十行。
先看一个批量重命名的例子。假设目录./logs/下有这些文件:
access-20240101.log access-20240102.log access-20240103.log你想把里面的日期改成2024-01-01这种格式。你可以这样写:
import os import re from pathlib import Path log_dir = Path("./logs") for f in log_dir.glob("access-*.log"): new_name = f"access-{f.stem[-8:]}.log" f.rename(f.parent / new_name)注意我用f.stem[-8:]取了文件名中后8位日期,然后重新命名。用Path.rename比os.rename更面向对象,也更少出现拼路径错的问题。
再比如你要把某类文件移动到归档目录:
import shutil from pathlib import Path source = Path("./downloads") target = Path("./archive") target.mkdir(exist_ok=True) for f in source.glob("*.pdf"): shutil.move(str(f), str(target / f.name))这里的mkdir(exist_ok=True)很关键,如果目录不存在会先创建。shutil.move既能移动文件,也能重命名。
实际批处理时,我强烈建议先打印出将要执行的操作,确认无误后再真正执行:
for f in source.glob("*.pdf"): dst = target / f.name print(f"移动 {f} -> {dst}") # shutil.move(str(f), str(dst)) # 确认后再取消注释这种“先dry-run,再执行”的习惯,能避免很多灾难。我自己有一次脚本里正则写错了,差一点把几十个文件的版本号全部洗掉,还好先跑了一遍print。
4. 常见问题与排查技巧实录
4.1 文件不存在的报错与检查思路
Python报FileNotFoundError,最常规的排查三步:
- 确认路径是否真的存在,最好用
Path.exists()先检查一下。 - 确认文件名的大小写,在Linux上区分大小写,Windows上不区分。
- 确认路径中的目录层级是否完整,别少写了一层。
还有一个隐藏问题:中文文件名或路径。Windows上用中文路径经常遇到编码问题,Python 3通常没问题,但如果调用一些老库或者子进程,可能会乱。最稳妥的办法是尽量用英文路径。如果你被迫处理中文目录,可以用pathlib来处理,它在底层已经做了Unicode处理。
4.2 文件内容读写乱码的排查
如果读出来是乱码,大概率是编码没对上。用编辑器打开原文件,看看右下角显示的编码,然后显式用那个编码读。还有一个技巧:用requests或open从网络上拿文件时,有些网站返回的是utf-8,有些是gbk,所以用response.encoding先判断一下:
import requests r = requests.get("https://example.com/data.csv") r.encoding = "utf-8" print(r.text)如果本地文件测试乱码,可以试试:
with open("data.csv", "rb") as f: raw = f.read() print(raw[:100])打印前100个字节,看是不是有\xff\xfe(UTF-16 BOM)、\xef\xbb\xbf(UTF-8 BOM)这些标志,帮你确认编码。
4.3 文件被占用:Windows专属的“另一个程序正在使用”
Windows用户经常遇到:程序运行一半报错“PermissionError: [Errno 13] Permission denied: 'xxx.xlsx'”,原因通常是这个文件在Excel里打开着,或者上一个脚本进程没有释放句柄。
排查办法:
- 关掉所有可能打开该文件的程序,尤其是Excel、Notepad。
- 检查任务管理器里是不是还有残留的python.exe进程,杀掉。
- 如果是自己代码写的,检查是不是用了
open()但没关闭,没有用with。
另外,如果你在脚本里多次打开同一个Excel文件,注意workbook.save之后,如果还持有workbook对象,文件句柄可能不会立即释放,最好workbook.close()。
4.4 环境相关命令找不到的问题汇总
热词里有一个很典型的:“git : 无法将‘git’项识别为 cmdlet、函数、脚本文件或可运行程序的名称”。这类问题跟python环境配置是同一个逻辑:你的命令目录不在PATH环境变量里。Python也一样,如果你装了Python但是运行python没反应,八成也是PATH问题。
快速诊断方法:在命令行里输入where python(Windows)或which python3(Linux/macOS),能看到当前使用的是哪个解释器。如果没任何输出,说明PATH里没加。
这里我顺便说一个比较隐蔽的情况:你明明设置了PATH,但还是找不到Python,那是因为安装时选的是“仅当前用户”,PATH变量存在于用户变量里,而不是系统变量。这种情况换个用户登录就失效。我一般建议直接装在系统层面,或者至少确保用户变量和系统变量都包含Python路径。
4.5 防止脚本误操作:备份和权限控制
最后一条排查技巧,其实也是预防技巧。批量改文件之前,先做一个备份副本,或者把所有文件复制到一个临时目录实验。如果是写文件操作,最好先用只读模式打开确认数据,再加"w"模式写。如果你只是要做数据清洗,不建议直接覆盖原文件,而是生成一个新文件。这样做的好处是:万一清洗逻辑写错了,原文件还在,你能很快回滚。
我们写批处理脚本时很推荐这个模式:
from pathlib import Path src = Path("raw_data.txt") dst = Path("cleaned_data.txt") with src.open("r", encoding="utf-8") as f: lines = f.readlines() cleaned_lines = [line.strip() for line in lines if line.strip()] with dst.open("w", encoding="utf-8") as f: f.writelines("\n".join(cleaned_lines))同样的“不直接修改原文件”原则,对于Excel、JSON、配置文件都适用。
5. 实战:我写的一个文件批处理小工具的全过程
5.1 需求描述与设计
为了把这些知识点串起来,我拿一个近期做过的小工具举例。需求是这样的:我有一堆运营导出的CSV文件,每个文件里有几千行数据,需要把里面某一列的日期从yyyyMMdd转成yyyy-MM-dd,然后按月份拆分成多个CSV文件,最后把结果打包成一个zip。
这个场景非常典型,涉及读CSV、按条件筛选、批量生成文件、打包压缩。我用标准库加一点pandas就能搞定。
先看目录结构:
raw_data/ 20240101_export.csv 20240102_export.csv ... output/ (待生成) tool.py5.2 第一步:读取并校验文件
我先写一个函数,遍历raw_data下所有CSV,用pandas读取,同时打印每个文件的行数和列名,确认日期列的格式。
import pandas as pd from pathlib import Path import zipfile RAW_DIR = Path("raw_data") OUT_DIR = Path("output") def load_all_files(): all_frames = [] for csv_file in RAW_DIR.glob("*.csv"): df = pd.read_csv(csv_file, dtype={"date": str}) print(f"读取 {csv_file.name}: {df.shape}") all_frames.append(df) if not all_frames: raise FileNotFoundError("raw_data目录下没有CSV文件") return pd.concat(all_frames, ignore_index=True)这里用dtype={"date": str}把日期列强制当作字符串读,防止pandas把它自动解析成时间类型,后面处理会省去很多格式问题。print一下形状,方便确认数据量。
5.3 第二步:清洗并标准化日期
日期列可能是20240101格式,也可能已经有2024-01-01,为了统一,我写一个小函数:
import datetime as dt def normalize_date(value): try: if "-" in value: return dt.datetime.strptime(value, "%Y-%m-%d").strftime("%Y-%m-%d") return dt.datetime.strptime(value, "%Y%m%d").strftime("%Y-%m-%d") except ValueError: # 无法解析就保留原值,并打日志 print(f"无法解析日期: {value}") return value然后在DataFrame里应用:
df["date"] = df["date"].apply(normalize_date)这一步注意:strptime后面跟着strftime,其实等效于重新格式化。如果你最后只需要字符串类型,这没问题。如果需要年月日分列,就用dt.year、dt.month。
5.4 第三步:按月分组并导出csv
把标准化后的数据按yyyy-MM取前7位,生成月份列,再groupby拆分,最后写入output目录。
df["month"] = df["date"].str[:7] OUT_DIR.mkdir(exist_ok=True) for month, group_df in df.groupby("month"): month_file = OUT_DIR / f"data_{month}.csv" group_df.drop(columns=["month"]).to_csv(month_file, index=False, encoding="utf-8-sig") print(f"已生成 {month_file.name}: {len(group_df)} 行")这里有个细节:写CSV时我用了encoding="utf-8-sig",而不是utf-8。为什么?因为在Windows上,Excel打开一个不带BOM的UTF-8文件会乱码。utf-8-sig就是带BOM的UTF-8,微软的Excel能正确识别。如果是给普通程序读,用utf-8更通用。这个细节我在实际交付时被业务同事反馈过,改完之后就没再出问题。
5.5 第四步:打包成zip
最终交付给运营时,他们不想一个一个下载CSV,所以我把output目录打包成zip。
zip_path = Path("delivery.zip") with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: for f in OUT_DIR.glob("*.csv"): zf.write(f, arcname=f.name)arcname参数可以控制zip里的文件名,这里只保留文件名不带目录,避免解压后多一层文件夹。ZIP_DEFLATED表示压缩,CSV文件一般能压到原大小的30%左右。
5.6 整体脚本与运行效果
把上面几段组合进一个main()函数,并在__main__里调用:
def main(): df = load_all_files() df["date"] = df["date"].apply(normalize_date) df["month"] = df["date"].str[:7] OUT_DIR.mkdir(exist_ok=True) for month, group_df in df.groupby("month"): group_df.drop(columns=["month"]).to_csv(OUT_DIR / f"data_{month}.csv", index=False, encoding="utf-8-sig") print(f"已生成 {month}.csv") with zipfile.ZipFile("delivery.zip", "w", zipfile.ZIP_DEFLATED) as zf: for f in OUT_DIR.glob("*.csv"): zf.write(f, arcname=f.name) print("全部完成") if __name__ == "__main__": main()运行输出类似:
读取 20240101_export.csv: (5120, 8) 读取 20240102_export.csv: (3890, 8) 已生成 data_2024-01.csv 已生成 data_2024-02.csv 全部完成整个脚本四五十行,但覆盖了文件遍历、批量读取、转化、导出和压缩。用到的一切都是pandas、pathlib、zipfile。
6. 给新手的几个文件操作小技巧
6.1 用tempfile处理临时文件
写脚本的时候,有时候需要生成中间文件,用完就扔。如果自己随便建一个tmp.txt,最后还得记得删,很麻烦。标准库的tempfile可以帮你自动处理:
import tempfile from pathlib import Path with tempfile.TemporaryDirectory() as tmpdir: tmp = Path(tmpdir) / "temp.csv" tmp.write_text("hello", encoding="utf-8") # 处理完之后目录自动清理这个模式在做数据处理流水线时特别方便,不用操心垃圾文件残留。
6.2 用fileinput快速修改多个文件
如果你想在多个文件里做原地替换,不想手动先读后写,可以用fileinput模块:
import fileinput for line in fileinput.input("*.txt", inplace=True, backup=".bak", encoding="utf-8"): print(line.replace("old", "new"), end="")这个脚本会让*.txt文件里的“old”全部替换成“new”,同时备份为.bak。虽然我其实很少推荐原地改文件,但如果你明确要做批量修改,fileinput确实是最短方案。
6.3 用pathlib统一处理目录时的小坑
Path.glob默认不会递归到子目录,想递归要用rglob。比如找出所有子目录下的.py文件:
for f in Path(".").rglob("*.py"): print(f)另外,Path(".")是当前目录,但Path("~")并不代表用户目录,要用Path.home()。写脚本的时候顺手导入Path,能少掉很多路径字符串拼接的麻烦。
6.4 检查文件是否为空
判断一个文件是否为空,别用os.path.getsize()硬记,用Path.stat().st_size:
from pathlib import Path p = Path("data.csv") if p.stat().st_size > 0: # 非空 pass如果你做的是流式处理,还可以先判断文件头几行是否为空再走逻辑,避免往下执行一堆无意义操作。
7. 常见问题速查表
| 问题现象 | 主要原因 | 解决建议 |
|---|---|---|
FileNotFoundError | 路径错误或文件不存在 | 用Path.exists()检查,确认大小写 |
UnicodeDecodeError | 编码与文件实际编码不符 | 显示指定encoding="utf-8",必要时检测编码 |
| Excel导出只有65536行 | 用了老式xls引擎 | 改用openpyxl引擎或导出xlsx |
| 文件被占用,无法保存 | 文件被其他程序打开或句柄未释放 | 关掉其他程序,检查是否漏了with |
命令行找不到python | PATH环境变量未配置 | 添加Python目录和Scripts目录到PATH |
| PowerShell禁止运行脚本 | 执行策略限制 | Set-ExecutionPolicy RemoteSigned |
| PyInstaller打包后找不到文件 | 工作目录变化 | 通过sys.frozen判断exe基础目录 |
| csv中文乱码 | Excel打开UTF-8文件无BOM | 用utf-8-sig编码导出 |
这张表我建议直接存下来,遇到同类问题先对着排查,比盲目去翻StackOverflow快得多。
从我个人的经验来说,Python文件操作的核心不难,难的是你总觉得它简单,然后忽略边界情况。路径、编码、句柄、环境,这四件事处理好了,绝大多数脚本都能稳稳跑起来。以后你再写点什么爬虫、数据处理、自动化工具,先把地基打好,真的能少吃很多苦。