Python for循环与数字列表:range、遍历与统计入门 从“抄一遍”到“想明白”Python 第六课里的 for 循环和数字列表带过几轮零基础学员之后我发现一个规律前五课大家学得都挺顺变量、字符串、列表、条件判断照着敲都能跑出结果可一到第六课Python 中的 for 循环及数字列表进度明显开始分化。有人当天就能用循环把一整个班的信息统计出来有人盯着for i in range(5):反复问“这个 i 到底从哪来、到哪去”。这不是悟性问题而是这一课第一次把“写一行执行一行”的线性思维切换成了“让机器替我重复”的批处理思维脑子没转过来代码怎么写都别扭。for 循环在 Python 里的地位相当于手工活里的“模具”你把一次动作定义好剩下的事情交给它重复执行。而数字列表是这套模具最顺手的原料——一堆规整的整数、一串待统计的数值、一段需要按序号处理的区间。第六课把这两样东西放在一起讲其实是在搭一条最小的自动化流水线用range()造数据用for走一遍数据用sum()、min()、max()、len()出结果。这篇内容适合三类人看刚学完列表和条件判断、准备啃循环的新手自学时被缩进和边界值反复折磨的转行者以及需要带别人入门、想找一套讲得清楚又不啰嗦的讲法的朋友。下面我按自己实际讲课和踩坑的顺序来展开代码都能直接复制运行参数怎么来的也会一步步推导给你看。1. 为什么第六课要卡在 for 循环这个位置1.1 课程的编排顺序其实藏着一套依赖关系先说清楚一件事教材把 for 循环放在第六课不是随意排的而是前面的知识点已经凑齐了触发循环的三个前置条件。变量教会你给数据起名字列表教会你把一堆数据装进一个容器条件判断教会程序根据情况走不同分支。这三样东西合起来才构成 for 循环的最小运行环境——你得有个容器可以被遍历得有个变量去接住每次取出来的元素还得有判断逻辑来决定循环体里做什么。如果顺序反过来先讲循环再讲列表学员会遇到一个很尴尬的局面for后面没东西可遍历只能拿字符串凑数讲不深也用不上。等到学列表的时候又要回头补循环知识是碎的。所以这一课真正的定位是“第一次让程序处理批量数据”理解了这一点后面学字典遍历、文件逐行读取、爬虫翻页全都是同一个套路换个容器而已。我个人的判断标准很朴素一个人是不是真的过了第六课这一关不看他会不会背语法而是看他遇到“要对一百条数据做同一件事”时第一反应是写一百行还是写一个循环。前者说明还在用电子表格的思维写代码后者才算入门。1.2 for 循环和 while 循环到底该选哪个新手最常见的困惑是既然有了 while为什么还要 for这两个东西表面都能重复执行骨子里的适用场景完全不同。我的经验是把它们分成两类知道要跑多少次用 for只知道什么时候该停用 while。遍历一个列表、生成 1 到 100 的数、把字符串每个字符过一遍次数是确定的或者由容器长度决定的这种活交给 for代码短、不容易写出死循环。而“一直读取输入直到用户输入 q 为止”“不断重试直到成功”这类场景你事先不知道会转几圈只能靠条件卡住那就用 while。还有一种情况要特别提醒很多人用 while 写计数循环写成这样——i 0然后while i 10:循环体末尾手动i 1。这段代码本身没错但只要某条分支提前continue之前忘了加一立刻死循环。for 循环天生把这个计数器交给你管for i in range(10):里i的推进由解释器负责你根本没机会写漏。这就是我为什么坚持让学员在能确定次数的情况下优先用 for——少一个可以写错的地方就少一个半夜排查 bug 的理由。1.3 数字列表为什么非要和循环绑在一起讲单独讲列表学员只能做增删改查单独讲循环学员只能打印字符串。两者一结合才出现真正有价值的东西统计。求总分、算平均分、找最高分、数有多少个及格的——这些操作在现实中出现的频率远高于“把列表排序打印出来”。而且数字列表有个天然优势它规整。list(range(1, 11))出来就是十个连续的整数你在验证循环逻辑对不对的时候心里能默算出正确答案。如果换成字符串或者混合类型一旦输出不对你都不确定是循环写错了还是数据本身有问题。用规整的数据调试逻辑用真实的数据验证功能这个习惯我从第六课就开始让学员建立。2. for 循环的机制拆解把它当成“挨个取件”2.1 遍历的本质是“取出一个、执行一遍、再取下一个”理解 for 循环最好的类比是快递驿站取件。货架上一排包裹这就是列表工作人员从第一个开始取一个、扫码登记执行循环体、放一边再取下一个直到货架空掉为止。for关键字后面那个变量就是“手上正拿着的那个包裹”。fruits [苹果, 香蕉, 橙子] for fruit in fruits: print(f正在处理{fruit})输出三行顺序和列表里一致。这里有个新手常问的问题fruit这个名字是谁规定的答案是随便起它只是临时的名字叫x也行、叫item也行。但我建议起有意义的名字——fruit一看就知道装的是水果x过三天你自己都想不起来它代表什么。变量名是写给人看的不是写给解释器看的这句话我在课上重复过很多次。再补一个容易被忽略的细节循环结束后fruit这个变量并不会消失它还留在内存里值是最后一个被取出的元素。这在 Python 里是允许的虽然很多其他语言里循环变量出了循环就作废。所以千万别在循环后面复用同名变量尤其是你打算拿它存别的东西的时候很容易出莫名其妙的错。2.2 冒号和缩进Python 用空白字符表达结构第一次看到 Python 代码的人都会愣一下怎么没有大括号答案是 Python 用缩进来表达“谁属于谁”。冒号:宣告“下面开始一个代码块”紧接着缩进的部分就是块内部缩进退回来就代表块结束。total 0 for n in [1, 2, 3, 4]: total total n print(当前累计, total) print(循环结束总分是, total)前两条print和total都在缩进内每轮都执行最后一条退回到顶格循环全部跑完才执行一次。注意缩进必须统一全程 4 个空格最稳妥。全空格和全 Tab 混着用会直接报TabError: inconsistent use of tabs and spaces in indentation这个报错在编辑器里肉眼几乎看不出来尤其是从网页上复制代码过来的时候最常见的坑就是这个。我一般让学员把编辑器的“显示空白字符”打开Tab 会显示成箭头空格显示成点一眼就能看出混没混。另外忘了写冒号报的是SyntaxError: expected :而写了冒号没缩进报的是IndentationError: expected an indented block这两种错误信息都算友好照着改就行别慌。2.3 break、continue 和 for-else 这三件工具循环体里偶尔会出现“提前收工”或者“跳过这一轮”的需求Python 给了两个关键字。break是直接砸门走人整个循环立刻结束后面的元素一个都不看了。典型场景是查找列表里找到目标值就可以停了没必要把剩下的全走一遍。continue是这一轮不干了但还没下班直接跳到下一轮从头开始。典型场景是过滤遇到偶数就跳过只处理奇数。numbers [3, 8, 15, 22, 7] for n in numbers: if n % 2 0: continue # 偶数跳过不往下执行 if n 20: break # 超过 20 就整体收工 print(处理奇数, n)至于for...else是 Python 特有的一种写法循环正常跑完没被 break 打断就执行 else 块被 break 打断就不执行。新手看到会觉得别扭硬记语法没什么意义等真的遇到“找了一圈没找到就报个提示”的需求时再回来看一下就懂了。3. range() 的三种形态与边界值推导3.1 三种写法对应三种需求range()是造数字序列的工厂一共三种调用方式都是从简单到复杂排列的。写法含义示例结果典型用途range(stop)从 0 开始到 stop 前停range(5)→ 0,1,2,3,4按序号遍历、固定次数重复range(start, stop)从 start 开始到 stop 前停range(1, 6)→ 1,2,3,4,5从 1 开始计数range(start, stop, step)加步长range(0, 10, 3)→ 0,3,6,9隔行处理、倒序注意range()返回的不是列表而是一个“惰性”的对象你直接print(range(5))看到的会是range(0, 5)而不是[0, 1, 2, 3, 4]。想看具体内容得用list()转一下。这个设计是为了省内存——就算写range(100000000)它也只是一句话不会真在内存里铺一亿个数。3.2 为什么 range(1, 5) 只到 4“左闭右开”是新手第一周最容易翻车的地方。range(1, 5)生成的是 1、2、3、4不含 5。为什么这么设计一句话解释便于用“长度”直接当右边界。如果我要遍历一个长度是 5 的列表直接写range(len(lst))就是 0 到 4正好是合法索引不用再减一。要是右边界包含所有涉及索引的循环都得写len(lst) - 1多一个减号就多一处写错的可能。实操中最容易出错的场景是“求 1 到 100 的和”。很多人直觉写range(1, 100)结果少加了 100答案是 4950 而不是 5050差了整整一百。验证方法很简单拿小数据试list(range(1, 5))跑一下看到没有 5自然就知道要写 101 了。提示凡是写range()先用小范围跑一遍list()看输出确认边界对了再放到大循环里。这个习惯能省掉大量“结果差一点点”的排查时间。3.3 步长为负和“浮点数不可用”这两条铁律倒序生成数字得靠负步长range(10, 0, -1)出来是 10 到 1。这里有两个坑要提前说清楚。第一步长为负时起始值必须大于结束值否则什么都生成不出来而且是静默返回空不报错。range(1, 10, -1)的结果是空序列循环体会一次都不执行。新手在这里经常懵明明写了循环怎么一行输出都没有原因就在这里。第二range()只接受整数写range(0, 1, 0.1)会直接抛TypeError。要生成 0 到 1 之间步长 0.1 的序列常见做法是先算次数[i / 10 for i in range(0, 11)]或者用整数序号乘以步长换算。这个知识点在第六课讲不一定用得上但提前埋一句等后面遇到画图坐标、物理模拟的时候就知道有多重要了。关于“range 能生成多少个元素”给个可以心算的公式步长为正时元素个数 (stop - start step - 1) // step。比如range(1, 10, 2)(10 - 1 2 - 1) // 2 5实际就是 1、3、5、7、9五个对得上。这个公式在预估循环次数、判断性能时挺有用比如我要循环一百万次先算一下大概耗时再决定要不要优化。4. 数字列表的生成、统计与推导式4.1 用 list() 配合 range() 一次造一整套数据list(range(n))是最常用的造数方式一行就能拿到 0 到 n-1 的列表。nums list(range(1, 11)) print(nums) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]另一种写法是用循环逐个追加squares [] for n in range(1, 11): squares.append(n ** 2) print(squares)这两种方式的选择上我一般建议学员先老老实实写第二种。为什么因为 append 的写法暴露了“先建空列表、再往里塞”的过程这个模式在真实开发里出现频率极高后面处理接口返回的数据、拼接路径、收集异常记录全是这个套路。等你把它写熟了再换成推导式只是省几行字的问题不是换思路的问题。4.2 sum、min、max、len 的用法和空列表陷阱Python 内置了四个统计函数配合数字列表非常顺手。函数作用空列表时的表现sum(lst)求和返回 0不报错len(lst)元素个数返回 0min(lst)最小值抛ValueErrormax(lst)最大值抛ValueError这个差异必须记牢sum([])是 0因为“没有东西加起来等于 0”符合数学直觉但min([])会直接报ValueError: min() arg is an empty sequence因为“空集合里的最小值是多少”这个问题本身没有答案。所以在算平均值的时候如果分母是len(lst)空列表会导致除零错误ZeroDivisionError得先判断。scores [88, 92, 79, 95, 61] if scores: # 空列表为假直接跳过 avg sum(scores) / len(scores) print(f平均分{avg:.2f}) print(f最高分{max(scores)}最低分{min(scores)}) else: print(没有成绩数据)if scores:这种写法比if len(scores) 0:更地道空列表、空字符串、空字典在条件判断里都是假值用不着显式比长度。4.3 列表推导式把三行压成一行的正确姿势推导式的基本形式是[表达式 for 变量 in 可迭代对象]后面还能挂if做过滤。# 求平方 squares [n ** 2 for n in range(1, 11)] # 只要偶数 evens [n for n in range(1, 21) if n % 2 0] # 带条件的三元表达式 labels [及格 if n 60 else 不及格 for n in [58, 72, 91, 45]]注意推导式能塞两个 for 甚至三个嵌套起来可读性会断崖式下跌。我的原则是——一眼看不明白的推导式一律改回普通 for 循环。代码是给人维护的省三行换来同事读十分钟不划算。另外一个容易忽略的点推导式里的变量作用域是独立的循环结束后不会污染外部同名变量。这跟普通 for 循环不一样普通循环的变量会留下来。这个小差异在调试时偶尔会让人困惑遇到了知道有这么回事就行。5. 实操用循环做几个能直接跑的小工具5.1 成绩统计从原始数据到一张汇总表这是我在课上必做的第一个完整练习因为它把循环、条件、统计函数全串起来了。scores [78, 92, 65, 88, 100, 54, 73, 89] total 0 pass_count 0 excellent [] for s in scores: total s if s 60: pass_count 1 if s 90: excellent.append(s) avg total / len(scores) print(f人数{len(scores)}) print(f总分{total}) print(f平均分{avg:.1f}) print(f及格人数{pass_count}及格率{pass_count / len(scores):.1%}) print(f90 分以上{excellent})这段代码里其实藏了两个可以复用的模式。一个是累加器先给total一个初始值 0循环里不断往上加最后得到总量。另一个是计数器pass_count同样是先归零再加一。这两个模式几乎出现在所有实际项目里认清它们比背语法重要得多。顺带说一个常见错误total的初始值写成total 0是对的但如果写total 1最后结果就多一如果忘了初始化直接total s会报NameError: name total is not defined。累加器的初始值一定是“加法的单位元”也就是 0要是做累乘初始值就是 1。5.2 嵌套循环打印图形与乘法表嵌套循环是第六课的难点因为一旦两层套起来执行次数的增长是指数级的外层转 3 次、内层转 4 次总共就是 12 次。新手在这里最容易搞混“谁在外谁在内”。# 打印 5 层三角形 for i in range(1, 6): print(* * i) # 九九乘法表经典嵌套 for i in range(1, 10): for j in range(1, i 1): print(f{j}×{i}{i*j}, end\t) print() # 内层结束换行判断哪个循环在外层的技巧看谁决定行谁就放外面。乘法表是第 i 行有 i 列行数由 i 控制所以 i 在外层内层的 j 从 1 走到 i自然写成range(1, i 1)。这个i 1就是左闭右开的直接应用因为要包含 i 本身所以右边界得加一。print()里的end\t是把默认的换行改成制表符让同一行的几项横向排列内层循环结束后单独调一次print()相当于按一下回车。这两个细节不写乘法表会变成一竖列完全看不出结构。5.3 用切片配合循环做批量处理列表切片在第五课应该讲过了但和循环结合时才真正显出威力。比较典型的需求是“每 3 条数据分一组”。data list(range(1, 21)) # 1 到 20 group_size 3 for start in range(0, len(data), group_size): group data[start:start group_size] print(f第 {start // group_size 1} 组{group})关键在于range(0, len(data), group_size)的步长用的是组大小start依次是 0、3、6、9……切片的右边界写start group_size超出末尾时 Python 会自动截断不报错。这个技巧在批量提交数据、分页展示时非常实用比写一堆 if 判断边界省事多了。不过要叮嘱一句切片越界是安全的但索引越界不安全。data[100]会直接抛IndexError: list index out of range而data[100:200]只是返回空列表。分页那种场景务必用切片别用索引硬取。6. 常见问题与排查速查表6.1 循环里改列表最隐蔽的一类 bug有一类问题我是反复强调的不要在遍历列表的同时增删元素。nums [1, 2, 2, 3, 2, 4] for n in nums: if n 2: nums.remove(n) print(nums) # 结果 [1, 2, 3, 4]还剩一个 2明明想删干净结果剩了一个而且不报错。原因是 for 循环靠内部索引推进删掉一个元素后后面的元素整体前移索引却继续向后走于是刚好跳过了一个。这个现象叫“边遍历边修改”几乎是每个人都会踩一次的坑。正确做法有两个一是遍历副本for n in nums[:]改的是原列表、动的是副本二是用推导式重建nums [n for n in nums if n ! 2]。我推荐第二种语义清晰也不容易想错。6.2 排查速查表报错或现象可能原因处理方式SyntaxError: expected :for 那行末尾漏冒号在in xxx后面补冒号IndentationError: expected an indented block冒号后没缩进循环体统一缩进 4 空格TabError: inconsistent use of tabs and spacesTab 与空格混用全选后统一转空格或开启空白字符显示循环一次没执行range()边界反了或步长方向错用list()打印序列确认结果少一个数range()右边界没加一记住左闭右开需要包含就 1NameError: name total is not defined累加器没初始化循环前写total 0ZeroDivisionError空列表求平均求平均前用if lst:判断死循环循环体内手动改了i或误用 while 忘自增检查是否在 for 里手动改循环变量输出全是最后一项print被写到了循环外面检查缩进层级6.3 几处容易忽略的小习惯第一循环变量别手动改。for i in range(10):里面写i 5虽然合法但下一轮开始 i 又会被range重新赋值你的修改等于白写只会让读代码的人困惑。第二range对象可以重复遍历。这点很多人不知道以为自己得先转成列表。其实r range(5)之后跑两次 for 都能正常拿到 0 到 4因为它每次遍历都重新生成不消耗自身。反而是生成器对象遍历一次就空了那是后面的内容。第三需要下标的时候别用range(len(lst))硬取。能用enumerate()就用虽然第六课可能还没讲但知道有这么个东西等学到时会恍然大悟。第四代码里出现魔法数字是大忌。写range(1, 100)的时候最好中间变量先赋值student_count 99再写range(1, student_count 1)。三个月后你自己看到range(1, 100)根本想不起来为什么是 100。我个人在这几轮课里摸出来的几点体会语言层面到这里已经能读懂大部分入门代码了真正卡住人的从来不是语法而是“我要用循环干什么”这个前置判断。每次布置练习我都会强调先别急着打开编辑器拿张纸把“输入是什么、循环转几圈、每圈做什么、最后输出什么”四件事写下来。写清楚了再敲代码改bug的时间至少能砍一半。还有一个我坚持了很多年的做法每写完一个循环一定先拿 3 到 5 条的小数据跑一遍人工核对输出对不对确认逻辑没问题了再换成完整数据。用真实的大数据集直接调试出了错你都不知道是第几轮偏的只能靠打满屏的 print 硬找。小数据先过一遍这个习惯能帮你在后面学函数、学数据处理的时候省下大量时间。最后补一句关于range边界的小技巧我自己的习惯是每次写range紧接着就在旁边写一行注释标出实际范围比如range(1, 11) # 1 到 10。这条注释看着多余但半年后回来改代码你绝对会因为当初多写了这一行而感谢自己。