
1. 项目概述为什么列表是Python的“瑞士军刀”如果你刚开始学Python学到第八课恭喜你你已经跨过了最基础的语法门槛准备进入真正让编程变得有趣和强大的领域了。前面几课你可能在跟print()、if-else、while循环这些“单兵作战”的指令打交道但从这节课开始你要接触的是能组织“军团”作战的核心工具——数据结构。而列表就是这个军团里最全能、最常用、也最值得你花时间深挖的“瑞士军刀”。我刚开始写代码时总觉得变量一个个声明就够了直到有一次需要处理几十个用户的成绩手写score1,score2...写到score30时差点崩溃才幡然醒悟数据结构存在的意义。列表简单说就是一个“容器”一个可以装下多个数据的“购物车”。它能让你把一堆相关联的数据整齐地打包在一起用一个名字来管理然后进行批量操作。无论是你从网上爬下来的十条新闻标题还是游戏里一个角色的背包物品或者是计算全班同学的平均分背后都离不开列表的身影。这节课我们不求快但求稳。目标是让你彻底理解列表是什么、怎么创建、如何把数据放进去又拿出来以及掌握几个最核心的“增删改查”操作。这些是地基地基打牢了后面学列表的进阶技巧比如切片、列表生成式和更复杂的数据结构元组、字典、集合才会事半功倍。你会发现理解了列表Python世界里一半以上的数据处理场景你都能找到清晰的解决思路。2. 列表的核心概念与创建方法2.1 列表究竟是什么从“购物车”到“储物柜”在Python中列表是一个有序、可变的元素集合。这两个特性是理解列表所有行为的关键。有序这意味着列表中的每个元素都有一个确定的位置称为索引。就像电影院里的座位1排1座和1排2座是两个不同的位置。在列表中我们通过从0开始的整数索引来定位元素。第一个元素的索引是0第二个是1以此类推。这个顺序是你放入元素时的顺序并且会被严格保持。可变这是列表与之后要学的元组最根本的区别。可变意味着列表创建后你可以随意地修改它添加新元素、删除旧元素、改变某个位置元素的值。它就像一个可以随时调整内部隔板的储物柜非常灵活。我们可以用一个更生活化的例子来类比管理一个音乐播放列表。列表本身就是那个播放列表比如“我的最爱”。列表中的元素就是一首首具体的歌比如[“晴天” “七里香” “以父之名”]。索引就是每首歌在列表中的位置序号“晴天”是第0首“七里香”是第1首。可变性意味着你可以随时从这个播放列表里删掉不喜欢的歌或者加入新发现的宝藏歌曲。2.2 三种创建列表的姿势知道列表是什么之后我们来看看怎么把它“造”出来。创建列表主要有三种方式适用于不同场景。1. 使用方括号[]直接创建最常用这是最直观、最快捷的方式。直接把所有元素用逗号隔开放在方括号里就行了。# 创建一个包含几个歌名的列表 playlist [“晴天” “七里香” “以父之名”] print(playlist) # 输出 [‘晴天’ ‘七里香’ ‘以父之名’] # 列表里的元素可以是任何类型甚至混搭 mixed_list [‘Python’ 3.14159 True [1 2, 3]] # 包含字符串、浮点数、布尔值甚至另一个列表 print(mixed_list) # 输出 [‘Python’ 3.14159 True [1, 2, 3]]注意虽然列表可以容纳不同类型的数据但在实际项目中为了逻辑清晰和便于处理一个列表通常只存储同一种类型的数据例如全部是字符串或全部是数字。mixed_list这种混搭更多用于演示概念或某些特殊的嵌套结构。2. 使用list()函数进行转换list()函数可以将其他可迭代的数据类型比如字符串、元组、字典的键等转换成列表。这在数据转换时特别有用。# 将字符串的每个字符转换成列表元素 word “hello” char_list list(word) print(char_list) # 输出 [‘h’ ‘e’ ‘l’ ‘l’ ‘o’] # 将元组转换成列表 a_tuple (1 2, 3) list_from_tuple list(a_tuple) print(list_from_tuple) # 输出 [1, 2, 3] # 创建一个空列表 empty_list list() # 这种方式不常用更常用的是 empty_list [] print(empty_list) # 输出 []3. 使用列表生成式先了解后续课程详解这是一种非常Pythonic具有Python风格且高效的创建列表的方式特别适合根据某种规则生成序列。这里先混个脸熟。# 生成一个包含0到9的平方的列表 squares [x**2 for x in range(10)] print(squares) # 输出 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]2.3 访问列表元素通过索引“点名”创建了列表我们就要学会如何把里面的数据“取出来”看看。这通过索引来完成。fruits [‘apple’ ‘banana’ ‘cherry’ ‘durian’] # 访问第一个元素索引为0 first_fruit fruits[0] print(first_fruit) # 输出 apple # 访问第三个元素索引为2 third_fruit fruits[2] print(third_fruit) # 输出 cherry # 尝试访问不存在的索引会引发 IndexError 错误 # print(fruits[10]) # 这会报错实操心得负索引的妙用索引不仅可以从左往右数0 1, 2...还可以从右往左数使用负数索引。-1代表最后一个元素-2代表倒数第二个以此类推。这在你不知道列表长度但又想获取末尾元素时极其方便。fruits [‘apple’ ‘banana’ ‘cherry’ ‘durian’] last_fruit fruits[-1] print(last_fruit) # 输出 durian second_last_fruit fruits[-2] print(second_last_fruit) # 输出 cherry3. 列表的“增删改查”基础操作掌握了创建和访问我们就进入了列表操作的核心——“增删改查”。这是你未来对列表进行任何复杂处理的基础。3.1 “增”如何向列表中添加新成员向列表添加元素主要有三种方法它们添加的位置不同。1.append()方法在末尾追加这是最常用的添加方法就像在排队时新来的人总是站到队伍最后面。students [‘Alice’ ‘Bob’] students.append(‘Charlie’) print(students) # 输出 [‘Alice’ ‘Bob’ ‘Charlie’] students.append(‘David’) print(students) # 输出 [‘Alice’ ‘Bob’ ‘Charlie’ ‘David’]作用将单个元素添加到列表的末尾。特点原地修改列表不返回新列表。2.insert()方法在指定位置插入如果你不想让新元素总是排在最后而是想“插队”到特定位置就用insert()。students [‘Alice’ ‘Bob’ ‘David’] # 在索引为2的位置即‘David’前面插入‘Charlie’ students.insert(2 ‘Charlie’) print(students) # 输出 [‘Alice’ ‘Bob’ ‘Charlie’ ‘David’] # 可以在开头插入索引为0 students.insert(0 ‘Zoe’) print(students) # 输出 [‘Zoe’ ‘Alice’ ‘Bob’ ‘Charlie’ ‘David’]语法list.insert(index element)参数index是你要插入的位置的索引element是要插入的元素。原位置及后面的元素会自动后移。注意如果指定的索引超出了列表长度insert()会默认将元素添加到末尾。但依赖这个特性并不好最好明确你的插入意图。3.extend()方法合并另一个列表append()是加一个元素extend()是加一堆元素通常来自另一个列表。它会把参数列表中的每个元素逐个添加到原列表的末尾。group_a [‘Alice’ ‘Bob’] group_b [‘Charlie’ ‘David’] group_a.extend(group_b) print(group_a) # 输出 [‘Alice’ ‘Bob’ ‘Charlie’ ‘David’] print(group_b) # 输出 [‘Charlie’ ‘David’] (group_b本身没有变化)常见踩坑点append()vsextend()新手最容易混淆的就是append()和extend()。看一个例子就明白了list1 [1, 2, 3] list2 [4, 5, 6] list1.append(list2) print(list1) # 输出 [1, 2, 3 [4, 5, 6]] # list2作为一个整体子列表被加入了 list1 [1, 2, 3] # 重置list1 list1.extend(list2) print(list1) # 输出 [1, 2, 3, 4, 5, 6] # list2中的元素被“展开”加入了简单记append()是“加一个盒子”extend()是“打开盒子加里面的东西”。3.2 “删”如何清理列表中的元素删除同样有多种方式根据你想怎么删来选择。1.remove()方法按值删除当你不知道元素的索引但知道它的值时用remove()。pets [‘cat’ ‘dog’ ‘parrot’ ‘dog’ ‘fish’] pets.remove(‘dog’) print(pets) # 输出 [‘cat’ ‘parrot’ ‘dog’ ‘fish’]特点只删除第一个匹配到的值。如上例只删除了第一个‘dog’。注意如果要删除的值不在列表中会引发ValueError错误。安全做法是先判断if ‘dog’ in pets: pets.remove(‘dog’)。2.pop()方法按索引删除并“弹出”pop()非常有用它不仅能删除指定位置的元素还能把这个被删除的元素“返回”给你让你可以用变量接住它。queue [‘任务A’ ‘任务B’ ‘任务C’] # 删除并获取最后一个元素模拟队列或栈 completed_task queue.pop() print(completed_task) # 输出 任务C print(queue) # 输出 [‘任务A’ ‘任务B’] # 删除并获取指定索引的元素 first_task queue.pop(0) # 删除索引0的元素 print(first_task) # 输出 任务A print(queue) # 输出 [‘任务B’]语法list.pop([index])。index参数可选不写默认是-1最后一个元素。应用常用于实现栈后进先出用pop()或队列先进先出用pop(0)的行为。3.del语句直接删除del是Python的一个语句功能强大可以直接删除列表中的某个元素、某段切片甚至删除整个列表变量。data [10 20 30 40 50] # 删除索引为2的元素 del data[2] print(data) # 输出 [10, 20, 40, 50] # 删除整个列表 del data # print(data) # 这行会报错因为data这个变量已经不存在了与pop()的区别del只是删除不返回被删除的值。pop()会返回。4.clear()方法清空列表如果你想快速移除列表中的所有元素但保留这个空列表的“壳子”就用clear()。items [‘a’ ‘b’ ‘c’] items.clear() print(items) # 输出 []3.3 “改”如何更新列表中的值修改列表元素是最简单的操作直接通过索引赋值即可。fruits [‘apple’ ‘banana’ ‘cherry’] # 把索引1的位置从‘banana’改成‘blueberry’ fruits[1] ‘blueberry’ print(fruits) # 输出 [‘apple’ ‘blueberry’ ‘cherry’] # 甚至可以一次修改多个值结合后面的切片知识 fruits[0:2] [‘apricot’ ‘blackberry’] # 修改前两个元素 print(fruits) # 输出 [‘apricot’ ‘blackberry’ ‘cherry’]重要原则修改操作必须基于有效的索引否则会引发IndexError。3.4 “查”如何查找与判断“查”不仅仅是访问还包括判断元素是否存在、查找元素位置等。1. 使用in和not in判断成员关系这是非常高效和直观的判断方法。tools [‘hammer’ ‘screwdriver’ ‘wrench’] has_wrench ‘wrench’ in tools print(has_wrench) # 输出 True is_saw ‘saw’ not in tools print(is_saw) # 输出 True2.index()方法查找元素首次出现的索引如果元素存在index()返回其第一次出现的索引如果不存在则引发ValueError。numbers [10 20 30 20 40] pos numbers.index(20) print(pos) # 输出 1 (第一个20的位置) # 可以指定查找的起止范围 pos numbers.index(20 2) # 从索引2开始找 print(pos) # 输出 3 # 安全查找的惯用写法 target 50 if target in numbers: print(f“{target} 的索引是 {numbers.index(target)}“) else: print(f“{target} 不在列表中”)3.count()方法统计元素出现次数统计某个值在列表中出现了多少次。votes [‘A’ ‘B’ ‘A’ ‘C’ ‘A’ ‘B’] count_a votes.count(‘A’) print(count_a) # 输出 34. 列表的遍历与基础应用当我们有了一个装满数据的列表后最常见的需求就是“遍历”——逐个访问其中的每一个元素进行处理。4.1 使用for循环遍历列表这是最标准、最Pythonic的遍历方式。fruits [‘apple’ ‘banana’ ‘orange’] for fruit in fruits: # 每次循环fruit变量会依次被赋值为列表中的每个元素 print(f“我喜欢吃 {fruit}“) # 输出 # 我喜欢吃 apple # 我喜欢吃 banana # 我喜欢吃 orange这种写法清晰明了当你不需要元素的索引时这是首选。4.2 使用for循环与range(len())获取索引如果你在循环中既需要元素的值又需要它的索引位置可以使用range()和len()组合。fruits [‘apple’ ‘banana’ ‘orange’] for i in range(len(fruits)): # len(fruits)获取列表长度range生成索引序列 print(f“第{i}个水果是{fruits[i]}“) # 输出 # 第0个水果是apple # 第1个水果是banana # 第2个水果是orange4.3 更优雅的方式enumerate()函数enumerate()是Python内置函数它能把一个可迭代对象如列表组合成一个索引序列同时列出数据和数据下标。这是同时获取索引和值的推荐写法。fruits [‘apple’ ‘banana’ ‘orange’] for index fruit in enumerate(fruits): print(f“第{index}个水果是{fruit}“) # 输出与上面相同但代码更简洁4.4 基础应用案例计算列表平均值结合遍历和之前学的知识我们可以解决一个实际问题计算一个数字列表的平均值。这是数据分析中最基础的操作之一。# 假设有一个班级的成绩列表 scores [85 92 78 90 88] # 1. 计算总分 total 0 # 初始化总分变量 for score in scores: total score # 等价于 total total score # 2. 计算平均分 average total / len(scores) # 总分除以人数列表长度 print(f“总分是{total}“) print(f“平均分是{average:.2f}“) # :.2f 表示保留两位小数 # 输出总分是433 # 输出平均分是86.60实操心得理解“原地操作”在遍历列表时有一个非常重要的原则需要理解不要轻易在遍历过程中增删列表元素。因为这会导致列表长度和索引关系发生变化很容易引发意想不到的错误或遗漏处理某些元素。# 一个危险的例子想在遍历时删除所有偶数 numbers [1, 2, 3, 4, 5, 6] for num in numbers: if num % 2 0: numbers.remove(num) # 在遍历时删除元素危险 print(numbers) # 输出可能是 [1, 3, 5, 6] 不对实际输出可能是 [1, 3, 5] 让我们看看 # 实际运行结果可能是 [1, 3, 5]。为什么6没被删掉 # 因为删除2后列表变成[13,4,5,6]下一个迭代轮到索引2此时是4跳过了3删除了4...逻辑混乱。安全做法如果需要筛选可以创建一个新列表或者遍历原列表的副本。# 安全做法1创建新列表 numbers [1, 2, 3, 4, 5, 6] new_numbers [] for num in numbers: if num % 2 ! 0: # 保留奇数 new_numbers.append(num) print(new_numbers) # 输出 [1, 3, 5] # 安全做法2更Pythonic使用列表生成式下节课会细讲 new_numbers [num for num in numbers if num % 2 ! 0]5. 列表的常用内置函数与方法总结Python为列表提供了丰富的内置函数和方法它们能让你用更少的代码做更多的事。这里我们把最常用的整理一下。5.1 内置函数作用于列表这些是Python的内置函数以列表作为参数。函数作用示例结果len(list)返回列表长度元素个数len([1,2,3])3max(list)返回列表中最大值max([1,5,3])5min(list)返回列表中最小值min([1,5,3])1sum(list)返回列表中所有数值元素的和sum([1,2,3,4])10sorted(list)返回一个排序后的新列表原列表不变sorted([3,1,2])[1,2,3]特别注意sorted()与list.sort()的区别sorted(list)这是一个函数它返回一个新的排序后的列表原列表不会被改变。list.sort()这是一个方法它直接原地修改原列表使其有序不返回新列表返回None。a [3, 1, 2] b sorted(a) print(a) # 输出 [3, 1, 2] (a没变) print(b) # 输出 [1, 2, 3] (b是新的排序列表) a.sort() print(a) # 输出 [1, 2, 3] (a自己被修改了)5.2 列表对象的方法list.method()这些方法需要通过列表对象来调用格式为列表名.方法名()。方法作用是否原地修改list.append(x)在末尾添加元素x是list.insert(i, x)在索引i处插入元素x是list.extend(iterable)将可迭代对象中的元素扩展到列表末尾是list.remove(x)删除第一个值为x的元素是list.pop([i])删除并返回索引i处的元素默认最后一个是list.clear()移除所有元素是list.index(x[, start[, end]])返回第一个值为x的元素的索引否list.count(x)统计x出现的次数否list.sort(keyNone, reverseFalse)对列表进行原地排序是list.reverse()将列表元素原地反转是list.copy()返回列表的一个浅拷贝否关于copy()的深度解析list.copy()或list[:]切片进行的是浅拷贝。对于只包含不可变类型数字、字符串、元组的列表浅拷贝完全够用。但如果列表嵌套了其他可变对象如子列表浅拷贝只拷贝了外层引用修改内层可变对象会影响原列表。# 浅拷贝的例子 import copy original [[1, 2], [3, 4]] shallow_copy original.copy() # 或 original[:] # 修改浅拷贝的外层列表不影响原列表 shallow_copy.append([5, 6]) print(original) # 输出 [[1, 2] [3, 4]] print(shallow_copy) # 输出 [[1, 2] [3, 4] [5, 6]] # 修改浅拷贝的内层子列表原列表也会被影响 shallow_copy[0][0] ‘X’ print(original) # 输出 [[‘X’ 2] [3, 4]] # 原列表也被改了 print(shallow_copy) # 输出 [[‘X’ 2] [3, 4] [5, 6]]如果需要完全独立的拷贝包括内层所有对象需要使用copy模块的deepcopy()函数。import copy original [[1, 2], [3, 4]] deep_copy copy.deepcopy(original) deep_copy[0][0] ‘X’ print(original) # 输出 [[1, 2] [3, 4]] (未受影响) print(deep_copy) # 输出 [[‘X’ 2] [3, 4]]6. 常见问题与排查技巧实录在实际编码中你肯定会遇到各种和列表相关的报错和疑惑。这里我整理了几个最常见的问题和解决方法很多都是我早期踩过的坑。6.1 索引错误IndexError: list index out of range这是新手遇到最多的错误没有之一。问题现象当你尝试访问一个不存在的索引时Python会抛出这个错误。my_list [‘a’ ‘b’ ‘c’] print(my_list[5]) # IndexError: list index out of range排查与解决检查列表长度使用len(my_list)确认列表到底有多少个元素。记住索引范围是0到len(my_list)-1。检查循环条件如果在循环中出错检查循环变量是否可能超出范围。例如for i in range(len(my_list)1)就会导致最后一次循环ilen(my_list)从而出错。使用负索引时小心负索引-1是有效的但-len(my_list)-1就无效了。例如对于一个长度为3的列表有效负索引是-1 -2, -3-4就会报错。防御性编程在访问索引前先判断。index_to_access 5 if 0 index_to_access len(my_list): print(my_list[index_to_access]) else: print(f“索引 {index_to_access} 超出范围”)6.2 值错误ValueError: list.remove(x): x not in list当使用remove()方法删除一个不存在的值时会引发此错误。解决方案 在调用remove()之前先用in关键字判断值是否存在。my_list [1, 2, 3] value_to_remove 4 if value_to_remove in my_list: my_list.remove(value_to_remove) else: print(f“值 {value_to_remove} 不在列表中无法删除。”)6.3 修改遍历中的列表导致逻辑错误如前所述在for item in list:循环中直接对list进行append()、remove()、pop()等操作会打乱迭代器的预期行为可能导致元素被跳过或处理异常。解决方案创建新列表遍历原列表将需要保留的元素添加到一个新列表中。遍历副本使用切片list[:]创建原列表的副本进行遍历然后修改原列表。numbers [1, 2, 3, 4, 5, 6] for num in numbers[:]: # 遍历的是numbers的副本 if num % 2 0: numbers.remove(num) # 删除的是原numbers中的元素 print(numbers) # 输出 [1, 3, 5]使用列表生成式推荐下节课重点讲这是最简洁、高效的方式。numbers [1, 2, 3, 4, 5, 6] numbers [num for num in numbers if num % 2 ! 0] # 直接生成一个新列表赋值回去 print(numbers) # 输出 [1, 3, 5]6.4 列表赋值与拷贝的陷阱这是另一个容易混淆的核心概念。问题list_b list_a这并不是创建了一个新列表而是让list_b和list_a指向了内存中的同一个列表对象。list_a [1, 2, 3] list_b list_a # 这不是拷贝是引用赋值 list_b.append(4) print(list_a) # 输出 [1, 2, 3, 4] # list_a也被改了解决方案 当你需要一份独立的、修改时不影响原列表的副本时必须使用拷贝。浅拷贝使用copy()方法或切片[:]。适用于元素都是不可变类型的简单列表。list_b list_a.copy() # 或 list_b list_a[:]深拷贝使用copy.deepcopy()。适用于嵌套了其他可变对象如列表中的列表的复杂结构。import copy list_b copy.deepcopy(list_a)6.5 性能小贴士虽然对于初学者和小数据量来说性能差异不大但了解这些习惯有助于你写出更专业的代码。在列表头部频繁插入/删除 (insert(0 x),pop(0)) 是低效的因为列表在内存中是连续存储的在头部操作需要移动后面所有元素。如果需要频繁在两端操作考虑使用collections.deque双端队列。判断元素是否存在用in操作符它很直观对于列表其时间复杂度是O(n)即需要遍历查找。对于非常大的列表如果频繁查找考虑使用set集合它的in操作平均是O(1)复杂度。append()通常比insert()快因为append()只在末尾操作而insert()可能需要移动元素。列表是Python的基石花时间把这些基础操作练到形成肌肉记忆绝对值得。我建议你打开Python解释器或者你的IDE把上面的每一个例子都亲手敲一遍再尝试用自己的数据做一些组合练习。比如创建一个购物清单列表练习添加商品、删除已购买的商品、修改商品数量、计算总价等。下节课我们会深入列表更强大的特性切片、列表生成式和更多内置方法你会发现用列表处理数据会变得无比优雅和高效。