
1. 项目概述为什么列表是Python入门的“定海神针”如果你刚开始学Python学到第八课恭喜你你已经跨过了最基础的语法门槛。但很多新手会在这个阶段遇到一个瓶颈感觉语法都会了但一上手写点稍微复杂的东西比如处理一堆用户数据、管理多个文件路径、或者分析一系列成绩就不知道从何下手代码写得又长又乱。这个瓶颈的根源往往就在于对“数据结构”的理解和运用不到位。而列表List作为Python中最基础、最灵活、最常用的数据结构就是你突破这个瓶颈、真正开始用Python“做事”的钥匙。它不是冷冰冰的术语而是你编程工具箱里最趁手的那把螺丝刀。简单来说列表就是一个“可以装很多东西的、有顺序的、能随时修改的容器”。想象一下你去超市购物手里拿的那个购物篮。你可以往篮子里放苹果、放牛奶、放饼干不同类型的数据并且你放进去的顺序先放苹果还是先放牛奶是被记住的。结账时你可以随时把某件商品拿出来删除或者临时再加一包糖添加。这个购物篮就是列表的一个完美比喻。在Python里我们用一对方括号[]来表示这个篮子里面的东西用逗号隔开。比如shopping_cart [‘苹果‘, ‘牛奶‘, 12.5, True]这个列表里就混合了字符串、浮点数和布尔值。为什么我要花一整课甚至不止一节课来讲列表因为它在实际项目中的出场率实在太高了。爬虫抓取的数据往往先存成列表机器学习的数据预处理大量操作都在列表或类列表结构如NumPy数组上进行自动化脚本里读取文件、管理任务队列列表是核心。甚至你看网络热词里“python 列表 自动化 example”、“python 列表 平均 完整代码”、“列表切片”这些高频搜索都指向了同一个事实大家在实际操作中遇到了列表相关的问题急需解决方案。掌握了列表你才算是真正摸到了Python实用编程的门道。这节课我们不搞花架子就从最根本的创建、访问和基础操作讲起让你能把列表这个工具稳稳地握在手里。2. 列表的创建与访问你的第一个“数据集装箱”2.1 创建列表的四种常规姿势创建列表就像准备一个空篮子或者往篮子里预先放点东西。方法非常直观1. 使用方括号[]直接创建这是最常用、最快捷的方式。# 创建一个空列表 empty_list [] print(empty_list) # 输出: [] # 创建一个包含元素的列表 fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘, ‘葡萄‘] numbers [1, 2, 3, 4, 5] mixed_bag [‘Python‘, 3.14159, True, 42] # 可以混合不同类型注意虽然列表能容纳不同类型的数据但在实际工程中除非有特殊需求比如记录一条结构复杂的日志否则我们通常会让一个列表里的元素类型保持一致例如全是字符串或全是数字。这有利于后续的数据处理避免因类型不一致导致的意外错误。2. 使用list()构造函数创建list()函数可以将其他可迭代对象如字符串、元组、字典的键等转换成列表。# 将字符串的每个字符变成列表元素 word_list list(‘hello‘) print(word_list) # 输出: [‘h‘, ‘e‘, ‘l‘, ‘l‘, ‘o‘] # 将元组转换成列表 a_tuple (1, 2, 3) list_from_tuple list(a_tuple) print(list_from_tuple) # 输出: [1, 2, 3] # 创建一个空列表与 [] 等效 another_empty_list list() print(another_empty_list) # 输出: []list()在需要转换数据格式时特别有用。比如你从某个函数得到一个元组但你需要修改其中的内容就必须先把它转成列表。3. 使用列表推导式创建先混个脸熟这是一种更高级、更Pythonic的创建方式特别适合基于现有序列生成新列表。这里先简单介绍后续课程会深入。# 生成一个0到9的平方数列表 squares [x**2 for x in range(10)] print(squares) # 输出: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] # 从一个字符串列表中筛选出长度大于3的字符串 words [‘cat‘, ‘window‘, ‘defenestrate‘] long_words [w for w in words if len(w) 3] print(long_words) # 输出: [‘window‘, ‘defenestrate‘]4. 通过乘法*运算符快速创建重复元素的列表这在需要初始化一个具有相同默认值的列表时很方便但要慎用尤其是当元素是可变对象时后续讲到可变性时会详细解释。# 创建5个0组成的列表常用于初始化计数器 zeros [0] * 5 print(zeros) # 输出: [0, 0, 0, 0, 0] # 创建3个相同字符串的列表 greetings [‘Hi‘] * 3 print(greetings) # 输出: [‘Hi‘, ‘Hi‘, ‘Hi‘]2.2 访问列表元素索引与切片篮子里的东西放好了怎么拿出来看呢靠的是“索引”。索引Indexing可以理解为每个元素在列表中的“座位号”。关键点在于Python的索引是从0开始计数的而不是1。第一个元素的索引是0第二个是1以此类推。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘, ‘葡萄‘] print(fruits[0]) # 输出: ‘苹果‘ print(fruits[1]) # 输出: ‘香蕉‘ print(fruits[2]) # 输出: ‘橙子‘如果你想从列表末尾开始访问可以使用负索引。-1 表示最后一个元素-2 表示倒数第二个非常方便。print(fruits[-1]) # 输出: ‘葡萄‘ (最后一个) print(fruits[-2]) # 输出: ‘橙子‘ (倒数第二个)切片Slicing如果你想要篮子里的某一段“连续”的东西而不是单个就需要切片。切片操作使用冒号:分隔起始索引、结束索引和步长格式为list[start:stop:step]。它的规则是“含头不含尾”即包含起始索引的元素但不包含结束索引的元素。numbers [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 获取索引1到4不含4的元素 print(numbers[1:4]) # 输出: [1, 2, 3] # 从头开始到索引5不含5 print(numbers[:5]) # 输出: [0, 1, 2, 3, 4] # 从索引5开始到末尾 print(numbers[5:]) # 输出: [5, 6, 7, 8, 9] # 获取整个列表的副本一种浅拷贝方式 print(numbers[:]) # 输出: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 使用步长每隔一个取一个元素 print(numbers[::2]) # 输出: [0, 2, 4, 6, 8] # 使用负步长实现列表反转这是一个非常优雅的技巧 print(numbers[::-1]) # 输出: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]实操心得切片操作不会修改原列表而是返回一个新的列表。numbers[:]是获取列表完整副本的常见写法。而numbers[::-1]是反转列表最简洁高效的方法之一比使用reversed()函数再转列表在某些场景下更直观。索引越界错误这是新手最常见的错误之一。如果你尝试访问一个不存在的索引Python会抛出IndexError。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘] # print(fruits[3]) # 这行如果执行会报错IndexError: list index out of range # print(fruits[-4]) # 同样会报错避免越界的关键是清楚列表的长度。可以使用内置函数len()来获取。length len(fruits) # length 3 # 因此合法的索引范围是 0, 1, 2 或 -1, -2, -33. 列表的核心操作增、删、改、查创建和访问是基础真正让列表“活”起来的是动态修改它的能力。这对应着数据的增、删、改、查四大基本操作。3.1 增向列表中添加元素有几种方法可以把新东西放进篮子。1.append()方法在列表末尾添加单个元素。这是最常用的添加方式效率很高。fruits [‘苹果‘, ‘香蕉‘] fruits.append(‘橙子‘) print(fruits) # 输出: [‘苹果‘, ‘香蕉‘, ‘橙子‘]append()是“原地修改”操作它直接改变原列表而不是返回一个新列表。2.insert()方法在指定索引位置插入元素。insert(i, x)表示在索引i的位置插入元素x原来该位置及之后的元素都向后移动一位。fruits.insert(1, ‘芒果‘) # 在索引1‘香蕉’之前插入 print(fruits) # 输出: [‘苹果‘, ‘芒果‘, ‘香蕉‘, ‘橙子‘]注意如果指定的索引超出了列表长度insert()会默认将元素添加到末尾。例如对一个长度为3的列表执行insert(10, ‘x‘)‘x‘会被加到索引3的位置即末尾而不是报错。3.extend()方法将另一个可迭代对象的所有元素添加到列表末尾。可以理解为把另一个篮子里的东西全部倒进当前篮子。fruits [‘苹果‘, ‘香蕉‘] more_fruits [‘橙子‘, ‘葡萄‘, ‘西瓜‘] fruits.extend(more_fruits) print(fruits) # 输出: [‘苹果‘, ‘香蕉‘, ‘橙子‘, ‘葡萄‘, ‘西瓜‘]extend()和用运算符连接列表 (fruits more_fruits) 效果类似但关键区别在于extend()是原地修改原列表而会创建一个全新的列表原列表不变。在处理大数据量时这个区别会影响性能和内存使用。4. 使用运算符连接列表正如上面提到的会生成一个新列表。list1 [1, 2] list2 [3, 4] new_list list1 list2 print(new_list) # 输出: [1, 2, 3, 4] print(list1) # 输出: [1, 2] (原列表未变) print(list2) # 输出: [3, 4] (原列表未变)3.2 删从列表中移除元素东西放多了或者没用了就需要拿出来。1.remove()方法移除列表中第一个匹配到的指定值的元素。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘, ‘香蕉‘] fruits.remove(‘香蕉‘) print(fruits) # 输出: [‘苹果‘, ‘橙子‘, ‘香蕉‘]注意它只移除第一个遇到的‘香蕉‘。如果值不存在会引发ValueError。2.pop()方法移除并返回指定索引位置的元素。如果不指定索引默认移除并返回最后一个元素。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘] last_fruit fruits.pop() # 移除并返回‘橙子‘ print(last_fruit) # 输出: ‘橙子‘ print(fruits) # 输出: [‘苹果‘, ‘香蕉‘] second_fruit fruits.pop(0) # 移除并返回索引0的元素‘苹果‘ print(second_fruit) # 输出: ‘苹果‘ print(fruits) # 输出: [‘香蕉‘]pop()非常有用特别是在实现栈后进先出LIFO或队列先进先出FIFO但用列表做队列效率不高数据结构时。3.del语句根据索引或切片删除元素不返回被删除的值。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘, ‘葡萄‘, ‘西瓜‘] del fruits[2] # 删除索引为2的元素 ‘橙子‘ print(fruits) # 输出: [‘苹果‘, ‘香蕉‘, ‘葡萄‘, ‘西瓜‘] del fruits[1:3] # 删除切片 fruits[1:3] 即 ‘香蕉‘, ‘葡萄‘ print(fruits) # 输出: [‘苹果‘, ‘西瓜‘] # 甚至可以删除整个列表变量 del fruits # print(fruits) # 这里再访问fruits会报 NameError4.clear()方法清空列表中的所有元素使其变为空列表。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘] fruits.clear() print(fruits) # 输出: []3.3 改修改列表中的元素修改元素是最直接的操作直接通过索引赋值即可。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘] fruits[1] ‘芒果‘ # 将索引1的元素从‘香蕉‘改为‘芒果‘ print(fruits) # 输出: [‘苹果‘, ‘芒果‘, ‘橙子‘] # 也可以通过切片批量修改 numbers [1, 2, 3, 4, 5] numbers[1:4] [20, 30, 40] # 将索引1到3的元素替换为新列表 print(numbers) # 输出: [1, 20, 30, 40, 5] # 甚至可以用长度不同的列表来替换切片 numbers[1:4] [200, 300] # 替换后列表长度会变化 print(numbers) # 输出: [1, 200, 300, 5]3.4 查查找与判断元素1.index()方法返回指定值第一次出现的索引。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘, ‘香蕉‘] banana_index fruits.index(‘香蕉‘) print(banana_index) # 输出: 1如果值不存在会引发ValueError。可以指定起始和结束搜索范围list.index(x, start, end)。2.count()方法统计某个值在列表中出现的次数。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘, ‘香蕉‘] banana_count fruits.count(‘香蕉‘) print(banana_count) # 输出: 2 apple_count fruits.count(‘西瓜‘) print(apple_count) # 输出: 03.in和not in运算符检查某个值是否存在于列表中。这是非常高效且常用的成员检查方法。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘] print(‘香蕉‘ in fruits) # 输出: True print(‘西瓜‘ not in fruits) # 输出: True通常使用in比先index()再捕获异常的方式更优雅、更Pythonic。4. 列表的排序与反转让数据井然有序处理数据时我们经常需要排序或调整顺序。4.1 永久性排序sort()方法list.sort()方法会对列表进行原地排序也就是直接修改原列表。它有两个常用参数reverse: 默认为False升序排序。设为True则为降序排序。key: 一个函数用于从每个元素中提取比较键例如按字符串长度排序。numbers [3, 1, 4, 1, 5, 9, 2] numbers.sort() print(numbers) # 输出: [1, 1, 2, 3, 4, 5, 9] (升序) numbers.sort(reverseTrue) print(numbers) # 输出: [9, 5, 4, 3, 2, 1, 1] (降序) fruits [‘banana‘, ‘apple‘, ‘cherry‘, ‘date‘] fruits.sort() # 默认按字母顺序ASCII/Unicode排序 print(fruits) # 输出: [‘apple‘, ‘banana‘, ‘cherry‘, ‘date‘] # 按字符串长度排序 fruits.sort(keylen) print(fruits) # 输出: [‘date‘, ‘apple‘, ‘banana‘, ‘cherry‘] (注意长度相同时原顺序可能保留)4.2 临时性排序sorted()函数内置函数sorted()会返回一个新的已排序列表而原列表保持不变。参数与sort()相同。numbers [3, 1, 4, 1, 5, 9, 2] sorted_numbers sorted(numbers) print(sorted_numbers) # 输出: [1, 1, 2, 3, 4, 5, 9] print(numbers) # 输出: [3, 1, 4, 1, 5, 9, 2] (原列表未变) # 同样支持 reverse 和 key 参数 sorted_fruits_desc sorted(fruits, reverseTrue, keylen)选择建议如果你需要保留原列表的顺序或者排序后的列表只是用于临时展示或计算用sorted()。如果你确定要永久改变列表的顺序用sort()它通常比sorted()稍微节省一点内存因为不需要创建新列表。4.3 列表反转reverse()方法与切片技巧reverse()方法原地反转列表元素的顺序。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘] fruits.reverse() print(fruits) # 输出: [‘橙子‘, ‘香蕉‘, ‘苹果‘]切片技巧[::-1]如前所述这是获取一个反转后新列表的简洁方法不改变原列表。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘] reversed_fruits fruits[::-1] print(reversed_fruits) # 输出: [‘橙子‘, ‘香蕉‘, ‘苹果‘] print(fruits) # 输出: [‘苹果‘, ‘香蕉‘, ‘橙子‘] (原列表未变)5. 列表的复制浅拷贝与深拷贝的“陷阱”这是列表操作中一个至关重要的概念也是新手最容易栽跟头的地方。直接赋值 () 并不会创建一个新的列表它只是给原有的列表起了个“新名字”创建了一个新的引用。list_a [1, 2, 3] list_b list_a # 这不是复制这是让 list_b 和 list_a 指向同一个列表对象 list_b.append(4) print(list_a) # 输出: [1, 2, 3, 4] !!! list_a 也被改变了 print(list_b) # 输出: [1, 2, 3, 4]这显然不是我们想要的。我们需要的是“拷贝”。拷贝分为两种1. 浅拷贝 (Shallow Copy)只拷贝列表对象本身如果列表元素是可变对象如嵌套列表则拷贝的是这些元素的引用。 创建浅拷贝的方法使用切片list_b list_a[:]使用copy()方法list_b list_a.copy()使用list()构造函数list_b list(list_a)list_a [1, 2, [3, 4]] list_b list_a.copy() # 浅拷贝 list_b.append(5) list_b[2][0] ‘changed‘ print(list_a) # 输出: [1, 2, [‘changed‘, 4]] # 注意嵌套列表被修改了 print(list_b) # 输出: [1, 2, [‘changed‘, 4], 5]可以看到浅拷贝后修改list_b的第一层添加5不影响list_a但修改第二层嵌套列表却影响了list_a因为两者共享了同一个嵌套列表的引用。2. 深拷贝 (Deep Copy)创建一个全新的、完全独立的列表副本包括所有嵌套的可变对象。需要使用copy模块的deepcopy()函数。import copy list_a [1, 2, [3, 4]] list_b copy.deepcopy(list_a) # 深拷贝 list_b[2][0] ‘changed‘ print(list_a) # 输出: [1, 2, [3, 4]] # 完全不受影响 print(list_b) # 输出: [1, 2, [‘changed‘, 4]]踩坑实录在处理包含复杂嵌套结构列表套列表、列表套字典的数据时如果不假思索地使用浅拷贝后续的修改可能会引发难以察觉的bug。我的经验法则是只要不确定或者数据结构有嵌套就直接用copy.deepcopy()。虽然性能上略有开销但能彻底避免引用共享带来的副作用代码更安全。6. 列表的遍历与循环的完美结合列表的威力在于它能和循环尤其是for循环无缝配合批量处理数据。基本遍历fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘] for fruit in fruits: # 直接遍历元素 print(f‘我喜欢吃{fruit}‘)这是最常用、最直观的方式。需要索引的遍历使用enumerate()函数当你既需要元素值又需要其索引时比如在循环内部需要根据索引做判断或修改enumerate()是你的好帮手。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘] for index, fruit in enumerate(fruits): print(f‘第{index}个水果是{fruit}‘) # 输出: # 第0个水果是苹果 # 第1个水果是香蕉 # 第2个水果是橙子enumerate()还可以指定起始索引for i, item in enumerate(my_list, start1):使用while循环遍历虽然不如for循环常用但在需要根据复杂条件动态修改列表时比如循环中删除元素while循环结合索引可能更可控。fruits [‘苹果‘, ‘香蕉‘, ‘橙子‘, ‘葡萄‘] i 0 while i len(fruits): if len(fruits[i]) 2: # 假设我们想删除名字长度大于2的水果举例 # 这里可以进行删除操作但要小心索引变化 pass i 1重要警告在for循环中直接遍历列表并删除元素是危险的因为这会改变列表长度和索引可能导致跳过元素或越界。安全的做法是创建一个原列表的副本进行遍历或者在while循环中从后往前处理。这是列表操作中的一个经典坑。7. 列表推导式优雅与高效的代名词列表推导式是Python语言的一大特色它提供了一种简洁、高效创建新列表的方法。其基本语法是[expression for item in iterable if condition]它等价于下面这个for循环new_list [] for item in iterable: if condition: new_list.append(expression)看几个例子就明白了# 1. 生成平方数列表 squares [x**2 for x in range(10)] print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] # 2. 过滤列表只保留偶数 numbers [1, 2, 3, 4, 5, 6, 7, 8, 9] evens [x for x in numbers if x % 2 0] print(evens) # [2, 4, 6, 8] # 3. 处理字符串列表将所有单词转为大写 words [‘hello‘, ‘world‘, ‘python‘] upper_words [word.upper() for word in words] print(upper_words) # [‘HELLO‘, ‘WORLD‘, ‘PYTHON‘] # 4. 嵌套循环的推导式生成坐标对 points [(x, y) for x in range(3) for y in range(2)] print(points) # [(0,0), (0,1), (1,0), (1,1), (2,0), (2,1)]列表推导式不仅代码更简洁而且在CPython解释器下其执行速度通常比等效的for循环稍快因为循环是在解释器内部以C语言的速度执行的。使用建议列表推导式非常强大但也要避免滥用。如果推导式逻辑过于复杂比如嵌套多层、条件判断很多为了可读性还是应该拆分成普通的for循环。记住代码是写给人看的其次才是给机器执行的。列表是Python数据结构的基石它的这些基础操作——创建、访问、增删改查、排序、复制、遍历——构成了你处理有序数据集合的全部工具箱。熟练掌握它们你就能应对日常编程中80%以上的数据组织需求。在下一课我们将深入列表更高级的特性如列表与函数、可变对象与不可变对象的本质区别以及如何利用列表实现一些常见算法让你的Python技能再上一个台阶。现在最好的学习方式就是打开你的代码编辑器把上面的每一个例子都敲一遍并尝试修改它们看看会发生什么。编程是一门实践的手艺。