
放在真正动笔之前我先说一句把控制结构和文件操作放到同一个实验里是有讲究的。你单学if、for、while顶多是会写几个判断和循环你单学open()、read()、write()也只是会摆弄几个文本文件。可一旦把它们组合起来你就能写真正“干活”的工具——自动读配置、批量处理日志、扫描目录、整理数据。这也是这个实验最值钱的地方它给了你一条从“写语法”到“写程序”的转折线。这个实验适合两类人。一类是刚开始学Python、正被各种教程带着走但还没动手写过自己程序的人另一类是工作中偶尔要用Python处理表格、日志、批量文件的非专业程序员。如果你属于这两类把这个实验吃透比你看十遍语法视频都管用。接下来我按自己的实操习惯把整个实验从环境准备、语法细节到综合案例完整过一遍全是能直接照抄的代码和步骤。1. 实验设计与环境准备1.1 这个实验到底在练什么很多人在实验报告上写“本实验掌握Python控制结构和文件操作”但真要问练到什么程度心里没底。我理解的控制结构不是能默写if和for而是拿到一个实际问题时知道该用哪种分支、哪种循环、怎么把循环写得不臃肿、怎么避免死循环。文件操作也不是会调open()就行而是要懂得读什么文件用什么模式、怎么处理编码、怎么批量操作多个文件、怎么在出错时不把数据写坏。实验设计成“控制结构文件操作”的组合就是逼着你把两件事串起来。比如你要统计一个日志文件里有多少条ERROR你得先会一行一行读文件再会判断每行里有没有“ERROR”这几个字符还要会累加计数。这中间任何一个环节断了程序就跑不出结果。这才是真实的写代码状态——语法点从来不是单独出现的。1.2 环境搭建的合理顺序开始写代码之前先把环境理清楚。Python官网下载安装包版本建议3.10以上我用的3.11。Windows安装时最容易被忽略的一步是勾选“Add Python to PATH”不勾的话后续在命令行里敲python会提示找不到命令。装完验证很简单打开终端输入python --version正常打印版本号就没问题。编辑器这块我推荐VS Code配Python插件打开一个py文件右下角选解释器再把终端集成打开就能直接敲python test.py运行。装完顺手用一条命令把pip升级到最新python -m pip install --upgrade pip。后面要装第三方库都是从这一步开始的。提示如果你打算久用建议再学会用venv建虚拟环境。每个项目一套独立环境避免不同项目依赖的库版本互相打架。这个习惯早养成早受益。1.3 用一个小脚本做环境冒烟测试环境配没配好写个几行的小脚本测一下就知道。我每次都会跑下面这段import sys print(Python版本:, sys.version) print(当前文件:, __file__) def status(flag): if flag: return OK return FAIL print(环境检查:, status(True))这段虽然短但已经把sys模块、函数定义、条件判断、字符串打印全过了一遍。能正常输出就没问题。你要是连文件都保存不了、找不到文件路径多半是没搞清楚当前工作目录这个在文件操作那节专门说。2. 控制结构不是背语法是练判断2.1 if/elif/else的条件判断条件判断是程序里的“岔路口”。最基础的写法不用多说但有三个细节我见过无数人栽跟头。第一个是缩进。Python靠缩进区分代码块同一个分支里的代码必须缩进一致混用Tab和空格直接报语法错误。编辑器统一设成“Tab转空格”最好再打开“显示空白字符”一眼能看出问题。第二个是条件的返回值。if后面接的表达式判断结果是True或False。很多人写判断时习惯跟True比较比如if flag True其实直接写if flag更Pythonic。更隐蔽的坑是判断空值判断一个列表是否为空应该写if items或if not items。列表为空时bool([])是False这个特性在判断文件有没有读进来时特别常用。第三个是分支顺序。elif是按顺序从上往下匹配的命中一个就不再往后走。所以范围判断要先写窄的条件再写宽的条件。举个简单例子score 83 if score 60: print(及格) elif score 85: print(优秀)这段代码跑出来的结果是“及格”因为83先满足了score 60。如果你想让85分以上先判为“优秀”就必须把conditions的顺序反过来把更严格的判断写在前面if score 85: print(优秀) elif score 60: print(及格) else: print(不及格)顺序错、逻辑错这是if判断里最经典也最容易忽略的问题。边界值要拿纸笔捋清楚特别是小于、小于等于、大于等于这些边界条件。测试时把极端值都试一遍84、85、86、59、60、61跑一遍心里就有数。2.2 for循环遍历一切可迭代对象for循环在Python里是真正的主力。它不像C语言那样有个计数器而是“从头到尾依次取每个元素”。这个设计让代码简洁很多但也让很多从其他语言转过来的人不习惯。遍历一个列表files [report.pdf, data.csv, notes.txt] for f in files: print(处理文件:, f)遍历一个字符串for ch in python: print(ch.upper())如果确实需要下标用range配合len或者直接用enumeratefor i, f in enumerate(files): print(i, f)enumerate比range(len(...))清晰得多能少写一行就少一行。range的用法也值得单独说range(stop)生成0到stop-1range(start, stop)生成start到stop-1range(start, stop, step)可以控制步长。倒着遍历就写range(5, 0, -1)结果是5、4、3、2、1。for循环还有一个容易忽略的else子句。循环正常结束没被break打断时else块会执行。这个特性在“找没找到东西”的场景很合适nums [3, 7, 9, 15] for n in nums: if n % 2 0: print(找到偶数:, n) break else: print(列表里没有偶数)这个else不是必须用但一旦用好了比设置一个found标志位再在循环后判断要清爽。2.3 while循环什么时候应该用while循环适合“不知道要循环多少次”的场景。比如重试连接、等待用户输入、持续处理直到满足条件。语法是while后接条件条件为True就继续条件为False就退出。一个最典型的例子读配置文件的时候一行一行读到文件末尾为止。这里甚至可以while配readline()来用但后面讲文件时你会看到for逐行读更方便。while容易出的问题只有一个——死循环。条件永远为真程序就永远跑下去。我见过最夸张的一次是同事写while True忘写break跑了一整晚。所以用while有两个习惯必须养成一是确保循环体内某个变量在朝条件结束的方向变化二是在必要的位置设置最大循环次数或者超时时间。比如写一个带次数限制的重试逻辑max_attempts 3 attempt 0 while attempt max_attempts: try: # 假设这里做某个可能会失败的操作 result 10 / 0 # 故意触发异常 break except ZeroDivisionError as e: attempt 1 print(f第{attempt}次尝试失败) if attempt max_attempts: raise RuntimeError(重试次数用尽)2.4 break、continue与循环嵌套的配合break是跳出整个循环continue是跳过当前这次进入下一次。这两个词一学就会但组合起来需要想清楚逻辑。嵌套循环时break只跳出最里面那层。想一次跳出多层一个简单的做法是设置一个标志变量found False for i in range(5): for j in range(5): if i j 7: print(i, j) found True break if found: breakPython没有goto这个写法是最直观的兼容方案。另一种更Pythonic的方案是把双层循环提取成函数用return直接从内层返回。后面讲文件操作时我会用一个真实的例子演示这个思路。3. 文件操作从open到with再到write3.1 open()函数的模式和参数文件操作的核心是open()函数。它的第一个参数是文件路径第二个参数是模式默认是r只读。模式这个东西必须背熟因为你用错了轻则报错重则把文件内容覆盖掉。模式含义常见用途r只读文件必须存在读取文本文件w写入存在则清空不存在则创建覆盖写入a追加在文件末尾续写日志追加rb / wb二进制读/写图片、压缩包等r读写文件必须存在需要同时读写我见过最惨的一次事故就是有人想把一行内容追加到文件结果用了w整个文件被清空了里面几千行数据全没了。追加一定要用a写覆盖一定要确认再动手。所以我现在写代码的习惯是任何使用w模式的地方先明确注释“这个文件会被覆盖”。open()的另一个重要参数是encoding。文本文件如果不指定encodingsWindows上默认是GBK而Python的源代码和大多数文本文件用的是UTF-8。不指定编码就读取最常见的报错就是UnicodeDecodeError。我写任何open()都会带上encodingutf-8宁可写冗余不要出洋相。3.2 用with语句管理文件很多人学open()的时候教程会教完事后调用close()。但close()经常会忘或者代码中间抛了异常close()根本执行不到文件句柄被占用后续再打开同一个文件就可能出问题。最稳妥的写法是用with语句with open(data.txt, r, encodingutf-8) as f: content f.read()with块结束时无论正常执行还是抛异常句柄都会自动关闭。这不只是语法糖它是资源管理的最佳实践。凡是Python内置的上下文管理器比如open()、threading.Lock、标准库里的很多资源类我都建议优先用with。3.3 读文件的三种姿势与选择读文件最常用的三种方法是read()、readline()、readlines()还有一个我特别想推荐的——直接for遍历文件对象。read()一次读取全部内容返回一个字符串。文件不大、内容在几十KB内随便用简单直接。readlines()一次读取所有行返回每一行组成的列表。处理小文件方便。但如果你读一个几个GB的大日志文件readlines()会把所有行都装进内存直接内存爆炸。最佳实践是逐行迭代把文件对象当迭代器用with open(access.log, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue # 处理这一行for循环会按行缓冲读取一次只在内存里保留一行内存占用小得多处理几GB的文件也不需要担心。这是处理日志文件、CSV文件、任何长文本文件的默认选择。3.4 写文件与缓冲机制写文件的几个常用的方法分别是write()、writelines()和一个print改写到文件的方式。write()和print()的差别在于print()会自动加换行符write()不会。如果你要往文件里写一行带换行的文本得自己补上换行符# 用 print 重定向到文件 with open(out.txt, a, encodingutf-8) as f: print(这是一行内容, filef) # 用 write 方法 with open(out2.txt, w, encodingutf-8) as f: f.write(第一行\n) f.write(第二行\n)要特别说明一下缓冲机制。写入操作不一定立刻写到磁盘Python默认有缓冲数据会先在内存中攒一段再写。如果程序正常结束或用with退出缓冲会刷新如果程序中途崩溃一部分写入可能丢失。不放心的时候可以调用flush()手动刷新缓冲。如果我需要批量写入比较多数据个人建议把内容先拼到一个列表里最后一次性写入比一次次调用write()快很多。3.5 目录遍历与磁盘信息文件操作不止于读写文件内容还涉及目录管理。os模块和pathlib模块是主力。我初学的时候一直用os.listdir()列目录直到接触到pathlib才觉得它更顺手from pathlib import Path folder Path(data) for item in folder.iterdir(): if item.is_file(): print(文件:, item.name, item.stat().st_size) elif item.is_dir(): print(目录:, item.name)如果你想递归遍历一个目录下所有子目录和文件用rglob更省事for p in Path(data).rglob(*.txt): print(p)这些文件系统操作在实验里并不会占很大的篇幅但你在做批量处理时非常有用。比如从几十个csv文件里挑出某天的记录、把指定类型的文件移动到备份目录、统计某个目录的总大小。这里想说一下在命令行里用df和du的常识df查看磁盘分区使用情况du查看目录占用的空间。Python没有直接复制这两个命令的实现但我们可以用shutil.disk_usage()来获取磁盘统计这是一个顶一个的功能。顺便提一个经常用的临时组合处理多个文件时先把目标文件过滤出来然后集中处理不要一边遍历目录一边修改目录结构那样很容易出问题。4. 综合案例日志文件错误统计工具4.1 需求拆解光讲语法没意思我们直接做一个案例写一个脚本统计指定日志文件里的ERROR数量把错误的级别和出现的次数聚合出来输出成一个结果文件。这个案例覆盖了控制结构与文件操作的核心内容。最终输入一个日志路径输出一个统计结果。我把需求拆成四个功能点打开日志文件逐行读取用条件判断识别ERROR行用字典结合循环结构统计不同错误模块的出现次数把统计结果写入新文件4.2 逐步写代码先写最里层的功能判断一行是否为ERROR行。真实日志格式通常是这样的2025-05-18 10:23:45 ERROR module_a: timeout after retries 2025-05-18 10:23:47 INFO module_b: request completed 2025-05-18 10:24:01 ERROR module_c: disk full我们可以把判断和统计功能拆成两个函数。先定义一个函数来判断这一行该归到哪个错误的类别——这里我们用“2025-05-18 10:23:45 ERROR module_a: timeout after retries”这种格式找到ERROR后面的第一个字段作为错误模块名再把整行内容保留下来便于之后定位问题。用split()切分字符串即可。如果一行里没有“ERROR”跳过不处理跳过时不统计。这用到continue。然后统计的核心是字典from pathlib import Path def extract_module(line): parts line.split() for i, p in enumerate(parts): if p ERROR: if i 1 len(parts): return parts[i 1] return unknown def count_errors(log_path): counter {} with open(log_path, r, encodingutf-8) as f: for line in f: line line.strip() if ERROR not in line: continue module extract_module(line) if module not in counter: counter[module] 0 counter[module] 1 return counter def write_report(counter, output_path): with open(output_path, w, encodingutf-8) as f: f.write(错误统计报告\n) f.write( * 30 \n) for module, count in sorted(counter.items(), keylambda x: x[1], reverseTrue): f.write(f{module}: {count}\n) log_file app.log report_file report.txt counter count_errors(log_file) write_report(counter, report_file) print(统计完成结果已写入, report_file)这段代码里count_errors用for循环逐行遍历用if和continue把非ERROR行过滤掉extract_module用遍历加下标判断提取模块名write_report用sorted按数量排序再逐一写入结果文件。控制结构和文件操作在这儿完全是自然交织的状态。4.3 边界情况处理与多文件版本这个脚本看起来简单实际一跑就知道边界情况一堆文件不存在、日志是空的、某一行的ERROR后面没有模块名、目录权限不对。我建议在函数入口加上最小防御比如文件不存在时给出友好提示而不是让traceback铺满屏幕。再扩展一点——如果日志文件是按天拆分的放在logs目录下你可以遍历所有.log文件逐一统计再汇总结果from pathlib import Path log_dir Path(logs) total_counter {} for log_file in log_dir.glob(*.log): counter count_errors(log_file) for module, count in counter.items(): total_counter[module] total_counter.get(module, 0) count write_report(total_counter, total_report.txt)这里get(module, 0)是字典计数时的惯用技巧比先判断再赋值更简洁。多文件批量处理覆盖了目录遍历和聚合逻辑这也是平时办公场景中特别实用的姿势。要把上面的脚本按第几天的要求改成定时跑、跑完把结果文件移动到另一个归档目录只要再补几行目录移动的代码就行。5. Python控制流与文件操作的实际项目应用场景上面这些内容看着基础但控制结构和文件操作几乎渗透到底层代码里。爬虫要从网页上抓文本、清洗数据、保存到CSV要判断状态码、遍历列表要循环分页获取。自动化办公要遍历一批Excel文件要么用pandas读表要么写条件分支对不同类型的数据做不同处理。分析量化交易的K线要用循环遍历每天的数据条件判断开仓还是关仓。甚至连运维脚本排查磁盘空间、检查目录大小也是先遍历目录再判断文件大小。所以写实验报告的时候如果只写“我学会了if和for”那太浪费了。更值得写的是你用它们解决了什么具体问题。比如你写了日志分析脚本、你批量把几万个文件名规范了、你把一个CSV里超过某个阈值的数据过滤出来了。这些东西写在报告里才是真正体现能力的地方。6. 常见问题排查与避坑实录6.1 环境与安装相关的坑搜索热词里有一大堆是关于python安装的。我在网上见过不少人卡在“python不是内部或外部命令”。这个问题几乎都是在Windows上安装时没有勾选Add to PATH导致的。解决办法有两个一个是重装Python时勾选另一个是手动把Python安装目录加入环境变量。装完之后记得重新开一个终端窗口环境变量才生效。还有一批人是装第三方库失败提示pip不是内部命令或者网络超时。这个问题的排查顺序是先确认python能跑再确认pip能跑最后确认能上网。如果pip超时最简单的处理是换国内镜像源命令是pip install 包名 -i 镜像地址。这里不过多展开但镜像地址搜索引擎一搜就有。6.2 语法错误和缩进错误新手报SyntaxError和IndentationError占全部错误的一大半。SyntaxError常见的几个来源条件或循环语句末尾忘写冒号、括号不配对、用了中文标点。建议编辑器里把字体调大一点中文输入法和英文输入法切错时候那个逗号“”和“,”肉眼基本看不出区别但解释器一秒钟就能识别出来。IndentationError多半是混用了Tab和空格。把编辑器的缩进统一设置成4个空格并启用“按Tab插入空格”几乎可以根治这个问题。还有不同代码块之间缩进层级不要错乱。Python的缩进就是语法的一部分不仅仅是代码风格好看的问题。6.3 文件操作相关的坑文件操作的坑明显比语法错误更隐蔽。第一个坑是UnicodeDecodeError。读取文本文件时不指定encoding在Windows上会默认用GBK遇到UTF-8编码的文件就会报错。这是实验中出现频率最高的运行时异常。我前面讲过凡是读取文本文件一律显式指定encodingutf-8。如果你不确定源文件编码可以在打开时用errorsreplace做容错处理或者用chardet识编码但最靠谱的还是统一文件编码。第二个坑是FileNotFoundError。一般在Windows上你双击打开的py文件所在目录和运行时的当前工作目录未必是同一个。比如在D盘根目录运行python C:\project\code\test.py当前目录是D:\而你的相对路径data.txt在C:\project\code\下面直接读data.txt就找不到了。解决办法是不要依赖当前工作目录用__file__所在的目录来拼路径from pathlib import Path BASE_DIR Path(__file__).parent file_path BASE_DIR / data.txt这个写法能保证无论从哪里运行脚本文件路径都不会错。凡是我写文件处理脚本都会用这一招。第三个坑是写入意外的空白行。在Windows上文本模式的write写“\n”时会自动翻译成“\r\n”。如果你用二进制模式wb写入带“\r\n”的内容又可能会多出空行。这种情况只要统一用文本模式别混用二进制模式就不会出问题。第四个坑是权限。Windows会提示需要TrustedInstaller权限才能修改某些文件比如系统目录下的文件。遇到这种情况别在那个目录下折腾把你的数据文件放到自己的项目目录或者管理员权限运行终端。更稳的做法是程序先判断目标文件可写不可写给用户一个清晰的错误提示。第五个坑是文件被占用。Windows上如果一个文件已经被别的程序打开再次写入就会报PermissionError。测试时最容易踩到你用记事本打开了out.txt又用Python程序去写同一个文件。遇到这个错误先关掉所有编辑器窗口再跑程序这比改代码更管用。6.4 控制逻辑相关的坑控制结构的坑说两个最高频的。for循环中修改正在遍历的列表。比如你想把列表里所有小于0的元素删除nums [1, -2, 3, -4, 5] for n in nums: if n 0: nums.remove(n)跑出来的结果会是[1, 3, -4, 5]-4还在。原因是在遍历过程中删除元素元素的索引会向前移动导致漏掉紧接着的元素。正确做法是遍历列表的副本或者用列表推导式生成一个新列表nums [n for n in nums if n 0]第二种做法更Pythonic同时不修改正在遍历的列表。另一个坑是在while循环里漏掉循环变量的更新语句。比如i 0 while i 10: print(i) # 忘了写 i 1这段代码会在完成后无限循环如果条件初期是成立的很容易让程序CPU占用率飙高。尤其是在实验或作业里敲完while循环一定检查有没有让条件变化的语句或者给while循环加个次数上限。生产级代码建议加入计数器变量。7. 实验验收与日常练习建议实验报告写完之后你可以再做一个自测以此检验是不是真的掌握了核心内容。我整理了一份清单每个问题都能在10分钟内完成写一个函数接收一个文件路径返回这个文件的字符数、行数、单词数写一个脚本把当前目录下所有.txt文件的后缀改成.log写一个循环找出1到100之间既能被3整除又能被5整除的数写入result.txt写一个交互式程序让用户循环输入分数输入q退出退出时输出最高分写一个脚本读取一个CSV文件把某列大于100的行输出到另一个文件这五个小任务做完基本上这份实验的九成内容你已经懂了。如果哪一项卡住了回去看对应的章节就行。再强调一个习惯不要只在编辑器里看代码要亲手敲。复制粘贴和手敲的区别就像看菜谱和自己做菜。错误是你最好的老师每撞上一次UnicodeDecodeError你对encoding参数的记忆就深一分。实验报告写上一句“踩过什么坑、怎么解决的”比干巴巴写“本实验掌握了什么”更有价值。我自己带过不少新人发现控制结构那章能通关的人后面学函数、学面向对象都不费劲文件操作熟练的人独立处理真实数据的底子就扎实了。这个实验看起来平平无奇但它在整个Python学习路径上的地位真的比很多花哨的高级主题要重要得多。把这些基础打好后面学pandas处理表格、学requests抓网页、学fastapi搭接口都不会觉得根基不稳。最后再说一个我这几年用得最多的小技巧写文件操作前先想清楚“如果这个文件不存在会怎样”“如果格式不对会怎样”。把边界情况放在一开始设计里后面你踩的坑能少一半。这个习惯远比记住某个API参数重要。