资讯动态

Python列表入门:从变量困境到批量数据存储与处理

发布时间:2026/10/15 23:18:54 来源:尧图企业网站定制
你有没有一次要存十个、二十个数据的经历比如期末成绩、购物清单、待办事项。如果按最朴素的想法可能会定义十个变量score1、score2……写到一半就想摔键盘。这类批量数据正好是 Python 列表最擅长的场景。列表用一对中括号表示能把不同类型的数据有序地装在一起还能随时用序号取出来、修改、添加或删除。这篇文章就从“列表是解锁数据存储的第一步”讲起把创建、索引、切片、增删改、遍历、排序、复制这些基础能力一次讲清楚最后用一个成绩统计的小项目把知识串起来。适合刚学 Python 不久变量、循环都还不太熟的同学也适合平时做表格、处理日志想用脚本替代手工的办公党。学完以后你再看到一堆散乱数据时第一反应就不再是写一堆变量而是先想到整理成一个列表然后批量处理。1. 为什么数据存储要先学列表从变量困境说起1.1 变量存数据数据一多就露馅变量本质上是在内存里给一个值贴标签一个名字对应一块存储空间。存三门课成绩时三个变量勉强能接受但要存三十个学生的成绩你就得写三十个变量。更让人头疼的是后续操作完全没法批量做想算总分得手动写一长串加法想找最高分得挨个比较想排序更是噩梦。这不是能力问题而是数据规模一变变量这种“一对一”的存储方式就不够用了。还有一个更隐蔽的问题变量的个数在写代码时往往是固定的但现实中数据的条数会动态变化。今天有八个订单明天新增一个后天退掉两个如果全用独立变量维护代码会变得极其脆弱。每次数据量变化都要改代码结构而不是只改数据本身。我自己刚接触编程时也干过这种傻事给一个月三十天的数据定义三十个变量写完之后自己都记不清哪个是哪个。列表就是来打破这种局面的。它把“多个数据”包装成一个整体对象赋值一次、传参一次、遍历一次就能处理整组数据。从这一刻起你不再关心某个数据叫什么名字而是关心它在列表里的位置以及列表里一共有多少数据。这种思维转变比记住语法本身更重要。1.2 列表解决的是“整组数据的组织问题”列表有三个核心特征分别是有序、可变、可批量操作。有序意味着每个元素都有稳定的位置可以通过序号找到它。就像一列火车车厢不需要知道每节车厢的名字只需要知道是第几节。可变意味着可以随时往列表里加元素、改掉某个位置的元素、删掉不要的元素而且这一切都不影响列表作为一个整体对象继续使用。批量操作能力则体现在 Python 的内建函数上。一个sum函数能把整个列表相加一个len能数出元素个数一个max能直接揪出最大值。换成几十个独立变量这些操作全都做不了。你可以把列表想象成一个收纳盒零散的东西丢进去之后收纳盒本身就成了一个可传递、可统计、可排序的整体。后面学循环时列表更是主力for循环遍历列表几乎是所有数据处理代码的基础骨架。1.3 列表和传统编程语言里的“数组”不是一回事很多学过 C 或 Java 的同学会下意识地把列表理解成数组这么想大方向没错但细节有明显差异。传统数组往往要求所有元素类型一致长度固定存满了就得重新开辟一块更大的内存。Python 列表则宽松得多不要求元素类型一致长度也不用预先声明解释器会自动处理扩容和收缩。Python 列表的底层其实是一个动态数组数组里保存的是指向各个对象的引用而不是对象本身。这句话现在不理解也没关系你只需要先建立一个认知列表是对象的容器里面每个元素本质上都是一个“指向真实数据的地址”。正因如此列表才能做到同一类型混搭比如一个列表里同时放字符串、整数、小数甚至另一个列表。这里也要提醒一句列表不是万能的。数据量大到几十万条、并且要做大量数值计算时纯列表效率会偏低后续可以换用更专业的数组工具。但作为入门阶段的数据结构列表就是帮你建立“数据组织”概念的起点。先把列表用熟练后面的元组、字典、集合学起来都会轻松不少。2. 创建列表的几种姿势从空盒子到复杂结构2.1 用中括号直接创建最符合直觉的写法创建列表最直接的方式就是写一对中括号把元素用逗号隔开放进去。empty_list [] # 空列表 numbers [1, 2, 3, 4, 5] names [小明, 小红, 小刚] single [0] # 注意这是含一个元素的列表不是整数 0有一个新手特别容易踩的坑只放一个元素时中括号不能省而且要记得写逗号吗在列表里不需要额外写逗号[0]就是一个元素的列表。如果去掉中括号写0那就是整数本身完全不是一回事。元素较多时我建议写成多行尤其是将来要维护的数据结构。menu [ 宫保鸡丁, 鱼香肉丝, 麻婆豆腐, ]末尾留一个逗号是 Python 里推荐的写法规后续追加元素时不容易因为忘写逗号而报错也能减少 Git 对比差异。这种“尾部逗号”的习惯在配置服务器列表、商品清单这类经常变动的数据时特别有用。你不需要把所有元素硬塞进一行代码是给人读的排得松散一点反而更好检查。2.2 用 list() 函数把其它数据“转”成列表除了中括号list()函数也能创建列表它会把一个可迭代对象逐元素转换成列表。print(list(hello)) # [h, e, l, l, o] print(list(range(5))) # [0, 1, 2, 3, 4] print(list((1, 2, 3))) # [1, 2, 3]字符串本身是字符序列所以list(hello)会把每个字符拆开。range(5)生成的也不是列表而是一个区间对象用list()装一下才能看到具体内容。这在实际开发中很常见很多函数返回的是“惰性”的可迭代对象我们为了能索引、能修改、能统计会先把它转成列表。list()也可以直接复制一个已有列表new_list list(old_list)。不过这里要敲个警钟这叫浅复制后面专门讲复制陷阱时还会提到。2.3 列表里能装什么类型混搭与嵌套结构Python 列表不要求元素类型一致这是它非常好用的一点。mixed [Python, 3.14, True, [1, 2]]上面这个列表里有字符串、浮点数、布尔值还有一个子列表。嵌套列表的价值在于它可以表达二维结构比如一张表格里的多行数据students [ [张三, 18, 通过], [李四, 19, 未通过], [王五, 18, 通过], ]每个子列表相当于一行记录外层列表装下所有行。这种结构以后在读取 Excel、CSV、数据库结果时几乎天天见到。看到这类数据时不要慌外层列表的索引定位“第几行”内层列表的索引定位“第几个字段”。还是要强调一遍列表里存的是引用不是数据本身。把一个大列表塞进另一个列表时并没有复制一份数据而是让外层列表的某个位置指向同一个数据对象。短期看感觉不到差异一旦修改内层子列表你可能会踩到引用共享的坑。2.4 变量和列表的真实关系贴标签而不是复制盒子很多新手在创建列表后会遇到一个奇怪现象明明我赋值给了另一个变量怎么改新变量原列表也变了a [1, 2, 3] b a # 这不是复制只是给同一个列表贴了第二个标签 b.append(4) print(a) # [1, 2, 3, 4]这里需要建立一个模型变量名是贴在一个盒子上的便利贴a [1, 2, 3]是先创建了一个列表盒子再把名为a的便利贴贴上去。执行b a时并没有创建新盒子而是又拿了一张便利贴贴到同一个盒子上。于是不管通过a还是b修改盒子内容看到的都是同一个结果。我自己在学习阶段经常因此怀疑是不是写错了后来发现很多老手也偶尔在这里翻车。避免的方法很简单先判断你是想“换一个名字访问同一个数据”还是想“创建一份独立副本”。如果是后者请用a[:]、a.copy()或list(a)这些内容在第 6 章展开。3. 索引与切片把元素精确定位到“第几个”3.1 正向索引和负向索引忘掉“第一个1”的直觉列表的索引从 0 开始这和现实生活中的“第一个”相差 1。比如nums [10, 20, 30, 40, 50] print(nums[0]) # 10 print(nums[4]) # 50 print(nums[-1]) # 50 print(nums[-2]) # 40正向索引是从左往右数第一个元素的下标是0负向索引是从右往左数最后一个元素的下标是-1。负索引是 Python 里特别贴心的设计想取最后一个元素时不需要先算len(nums) - 1直接写nums[-1]就行。为什么从 0 开始因为计算机在内存里通过“偏移量”访问数据第一个元素的偏移量是 0第二个才是 1。这种约定在你初学时觉得别扭但长期来看会让很多算法实现更简洁。比如循环for i in range(len(nums))拿到的i正好能直接用作下标自然得不能再自然。3.2 切片一次取出一段子列表切片是 Python 列表的招牌特性写法是列表[开始位置:结束位置]但结束位置不包含在内这叫“左闭右开”。nums [10, 20, 30, 40, 50] print(nums[1:4]) # [20, 30, 40]包含索引1、2、3不包含索引4 print(nums[:3]) # [10, 20, 30]从开头取到索引2 print(nums[3:]) # [40, 50]从索引3取到末尾 print(nums[:]) # 整个列表的复制 print(nums[::2]) # [10, 30, 50]每隔一个取一个 print(nums[::-1]) # [50, 40, 30, 20, 10]反序第三个参数叫步长负数表示从右往左走。nums[::-1]是常见的列表反转手法。切片时还可以两个冒号一起用nums[1:5:2]表示从索引 1 开始到索引 4每两个元素取一个。切片的好处是不用写循环一行代码就能完成截取。比如取成绩单前三名调用scores[:3]取最后三条记录调用records[-3:]。但要提醒一点切片时结束位置越界不会报错只会默默返回能取到的部分。这个特性在某些场景是好事在某些场景可能隐藏 bug后面第 8 章会专门讲。3.3 切片出来的结果到底是引用还是新列表切片返回的是一个新列表这是它和直接赋值的最大区别。nums [10, 20, 30, 40, 50] sub nums[1:3] sub[0] 99 print(sub) # [99, 30] print(nums) # [10, 20, 30, 40, 50]原列表不受影响修改sub不会动到nums说明切片已经创建了一份独立的“外壳”列表。但这个独立是有限度的如果列表里嵌套了子列表切片只会复制外层容器内层子列表依然是共享的。也就是说sub[0]如果是另一个列表你修改那个子列表里的元素原列表也会跟着变。想要彻底独立副本需要深复制具体操作在第 6 章。新手经常会分不清nums[0]和nums[0:1]前者返回第一个元素的原始类型比如整数10后者返回一个只含一个元素的列表[10]。这个区别看起来细微但写代码时影响极大尤其在做sum()、max()这类操作时会直接决定报不报错。3.4 边界易错点越界报错与空切片索引和切片虽然看着简单但边界问题一直是新手的重灾区。nums [10, 20, 30, 40, 50] # print(nums[5]) # IndexError: list index out of range print(nums[3:100]) # [40, 50]切片越界不报错 print(nums[0:0]) # []空切片索引越界会直接抛异常程序当场崩溃切片越界则很有“包容心”能取多少就取多少。很多新手在循环里用索引访问时突然崩溃多半是没注意到range(len(nums))里的len到底是元素个数还是最大下标。元素个数是 5最大下标是 4想要安全访问最后一个元素要么用nums[-1]要么用nums[len(nums)-1]千万别写nums[len(nums)]。我自己的经验是能写切片就别手算下标。切片让代码意图清楚又不会因为下标差一而崩。真正需要原生索引的时候多用负索引和enumerate这两招能省掉很多边界判断。4. 增删改让你的列表动起来4.1 修改元素直接按位置赋值列表最有价值的一点就是可变可以原地修改。修改方式也最直观直接对索引位置赋值。nums [10, 20, 30, 40, 50] nums[1] 99 print(nums) # [10, 99, 30, 40, 50]要特别注意赋值目标的索引必须已经存在。给一个长度 5 的列表写nums[5] 60会直接报错因为最大下标只有 4。切片也可以用来批量替换nums[1:3] [7, 8, 9]这段代码会把索引 1、2 两个位置替换成三个新元素结果列表长度会变化。这种灵活替换在很多练手项目里很有用但也容易让阅读代码的人困惑使用时最好加注释说明替换的意图。4.2 添加元素append、insert、extend 各有各的活儿append把元素加到末尾一次只加一个。insert可以把元素插入任意位置。extend则把另一个可迭代对象里的元素逐个合并进来。stack [1, 2, 3] stack.append(4) # [1, 2, 3, 4] stack.insert(0, 0) # [0, 1, 2, 3, 4] stack.extend([5, 6]) # [0, 1, 2, 3, 4, 5, 6]最容易混淆的是append和extend。append([5, 6])是把[5, 6]当做一个整体元素塞进去结果变成[1, 2, 3, [5, 6]]。extend([5, 6])则是把5和6分别加入结果变成[1, 2, 3, 5, 6]。如果你在做数据处理多半想要的是extend如果你在维护“列表的列表”比如往日志记录里追加一条明细多半想要的是append。这三个方法都是原地修改返回值是None。新手容易写出mylist mylist.append(1)的代码结果发现mylist变成了None这就是不理解原地修改的表现。记住列表自己的方法大多是原地操作返回值不是给你重新赋值的。4.3 删除元素pop、remove、del、clear四种思路删除元素的方法很多按删除依据可分为两类按位置删除和按值删除。nums [10, 20, 30, 40, 50] last nums.pop() # 默认删除最后一个返回 50nums 变成 [10, 20, 30, 40] second nums.pop(1) # 删除下标 1 的元素返回 20 nums2 [10, 20, 30, 20] nums2.remove(20) # 删除第一个值为 20 的元素结果 [10, 30, 20]pop是“取出并删除”常用在需要拿回被删元素的值时。remove则是按值搜索只删第一个匹配项如果列表里有多个相同值后面的不会被删。如果值不存在remove会抛出ValueError所以用之前最好先in判断一下。del是一个语句不是列表的方法专门用来按位置删除也可以删除整个变量或一段切片。nums [10, 20, 30, 40, 50] del nums[0] # [20, 30, 40, 50] del nums[1:3] # [20, 50]clear()则是清空所有元素让列表变成空列表[]。最稳妥的删除思路是需要拿到值就pop只需要删掉就del只知道内容不知道位置就remove。4.4 一种经典用法用 append 和 pop 模拟“后进先出”栈栈是一个非常经典的数据结构特点是后进先出像一摞盘子最后放上去的盘子最先被拿走。Python 列表天然就能模拟栈stack [] stack.append(浏览页面1) stack.append(浏览页面2) stack.append(浏览页面3) current stack.pop() print(current) # 浏览页面3浏览器返回上一页、编辑器撤销操作背后都是这种结构。你在练习时完全可以写一个迷你函数用列表维护操作记录。这个例子虽小但它说明了一个重要观点学列表不是学孤立语法而是学数据组织方式。当选好append和pop的组合代码就会非常清爽。要特别小心空列表的pop()它同样会抛IndexError。在高频操作的代码里调用前最好判断一下if stack:再执行pop。这个习惯能帮你省掉很多莫名其妙的崩溃日志。5. 遍历列表的实用套路从傻傻地数下标到优雅地处理5.1 直接用 for 循环遍历读数据的最简单方式列表最常出现的场景就是循环。当一个元素一个元素处理时Python 的for循环可以直接拿到元素本身不需要操心下标。fruits [苹果, 香蕉, 橙子] for fruit in fruits: print(fruit)这种写法已经成了 Python 社区的主流习惯。相比for i in range(len(fruits))它少了一次索引查找也让代码意图更清楚我只想要每一个水果的值不关心它在什么位置。那什么时候必须用range当你确实需要“第几个”这个信息时。比如把水果和价格按顺序配对或者要修改列表某个位置的值时直接拿值改不到原列表里的对应位置。这时候可以改用下面的enumerate。5.2 需要下标时优先用 enumerate而不是手写计数enumerate的作用是同时取出“下标”和“元素”。fruits [苹果, 香蕉, 橙子] for i, fruit in enumerate(fruits): print(i, fruit) # 输出0 苹果 / 1 香蕉 / 2 橙子 for i, fruit in enumerate(fruits, start1): print(i, fruit) # 输出1 苹果 / 2 香蕉 / 3 橙子第二个参数start很有用比如你想给清单编号从 1 开始不需要自己再维护一个count变量。很多新手喜欢写flag 0然后在循环里flag 1既容易出错又不简洁。只要看到“遍历列表时还需要位置信息”的需求第一反应就应该是enumerate。我在处理带标题的表格数据时也常用enumerate判断当前行是不是表头。它比手动下标安全得多因为不会再犯下标越界的低级错误。5.3 遍历的同时修改列表一个隐藏很深的陷阱很多新手会在循环里删除元素然后惊讶地发现结果不对。lst [1, 2, 3, 4, 5] for x in lst: if x 2: lst.remove(x) print(lst) # 看似没问题如果删除的是多个符合条件的元素问题就明显了。原因在于for x in lst是按索引逐步推进的每删除一个元素后面所有元素都会往前提一位而循环索引依然往下走于是某个元素就被跳过了。比如lst [1, 2, 2, 3] for x in lst: if x 2: lst.remove(x) print(lst) # 结果是 [1, 2, 3]而不是 [1, 3]解决办法有三种第一种遍历列表的副本修改原列表第二种用列表推导式直接生成新列表第三种用while循环从后往前删。我个人最推荐第二种既简洁又不会漏删。lst [x for x in lst if x ! 2]这样写逻辑清晰过滤掉不需要的元素保留剩下的。它不会原地修改而是产生一个新列表所以别忘记重新赋值给变量。5.4 列表推导式一行代码生成新列表列表推导式是 Python 列表操作中最有“杀伤力”的语法之一。squares [x * x for x in range(10)] # 等效写法 squares [] for x in range(10): squares.append(x * x)它还可以加过滤条件nums [1, 2, 3, 4, 5, 6] even [x for x in nums if x % 2 0] # [2, 4, 6]语法可以概括成表达式 for 变量 in 可迭代对象 if 条件。表达式是你要放进新列表的值for负责取元素if负责过滤。这种写法让“从一堆数据里挑出一部分并加工”变成一行代码在清洗数据、生成配置项时非常常用。但我也要奉劝一句不要为了炫技而嵌套多层列表推导式。两三层以上代码可读性会暴跌。宁可拆成普通循环加判断也别写出别人看了想砸键盘的表达式。列表推导式最大的优势是简洁简洁的前提是逻辑足够简单。6. 常用方法与排序查找别把简单事情做复杂6.1 index、count、in查找元素的位置与次数想知道一个元素在列表里的位置用index想知道它出现多少次用count只想知道在不在用in。positions [零, 一, 二, 一] print(positions.index(一)) # 1只找第一个 print(positions.count(一)) # 2 print(二 in positions) # Trueindex在找不到元素时会抛出ValueError所以更安全的写法是if 三 in positions: print(positions.index(三))或者先把in判断和index结合起来。很多初学者容易把index和find混淆但字符串里的find找不到会返回 -1而列表的index会报错这是两个不同的设计。日常处理中我会优先用in做存在性判断再考虑要不要拿位置。6.2 sort 和 sorted原地排序还是生成新列表排序是列表最常用的能力。sort是列表的方法原地修改排序sorted是内置函数返回新列表。nums [3, 1, 2] nums.sort() # nums 自己变成 [1, 2, 3] nums2 [3, 1, 2] new_list sorted(nums2) # nums2 不变new_list 是 [1, 2, 3]如果需要一个有序副本但原列表顺序还有别的用途就选sorted。如果在一次数据处理流程里反正不再需要原顺序直接sort更省内存。两者默认都是升序想降序就加参数reverseTrue。更重要的是key参数。它可以指定按什么规则排序比如按字符串长度、按对象的某个属性words [a, ccc, bb] words.sort(keylen) print(words) # [a, bb, ccc]key接收一个函数排序时每个元素先经过这个函数处理再按处理结果比较。这个技巧在处理成绩按分数排序、人员按年龄排序时极为有用。还有一个经典坑new_nums nums.sort()得到的new_nums是None因为sort是原地操作不返回列表。这种错误我在不少代码评审里都见过一定要提前建立记忆。6.3 reverse、reversed、切片反转选哪个反转列表也有多种方式。reverse()是原地反转返回Nonereversed是一个内置函数返回一个反向迭代器想得到列表还得用list()包一下切片[::-1]则会直接返回一个新列表。nums [1, 2, 3] nums.reverse() # 原地反转为 [3, 2, 1] nums2 [1, 2, 3] new_nums nums2[::-1] # new_nums 是 [3, 2, 1]nums2 不变 nums3 [1, 2, 3] new_nums2 list(reversed(nums3)) # [3, 2, 1]选择标准很简单不再需要原顺序就reverse()需要保留原顺序就切片反转如果只是想在循环里从后往前读用reversed更省内存。切片反转虽然方便但它会完整复制一份列表对大列表来说会产生额外内存开销。6.4 复制列表的正确姿势浅复制与深复制前面在创建列表时提到直接b a只是贴新标签。真正复制要区分浅复制和深复制。a [1, 2, 3] b a[:] # 浅复制新列表 c list(a) # 浅复制 d a.copy() # 浅复制 import copy e copy.deepcopy(a) # 深复制对于一维列表上面的浅复制已经够用。浅复制的意思是新列表和原列表共享内部的元素对象如果列表里嵌套了子列表修改子列表里的内容两边都会变。matrix [[1, 2], [3, 4]] copy_matrix matrix.copy() copy_matrix[0].append(99) print(matrix) # [[1, 2, 99], [3, 4]]要想连嵌套子列表也彻底隔离必须用copy.deepcopy。在实战中浅复制能覆盖大部分需求比如备份一整块配置项、临时保存一份待排序列表。一旦你发现复制后改数据影响了原列表就先检查一下是不是嵌套列表导致的然后再决定是否需要deepcopy。不会用深复制也不要紧但至少要理解“复制外壳”与“复制全部”的区别这个坑后面早晚会踩。7. 实战写一个简易成绩统计把前面知识串起来7.1 需求拆解先想清楚要算什么实战是最好的复习。假设你手头有一组学生成绩要算总分、平均分、最高分、最低分、及格人数和及格率。先不要急着写代码而是想一想数据长什么样以及哪些操作能直接用到列表。数据本身就是列表scores [85, 92, 58, 67, 100, 76, 89]。总分会想到sum(scores)元素个数用len(scores)最高最低用max和min及格人数则需要过滤出大于等于 60 的元素再数个数。一套操作下来整个过程不超过十几行代码这就是列表批量能力的直观体现。如果将来分数来自用户输入还可以先让用户用空格分隔输入再把字符串拆成列表data input(请输入成绩用空格分隔) scores [int(x) for x in data.split()]这里的split()把字符串按空白切成若干部分再用列表推导式把每个部分转成整数。这一步就同时用到了字符串方法和列表推导式非常典型。7.2 核心代码从统计到输出下面是一个可以直接运行的成绩统计脚本scores [85, 92, 58, 67, 100, 76, 89] total sum(scores) avg total / len(scores) highest max(scores) lowest min(scores) passed [s for s in scores if s 60] pass_num len(passed) pass_rate pass_num / len(scores) * 100 print(f总分{total}) print(f平均分{avg:.1f}) print(f最高分{highest}) print(f最低分{lowest}) print(f及格人数{pass_num}) print(f及格率{pass_rate:.1f}%)运行结果大概是这样总分547 平均分78.1 最高分100 最低分58 及格人数6 及格率85.7%7.3 逐段讲解为什么能这样写sum(scores)之所以能一行求总数是因为列表是可迭代对象内置函数可以自动遍历它。len(scores)返回元素个数用它当分母就能算平均分。max(scores)和min(scores)同样会遍历整个列表找出最大最小值。如果不依赖内置函数你就要写循环加条件判断其实内部的原理也一样只是内置函数更可靠、更快。passed [s for s in scores if s 60]是列表推导式它生成一个新列表里面只保留符合条件的分数。再调用len(passed)就得到了及格人数。这里有个细节原列表scores没有被修改passed是独立的新列表。如果你后续还要用到原始成绩排序或画图这个特性会非常宝贵。7.4 可以继续扩展的思路从统计到实战工具这个例子虽然简单但扩展空间很大。你可以把姓名和成绩配对成一个嵌套列表像[[张三, 85], [李四, 92], ...]然后按成绩排序取出前三名。也可以按分数区间统计人数比如用列表推导式分别统计 90 分以上、80 到 89 分、60 到 79 分、60 分以下各有多少人。还可以把结果写进一个汇总列表再配合文件读写保存成报告。如果你是在办公场景遇到真实数据更常见的做法是从表格软件里复制一列数据粘贴到文本文件再用 Python 读取成列表。到那个时候你会发现列表的每一项已经从“数字”变成“字符串”要先用strip()清理空格、再用float()转成数字。所有操作都离不开本章的基础。所以我建议你不要只盯着代码本身而是把“先组织数据、再批量处理”的思维内化掉这才是列表带来的最宝贵能力。8. 常见问题与避坑速查把容易翻车的地方一次说清8.1 下标越界为什么最后一个索引不是 len新手最常见的问题就是访问nums[len(nums)]然后看到IndexError。因为列表索引从 0 开始长度是 5 的列表最后一个元素下标是 4也就是len(nums) - 1。想取最后一项负索引最安全nums[-1]。想遍历全部下标用range(len(nums))不要自作主张加 1。切片则不会因越界报错这是一个容易混淆的双面特性。nums[5:10]不会报错但会得到空列表或剩余部分。在写代码时要明确自己用的是“索引访问”还是“切片访问”两者的边界规则不同出错表现也不同。8.2 列表为什么不能当字典的键有同学写代码时想把列表作为字典的键结果报错TypeError: unhashable type: list。原因是字典的键要求是不可变类型而列表可变它的内容一变哈希值就没法稳定下来无法作为“唯一标识”。遇到这种需求把列表换成元组即可。下面是一个对比# 错误示例 # key [1, 2] # {key: value} # 会报错 # 正确示例 key (1, 2) print({key: value}) # {(1, 2): value}这其实提醒我们Python 里每种容器都有自己的适用场景列表适合顺序和频繁变动元组适合固定结构字典适合按键查找。不要因为列表用着顺手就让所有数据都住进列表。8.3 嵌套列表浅拷贝陷阱复制了还是同一份这是我在实际项目中踩过的坑。复制二维列表时如果只用copy()或[:]外层确实新开了一个容器但内层子列表仍然是同一个对象。修改副本中的内层列表原列表会跟着变。解决方法是copy.deepcopy或者手动为每个内层列表都做一次切片复制。import copy matrix [[1, 2], [3, 4]] deep copy.deepcopy(matrix) deep[0][0] 99 print(matrix) # [[1, 2], [3, 4]]判断是否需要深复制就看你的列表里有没有嵌套的可变容器。如果有而且后续要分别修改就老老实实用深复制别图省事。8.4 循环中删除元素元素会“悄悄逃走”第 5 章提到的坑这里再完整梳理一次在for循环里直接删除当前列表元素会导致部分元素被跳过。原因是 Python 内部按索引推进每次删除都会让后面的元素向前移动。最简单的解决方式是列表推导式它天然生成新列表nums [1, 2, 2, 3, 4, 5] nums [x for x in nums if x ! 2] print(nums) # [1, 3, 4, 5]如果你确实需要原地修改建议遍历副本for x in nums[:]然后对原列表remove。这里的nums[:]是切片副本遍历它就不会受到删除操作影响。这类问题在数据清洗时特别常见遇到筛选、去重、删除后总是莫名少数据先从这找原因。我个人教过不少零基础学 Python 的朋友发现大家最容易卡住的不是语法本身而是不会把现实问题组织成“数据容器”的形态。列表就是第一道关口。刚开始学的时候别急着背方法拿纸笔画一画列表的下标和切片多写几个增删改的小实验比任何速查表都管用。等你能随手把一串文本拆成列表、再用一行列表推导式完成筛选时你会明白这些基本功值得反复练。列表掌握好了后面的字典、集合、字符串处理都会顺很多这一章确实是整个 Python 入门里最值得慢下来的地方。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑