Python列表入门:从批量数据处理到增删改查实战指南

📅 发布时间:2026/10/11 4:55:18
Python列表入门:从批量数据处理到增删改查实战指南
最开始接触列表的时候我有一个很直观的感受前面几节课学的变量、字符串、条件判断、循环都是在和“单个数据”打交道比如一个成绩、一个名字、一个开关状态。等到了列表这一节手里的数据终于从“一个”变成了“一批”整个写代码的感觉都不一样了。这一课是 Python 入门系列里的第八讲放在这个位置其实很讲究。前面的基础语法攒够了列表又刚好是通向“批量处理”的第一扇门。无论你后面想做数据分析、写自动化脚本还是搞 Web 后端列表都是绕不开的一块基石。这篇内容没有废话全是我带入门同学时实实在在讲过的思路、写过的例子和踩过的坑照着一步步来你就能把列表这块地基打牢。1. 列表到底是什么从“一个变量存一个值”说起1.1 先解决一个朴素问题为什么需要列表回想一下前几课我们处理数据都是这样定义一个变量存一个值。假如现在有三科成绩你可以写score1 85 score2 92 score3 78三个变量勉强还能凑合那如果是三十科成绩呢总不能 score1 写到 score30。更要命的是实际场景中成绩的数量往往是不固定的——今天考三科下周可能考五科你总不能每次都重新改代码。这个时候你就需要一个“能装任意多个值”的容器Python 里最常用的容器就是列表。列表的意义远不止省掉命名的麻烦。没有列表循环就是个空架子。你把成绩放进列表之后一行sum(scores) / len(scores)就能算出平均分用一个for循环就能把所有成绩遍历出来。很多其他语言里的数组是定长的存什么类型也得提前定死Python 的列表则随意得多——长度可以随时变里面可以混着放整数、小数、字符串甚至可以嵌套另一个列表。我常跟学员打一个比方列表就像是家里玄关处的鞋柜每一格可以放一双鞋一个元素格数不用提前定死今天加一双就多占一格不要了抽出来也不影响其他位置。一格放运动鞋另一格放靴子完全没有问题。把“列表”这个概念在脑子里落成这样具体的画面后面学增删改查就自然多了。1.2 可变与有序两个核心身份列表有两个标签理解它们后面才不会迷路有序、可变。有序指的是元素有明确的位置顺序。什么意思同样是[Python, Java, Go]和[Go, Java, Python]只是元素一样但顺序不同就是两个不同的列表。每个元素都有一个数字编号叫做索引Index。下标从 0 开始Python在第 0 位Java在第 1 位Go在第 2 位。很多新手不习惯“从 0 开始数”但这是编程语言的普遍规矩用几次就熟练了。可变意味着列表里的元素可以被原地修改你可以替换某个位置的元素可以在末尾追加新元素也可以把某个元素删掉。这一点和字符串正好相反。字符串是不可变对象你写s hello然后想s[0] HPython 会直接报错但列表lst[0] H一点问题都没有。为什么字符串要搞这么“死板”因为字符串经常被用作字典的键、作为配置参数到处传不可变能避免很多莫名其妙的副作用。而列表天生就是个“工作台”数据放上去之后要不断调整可变性正是它最大的优势。还有些同学会问“列表和数组到底什么区别”。严格来说Python 标准库里有个array模块后面做数值计算还会遇到 numpy 的 ndarray那些才是真正意义上的“数组”。但日常口语里大家说“数组”时多半指的就是列表。学习阶段不用纠结术语你只要知道列表是 Python 自带的最灵活的序列类型足够应付绝大多数场景。2. 列表创建与访问操作花几分钟就能看明白2.1 创建列表的几种常用姿势最直白的创建方式就是方括号加逗号分隔的元素fruits [苹果, 香蕉, 橘子] empty_list [] nums [1, 2, 3, 4, 5]空列表写作[]后面做循环填充数据时会经常用到。第二种方式是用list()构造函数。直接调用list()得到空列表如果传一个可迭代对象进去它会把里面的元素逐个取出来生成新列表。比如list(abc) # 结果是 [a, b, c] list(range(5)) # 结果是 [0, 1, 2, 3, 4]这个统一接口非常实用。后面你会碰到很多“可迭代对象”比如字符串、range 对象、文件行序列想一次性转成列表处理list()就是最省事的手段。第三种高频姿势是从字符串拆出来。处理输入时最常用的是split()方法line 3.14,2.71,1.41 nums_str line.split(,) print(nums_str) # [3.14, 2.71, 1.41]但是这里必须提醒一句split()返回的每个元素都是字符串。你后面如果要计算float(nums_str[0])才能参与数学运算直接拿3.14当数字用分分钟出 TypeError 或者比出匪夷所思的结果。检查数据类型的习惯最好从这一课就开始培养。第四种方式是程序设计中最常见的套路先建一个空列表再用循环把数据一个一个追加进去。比如逐步读取用户输入并保存或者从文件里逐行读取结果。这种做法需要先掌握append()方法我放到下一节详细讲。创建列表时还有一个新手特别容易掉进去的坑用乘法复制列表。lst [0] * 5确实会得到[0, 0, 0, 0, 0]因为整数是不可变对象复制多少份都互不干扰。但如果你用的是嵌套列表比如lst [[]] * 3表面上看是建了三个空列表实际上这三个元素指向的却是同一个空列表。你给其中一个追加数据另外两个也会一起变而且是“凭空”变出来排查起来极其隐蔽。遇到嵌套结构老老实实用列表推导式别图省事用乘法。2.2 索引、负索引和切片定位数据的三板斧知道列表元素有编号之后第一件事就是学会怎么定位。正索引从 0 开始范围是 0 到len(列表) - 1。len()是内建函数返回列表长度。看个例子lst [a, b, c] print(lst[0]) # a print(lst[2]) # c print(lst[3]) # 报错 IndexError第三个会爆IndexError。原因很简单列表只有 0、1、2 三个位置你偏偏要第 4 个。这是入门阶段最常见的报错之一。遇到这个问题先别急着挠头打印一个len(lst)看看范围基本就明白怎么回事了。负索引是从末尾往前数的便捷方式。lst[-1]是最后一个元素lst[-2]是倒数第二个。把负索引想象成从队尾往回数位置第 -1 个就是队尾。处理“最后几个元素”的时候特别好用尤其是不知道列表具体有多长的时候。切片是取子列表的利器语法是lst[start:end:step]。切出来的范围是“前闭后开”包含start这个位置但不包含end这个位置。比如lst [10, 20, 30, 40, 50] print(lst[1:3]) # [20, 30]注意没有 40 print(lst[:2]) # [10, 20]从头开始可以省略 start print(lst[3:]) # [40, 50]一直取到结尾可以省略 end print(lst[::2]) # [10, 30, 50]步长为 2 print(lst[::-1]) # [50, 40, 30, 20, 10]倒序生成新列表切片有个重要特性它永远返回一个全新的列表并不改动原列表。想保留原始数据做对比分析的时候切片就是个天然的备份工具。但注意切片不只是用来“读”的它也能用于赋值比如lst[1:3] [100, 200]可以批量替换这一段位置的元素。这是列表可变性的直接体现后面实战会用到。2.3 修改元素的细节赋值与批量替换修改单个元素极其简单——只要下标不越界直接赋值就行fruits [苹果, 香蕉, 橘子] fruits[0] 西瓜 print(fruits) # [西瓜, 香蕉, 橘子]字符串上这么干会报错列表上完全合法。下标越界了照样抛 IndexError所以要时刻对“位置范围”保持敏感。批量修改可以借助切片赋值。比如把下标 1 到 2 这一段换成两个新元素lst [10, 20, 30, 40, 50] lst[1:3] [21, 22] print(lst) # [10, 21, 22, 40, 50]批量修改还有一种极端场景清空列表。你可能会用lst []但这样做其实是把lst这个变量名贴到了一个全新的空列表上原来那个列表还在内存里躺着如果还有别的变量引用着原列表那些变量的内容不会变。真正把列表内容清空应该用lst.clear()或lst[:] []。这种“清除内容”和“换一个新对象”的区别是后面理解引用传递的重要伏笔。配合这一节的内容我建议你做一个练习把列表[10, 20, 30, 40, 50]用切片把中间三个位置替换成[21, 22]然后再用[::-1]得到倒序结果手工写出每一步列表长什么样再在终端里跑一遍对照。这种“先用手推再用机器验证”的练习方式比死记语法有效得多。3. 列表的“增删改”操作搞清方法的脾气再动手3.1 append 与 extend别再把两个列表拼出嵌套结构append(x)是把x当作一个单独的新元素追加到列表末尾extend(iterable)则是把一个可迭代对象里的所有元素逐个取出来追加到列表末尾。文字说得再清楚不看代码还是会混a [1, 2] a.append([3, 4]) print(a) # [1, 2, [3, 4]] b [1, 2] b.extend([3, 4]) print(b) # [1, 2, 3, 4]这里非常容易翻车很多人想合并两个列表顺手就写了a.append(b)结果得到的是嵌套列表后续遍历、统计全都乱套。记住一个形象的记忆法append 是往篮子里塞进一个整箱extend 是把箱子里的东西全部倒进篮子。需要把“一个列表”整体作为元素时比如搞二维矩阵的一行用 append需要拼接数据流时用 extend 才是你要的结果。extend不光能接列表还能接任何可迭代对象比如a.extend(abc)会把字符串拆成三个字符加进去。这一点在批量处理的时候很灵活但也要小心别把本来想整体添加的字符串给拆碎了。3.2 插入与删除insert、pop、remove、del 怎么选插入用insert(index, x)它会在index位置前面插入新元素后面的元素自动往后顺延。比如lst [1, 2, 3] lst.insert(1, 99) print(lst) # [1, 99, 2, 3]删除的选择就多一些很容易纠结。pop(index)会取出并删除指定位置的元素同时把它作为返回值返回。不传参数时pop()删除并返回最后一个元素。经典“后进先出”的栈操作就是这么来的。remove(x)会删除第一次出现的、值为x的元素。注意两个关键点它只删第一个不是把所有相同值全删掉如果列表里根本不存在这个值它会抛ValueError。del是 Python 语句不是方法。del lst[2]删除指定位置del lst[1:3]删除切片区间del lst则直接把整个变量名干掉。clear()清空列表全部内容。我的选择经验很简单需要被删元素的值就用pop明确知道下标又不要返回值用del最省事只按内容删除且不关心位置用remove。记住pop和del的差异在于“有没有返回值”面试和考试特别喜欢拿这个出题。还有个使用频率很高的误区删除不存在的值时remove会抛异常。所以在不确定元素是否存在时可以先判断一下if x in lst:再删或者用try ... except兜住异常。后面写工程代码时这个习惯能少掉不少运行时的崩溃。3.3 遍历时不要直接改原列表这个雷要趁早避这是入门阶段最经典的“看起来对、跑起来魔幻”的例子lst [1, 2, 3, 2, 4, 2] for x in lst: if x 2: lst.remove(2)你的逻辑是把所有的 2 都删掉。可实际跑完你会发现结果是[1, 3, 4, 2]还残留了一个 2。原因是for循环在遍历时会记录当前取到的位置索引你一边遍历一边删列表长度不断变化索引也就跟着错位很多元素被悄悄跳过。这类问题在入门阶段极其隐蔽。我的建议很简单直接如果要对列表做过滤最稳的办法是生成一个新列表而不是原地删。用列表推导式一行搞定lst [x for x in lst if x ! 2]如果你实在必须在原列表上操作可以倒序遍历删除或者先复制一份再遍历副本原列表在循环结束后统一处理。记住这条底层规律不要在遍历列表的同时修改列表的长度和内容否则你会得到一个很难调试的烂摊子。4. 遍历与推导式让批量处理真正顺手起来4.1 for 循环遍历与 enumerate既要元素又要下标列表装好了数据接下来要解决的就是“挨个处理”。Python 的for循环写起来极其自然total 0 for score in scores: total score循环里的score依次绑定到列表的每个元素你不用关心下标怎么变化Python 内部全处理好了。这是初学者最友好的遍历方式。但有些场景下你不光需要元素还需要知道它的位置。比如打印“第几个学生考了多少分”。最传统的写法是for i in range(len(scores)): print(i, scores[i])可这样既写了range又惦记len不够利落。更 Pythonic 的写法是用enumeratefor i, score in enumerate(scores): print(i, score)enumerate会同时产出下标和元素默认下标从 0 开始。如果不希望从 0 开始可以传第二个参数for i, score in enumerate(scores, start1): print(i, score)这个小小的参数在“输出 1 号、2 号……”这种场景里特别有用省得自己维护一个计数器变量。如果你同时有两个长度对应的列表比如名字列表和成绩列表想配对遍历可以用zipfor name, score in zip(names, scores): print(name, score)zip会把多个可迭代对象按位置一一配对。这个函数在后续处理表格数据时可以算是神器现在先记个脸熟。遍历时还有一个隐蔽点直接在循环里给x赋值不会改动列表元素。因为x只是当前元素的临时引用你对它赋新值仅仅是把临时标签贴到了新对象上原列表纹丝不动。真想逐个修改元素必须通过下标赋值for i in range(len(lst)): lst[i] lst[i] * 24.2 列表推导式一条语句生成新列表列表推导式是 Python 最让人上头的语法之一基本结构是[表达式 for 变量 in 可迭代对象]。它做的事情是对每个元素执行表达式把结果收集成一个新列表。把每个成绩加五分new_scores [s 5 for s in scores]只保留及格的成绩passed [s for s in scores if s 60]相比写一个三行的for循环加append推导式把整个过程压缩成一行读起来像数学上的集合描述很清爽。初学推导式有一个很实用的方法论别硬背语法先从普通循环改写。先写一个标准的循环收集版本passed [] for s in scores: if s 60: passed.append(s)然后按照“表达式放最前面、循环结构放中间、条件放最后”的顺序一步步改成推导式。多翻几次之后你会发现推导式其实并不神秘。不过也要提醒一句推导式不是越多层越好。两层嵌套循环写起来还算能接受再往上可读性断崖式下跌。代码首先是给人读的其次才是给机器跑的。该用循环的时候就大大方方用循环。还有一个容易忽略的行为推导式生成的是完整新列表全部元素都会一次性放进内存。如果数据量是百万级、千万级就要考虑用生成器表达式——把方括号换成圆括号惰性取值内存占用会低很多。这个属于进阶话题先把推导式写顺再说。5. 排序、复制与引用几个容易翻车的地方5.1 sort 与 sorted一个原地排序一个返回新列表排序是列表最常见的操作。但入门阶段sort()和sorted()的差异几乎每个月都要回答一遍。sort()是列表自带的方法它会原地修改原列表的顺序并且返回None。sorted()是 Python 内建函数它对任何可迭代对象都能排序返回一个全新的列表原对象保持不变。最常见的翻车现场是这样的lst [3, 1, 2] new_lst lst.sort() # new_lst 是 None因为sort()返回的是None你把一个None当成排序结果存起来后面再调用new_lst[0]就会报 TypeError。正确做法是想原地排序就用lst.sort()然后继续用lst想要一个新列表且保留原列表就用sorted(lst)。sort和sorted都支持两个常用参数key和reverse。reverseTrue表示降序。key用来指定排序依据例如按字符串长度排序words [banana, apple, cherry, kiwi] words.sort(keylen) print(words) # [kiwi, apple, banana, cherry]对字典列表按某个字段排序时key也非常有用。比如按年龄排序people [{name: 张三, age: 30}, {name: 李四, age: 20}] people.sort(keylambda p: p[age])这里用到的lambda先不用深入你只需知道key参数接收一个函数它决定了“按哪个内容来比较大小”。还有一个很多人懵过的地方字符串列表排序和数字列表排序不是一回事。nums [10, 9, 12] nums.sort() print(nums) # [10, 12, 9]字符串排序比较的是字符的编码顺序所以10排在9前面。想按数值排序必须先转成整数。这就是前面反复强调“确认类型”的深层原因——不光是做运算排序和比较同样受类型影响。调试这类问题时先print(type(lst[0]))大概率就能定位。5.2 等号、拷贝与嵌套列表可变对象的引用陷阱把列表赋值给另一个变量并不会创建副本a [1, 2, 3] b a b.append(4) print(a) # [1, 2, 3, 4]对很多习惯“变量就是盒子”的人来说这个结果很意外。实际上Python 的变量名更像“贴在对象上的标签”。b a做的事情只是给同一个列表对象多贴了一个名为b的标签。通过b修改列表a自然也会看到变化。想真正创建一个独立副本可以用切片b a[:]也可以调用b a.copy()。这两种方式都是浅拷贝。浅拷贝对一维列表通常够用但如果列表里有嵌套的可变对象比如lst [[1, 2], [3, 4]]浅拷贝会复制外部列表但内部那两个小列表仍然是同一个对象。这时修改副本里的内部元素原列表同样会被连累。遇到这种情况最稳的方案是用标准库的deepcopyfrom copy import deepcopy b deepcopy(lst)入门阶段可以不用死背浅拷贝和深拷贝的定义但一定要记住一条自查规则复制之后修改副本时如果发现原列表也被改了说明复制得不够深。这条经验在以后处理复杂数据结构时会反复救你。初始化嵌套列表时也要避免用[[]] * n这个写法理由前面已经说过它创建的是同一个空列表的 n 个引用而不是 n 个独立空列表。真想建 n 个独立空列表用[[] for _ in range(n)]。5.3 函数传参时列表会“偷偷被改”把列表传给函数在函数里对它做修改外面的列表也会一起变。因为传递的依然是同一个引用def add_one(lst): for i in range(len(lst)): lst[i] 1 data [1, 2, 3] add_one(data) print(data) # [2, 3, 4]这个特性和整数、字符串完全不同。整数和字符串是不可变对象函数内部怎么折腾都不影响外部变量列表是可变对象函数内外共享的是同一份数据。这不算错误但确实是新人最容易踩的“副作用”坑。如果你写的函数会修改传入的列表最好在函数名或注释里写清楚此函数会原地修改列表。团队协作时这种习惯能避免很多人的困惑。6. 实战用列表写一个成绩统计小程序6.1 场景与目标理论讲了一大堆现在用一个小项目把它们串起来。假设班里有 5 个学生成绩分别是 78、92、85、64、99。需要写一个程序完成三件事计算平均分找出最高分和最低分输出每个学生是否及格。这个题目非常适合第八课当作业。它需要你用到列表创建、内置函数、遍历、条件判断几乎是前面所有内容的综合演练。6.2 三种实现版本从固定数据到动态输入版本一数据写死在代码里。scores [78, 92, 85, 64, 99] average sum(scores) / len(scores) max_score max(scores) min_score min(scores) print(f平均分{average}) print(f最高分{max_score}) print(f最低分{min_score}) for i, score in enumerate(scores, start1): status 及格 if score 60 else 不及格 print(f第{i}个学生{score}分{status})这段代码把sum、max、min、len、enumerate、f-string、条件表达式全用上了。最关键的是不管数据量从 5 变成 500代码一行都不用改。这就是把数据收拢进列表的价值。版本二从键盘动态输入。很多学员在这个版本会卡一下scores [] n 5 for i in range(n): value float(input(请输入成绩)) scores.append(value)这里最重要的细节是float()转换。input()永远返回字符串不转换就存进列表后面的sum和比较运算全会报错。先把数据变成数值再append可以避免一大半新手问题。这个“先转换类型再入列”的习惯在数据处理中怎么强调都不过分。版本三用列表推导式生成模拟数据方便反复测试统计逻辑import random scores [random.randint(40, 100) for _ in range(5)]测试时用随机数据特别方便不用手动一次次输入还能快速验证程序在不同数据下是否正常。等真正要跑真实数据时把生成模拟数据的那一行换成输入逻辑就行。6.3 扩展方向与进阶练习基础做完之后想巩固的话可以加这些任务统计高于平均分的人数用两个列表分别存姓名和成绩按成绩排序输出用sorted降序取前 3 名把一个二维列表按“每个学生的各科成绩”转置成“每门课的全部学生成绩”。最后一个任务会顺带锻炼嵌套列表的处理意识是一个很有意思的进阶练习。7. 常见报错与自查清单真出问题时别慌7.1 五个高频报错速查我把入门阶段最高频的 5 类报错整理成了一张速查表照着定位非常快报错信息常见原因解决思路IndexError: list index out of range访问了不存在的下标打印len(lst)确认边界记住最大下标是len(lst)-1ValueError: list.remove(x): x not in list删除了不存在的元素删除前先if x in lst判断TypeError: not supported between instances of str and int列表元素类型不统一还拿去排序比较打印type(lst[0])统一转成数值类型AttributeError: NoneType object has no attribute sort把lst.sort()的返回值None当列表用了确认sort()原地返回None需要用新列表就改用sorted()TypeError: list indices must be integers or slices, not str用字符串当下标访问列表列表下标必须是数字检查索引是从哪来的7.2 调试列表问题的三个笨办法但很管用遇到列表相关的诡异问题先别急着怀疑人生按这套流程来排查绝大多数问题都能定位。第一步打印横切面。把程序卡住的列表切片打出来print(lst[:5])。既能验证长度也能看元素内容是否符合预期。切片调试法的好处是即使列表很大也不会让控制台刷屏。第二步打印类型。不确定每个元素是什么类型就print(type(lst[0]))。字符串和数值混在一个列表里是排序和运算出错的头号原因。这一步往往一步就能锁定问题。第三步缩小样本量。把一个几百条数据的大列表换成三条数据的最小复现用例把循环和操作一步步手动演算一遍。很多“莫名其妙”的错误在你缩小场景之后其实非常清晰。比如遍历时删除导致跳元素的问题把它缩小到[1, 2, 2, 3]这种小样本跑一遍立刻就能看出道理。7.3 几个拿来即用的自查问题写完列表相关代码后可以问自己几个问题我有没有在遍历列表时修改原列表我用的是sort()还是sorted()原列表是否被意外改动我把列表赋值给新变量时是不是无意中共享了同一个对象我明确知道列表里每个元素的类型吗这几个问题能挡住大多数隐藏 bug尤其是在写稍长一点代码的时候。最后再分享一个小经验我见过太多初学者把列表的各种方法背得滚瓜烂熟但真到写项目时还是抓瞎。列表这东西光靠看是学不会的必须亲手敲。你可以从今天开始把平时记零散信息的方式改成“用列表收集、用循环处理”。比如楼下超市的购物清单、喜欢的电影榜单、每天背的单词表通通拉进去练手。把这些真实的小事写进代码里你对列表的感觉会完全不一样。等你把列表玩溜了后面学字典、集合、元组会发现它们之间很多思路都是相通的。