作为写 Python 写了十几年的老开发我几乎每天都在跟列表打交道。它可能是 Python 里最不起眼、但也是最常被用到的数据结构小到临时存几个数值大到处理几十万行的数据清洗列表都跑不掉。今天这篇东西我不打算从文档角度把每个方法念一遍而是打算从实际使用场景出发把列表的创建、切片、增删改查、推导式、嵌套处理、去重排序这些操作串起来讲清楚。这篇内容适合刚学完 Python 基础语法、想系统把列表吃透的人也适合写了一阵子代码但碰到切片、深浅拷贝、遍历删除这些老坑就发怵的初级程序员。看完之后你至少能收获几套可以直接抄到项目里用的列表处理方案也知道列表元素的内存布局是怎么回事、为什么拷贝列表要分深浅、为什么排序时sort()和sorted()身世不同。放心我会把每一步代码都贴出来连输出结果都一并写了。1. 列表的本质与设计思路1.1 为什么列表是 Python 的“默认容器”很多新手一开始会把列表当成一个“能装东西的大盒子”这个类比方向没问题但其实漏了最核心的一点列表在 Python 里的定位是“动态数组”。它在底层维护的是一段连续的内存空间同时预留了额外的容量所以它既支持按下标快速访问元素时间复杂度 O(1)又支持随时追加和删除元素。这一点决定了它跟元组、集合、字典这些容器的根本性区别——列表是唯一一个既保持插入顺序、又可变、又能用整数下标直接定位的内置序列类型。理解“动态数组”这个本质后很多操作就变得好解释了。比如为什么append一个元素通常很快偶尔却很慢因为当预留空间不够时Python 会重新申请一块更大的内存并把旧数据整体搬过去这一瞬间它会顿一下。再比如为什么list.insert(0, x)的性能明显比list.append(x)差因为头部插入会让后面所有元素整体后移一位复杂度是 O(n)。这些底层机制听着抽象但放到生产环境里会直接影响程序速度。我曾经用列表造过一个实时数据缓冲高峰期每秒往里插几千条一开始用了insert(0, ...)结果 CPU 直线飙升。所以看待列表不应该只看它“能不能装”还要看它“怎么装”“装得多不多”。用什么样的容器、用什么样的操作本质是一场时间与空间的权衡游戏。对列表而言绝大多数场景下它是够用的尤其是数据量在几十万以内的时候随便折腾都不会太慢。但一旦数据量上了千万级你就该停下来想想是否该换成array、numpy或者直接上数据库了。1.2 列表的索引机制与内存布局列表的元素在内存中并不是直接存数据本身而是存指向数据对象的引用。你可以把列表想象成一张座位表每个座位上都贴了一张纸条纸条写的是“数据放在哪个房间”。当你执行lst[2]时Python 做的事情是根据偏移量快速找到第三个座位读出纸条上的房间号然后去房间里把真正的数据取出来。这就是为什么列表可以混装整数、字符串、对象等各种类型——因为列表本身不关心房间里装的是什么它只管理纸条。这个设计带来的直接后果是列表的内存开销比较高。每个元素不仅要存数据本身还要多存一个 8 字节左右的指针引用。如果你的数据都是整数那么一个长度为 100 万的列表光指针部分就要多占约 8MB 内存。对于一般场景这无所谓但如果你在写内存敏感的脚本比如爬虫里缓存几百万条 URL就要考虑用array(u)或tuple来压缩内存。另外理解“列表存引用”对后面讲深拷贝至关重要。你往列表里放进一个可变对象比如字典或另一个列表复制列表时如果只复制了座位表那新旧两个座位表上的纸条指向的还是同一批房间改一个就会联动另一个。这也是无数新手最初踩进深浅拷贝坑的根本原因。2. 创建列表从基础写法到快速生成的多种姿势2.1 直接用字面量创建最朴素的创建方式就是用方括号包住一组元素这个不用多说。但我要强调的是空列表[]和“包含一个空列表的列表”[[]]完全是两回事后者是一个长度为 1、唯一元素是空列表的列表。很多人写算法题时想把结果集初始化成[[]]去收集组合结果却不小心写成了[] * 3得到的三个元素其实是同一个空列表的引用后面一改全改一脸懵。# 三个独立空列表 lst1 [[], [], []] # 三个指向同一对象的引用强烈不建议 lst2 [[] for _ in range(3)] # 正确做法 lst3 [] * 3 # 得到空列表因为 [] 里没有任何元素这里有新手容易混的点[0] * 5会得到[0, 0, 0, 0, 0]这个操作看着像把数字复制了五份其实也是建了五个指向同一个整数对象的引用。但因为整数是不可变对象你改一个不会影响另一个所以感觉上没什么毛病。上面提到的[[]] * 3之所以危险是因为列表是可变对象引用共享的破坏性立刻就能体现出来。实操中我建议大家记住一个口诀想创建一堆彼此完全独立的容器永远用推导式别用乘法。2.2 用 range 和字符串创建列表列表最常见的批量生产方式就是把range转成列表list(range(10))会得到 0 到 9 的整数列。range本身是个惰性序列只有在转成list时才会一次性把所有元素算出来。在 Python 3 里range不会像 Python 2 那样立刻生成全部数字这也是为什么大范围range(100000000)不会占内存但list(range(100000000))会瞬间吃满内存。从字符串切分创建列表也极其常用尤其是做文本处理时words hello world python list.split() # [hello, world, python, list] line a,b,c,d items line.split(,) # [a, b, c, d]你可能觉得这太基础了但我想提醒一个坑split()不带参数时会按任意空白字符切分并且自动过滤空串。带参数时比如按逗号它不会过滤连续分隔符产生的空字符串。所以a,,b.split(,)得到的是[a, , b]而不是[a, b]。如果 CSV 数据里有很多连续的逗号不加处理就直接用你会在后续逻辑里拿到一堆空字符串排查半天才发现源头在这。2.3 从生成器与推导式创建列表从文件读取行、从函数返回值里构建列表这类需求不胜枚举。最优雅的写法是用列表推导式squares [x * x for x in range(10)] # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] even [x for x in range(20) if x % 2 0] # [0, 2, 4, ..., 18]列表推导式的执行顺序是“从左往右嵌套理解”——先遍历for后面的部分再执行前面的表达式遇到if则先做筛选。如果推导式里面再套一个for那就是嵌套循环的展开后面我会专门讲。这里我要提一个经验列表推导式虽然优雅但不要太贪心。一个表达式里塞进三个for和两个if读起来简直灾难。遇到这种情况我宁可拆成普通循环或者用辅助函数包装逻辑牺牲一点“看起来很高端”的炫耀感换代码的可维护性。3. 索引与切片列表最强大的操作没有之一3.1 索引的灵活用法列表索引支持正数和负数。lst[0]是第一个元素lst[-1]是最后一个元素。这个很多教材都会写但我要强调的是“链式索引”的应用。如果你有个嵌套列表例如一个 3x3 的矩阵matrix [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ] matrix[1][2] # 6matrix[1]取出第二行再往后[2]取出该行的第三个元素。在图像处理、表格数据处理中这套链式索引是家常便饭。但请注意链式索引只能用于“确实嵌套”的列表如果某个元素不是列表再往下取索引就会立刻得到TypeError: int object is not subscriptable。这个报错初学者几乎每天都要见一次。另一个容易忽略的是Python 的索引没有越界保护。访问lst[100]会直接抛IndexError。这在写 C 或 Java 转过来的程序员看来有点“不友好”但其实是为了保持简单性的刻意设计。想要安全取值可以用lst[100] if len(lst) 100 else default或者干脆用 Python 3.10 以后支持的三元写法。3.2 切片语法精讲切片算是列表的灵魂操作。lst[start:stop:step]的规则是从start开始到stop之前结束间隔step取元素。start省略默认从开头stop省略默认到末尾step省略默认为 1。我整理了一份速查表实际开发里我几乎每次写切片都要在脑子里过一遍边界规则表达式结果含义lst[:]复制整个列表浅拷贝lst[::-1]整个列表反转lst[1:]去掉第一个元素lst[:-1]去掉最后一个元素lst[::2]取所有偶数位置元素lst[1::2]取所有奇数位置元素lst[-3:]取最后三个元素切片里最容易犯的错误集中在stop边界上。lst[1:3]取出的索引是 1 和 2不包含 3。这个“左闭右开”规则跟range是一致的Python 里几乎所有区间操作都遵循这个约定包括range(5)生成 0 到 4。你要是习惯性把 stop 当成“包含这个位置”写出来的切片十有八九会多取或少取一个元素。步长为负数时规则稍微绕一点。lst[5:0:-1]是从索引 5 倒着走到索引 1索引 0 不被包含。如果你要完整逆序包含所有元素直接lst[::-1]最省心。用负步长时千万不要再配上正向的 start/stop 理解容易越绕越晕我建议直接实验两三次就记住了。切片还有个特别强大的隐藏功能切片赋值。这不是读取切片而是把切片作为一个整体替换lst [1, 2, 3, 4, 5] lst[1:3] [20, 30, 40] # [1, 20, 30, 40, 4, 5]这个操作可以把一个区间整体替换成任意长度的序列。我当年写一个荷兰国旗类分组算法时用切片赋值一行代码就完成了分区同事看我推代码时惊了。切片赋值甚至支持用空列表清空区间lst[1:3] []就相当于删除了这个区间的元素。反过来也可以往一个空切片里塞元素实现“插入”效果lst[1:1] [a, b]等价于在索引 1 处插入两个元素。3.3 切片与普通复制的区别很多人在返回列表时习惯写return lst但这其实返回的是引用。如果调用方后续修改了返回结果原列表也会跟着变。想要返回一份独立拷贝应该写return lst[:]或return lst.copy()。这就是我在这一节开头说的“引用 vs 拷贝”问题在函数式编程里属于最容易出 bug 的一类。不过要注意lst[:]和lst.copy()都是浅拷贝。如果列表里嵌套了可变对象浅拷贝只是复制了外层座椅表里层房间还是共享的。想实现完全独立的深拷贝需要引入copy模块的deepcopy。但是深拷贝性能开销很大普通场景不要乱用能用切片放心就用切片。4. 增删改查的完整方法论4.1 添加元素append、extend、insert 的取舍append把参数作为一个整体放入列表末尾extend则是把一个可迭代对象里的每个元素依次放入列表末尾。这两个的区别是许多新手遇到的第一道分水岭lst [1, 2] lst.append([3, 4]) # [1, 2, [3, 4]] lst.extend([5, 6]) # [1, 2, 5, 6]append([3, 4])得到的是一个长度为 3 的列表最后一个元素是列表本身extend会把传入的列表拆开元素逐个追加。这两个如果混用后续取数据时往往就会发生“元素不匹配”的诡异现象。insert(index, element)可以在任意指定位置插入元素。我在前面说过头部插入性能差因为后面的元素都要整体移位。如果需求是频繁在头部添加建议改用collections.deque它的左右两端操作都是 O(1)。这个性能差异在数据量大到百万级别时会非常明显。一般来说构建列表时如果你已知最终长度提前用[0] * n占位再按下标赋值比反复append要快一点因为省去了扩容和重分配的开销。这种细节优化对爬虫批量拉取 JSON 后组装数据很有用。4.2 删除元素remove、pop、del 的适用边界remove(value)按值删除只删除找到的第一个匹配项pop(index)按下标删除并返回该元素del lst[index]按下标删除但不返回值clear()一次性清空整个列表。四者的适用场景完全不同。remove最典型的一个坑是列表里根本没有这个值时会抛ValueError。如果你的场景里待删除元素可能不存在务必先判断或者用try...except包住。我之前在一次数据清洗任务里直接用remove删一批黑名单域名结果一个域名不在列表里整个清洗进程中断了。后来改成先判断存在性再删除虽然多了一次遍历但稳妥得多。pop适合模拟栈的弹出操作pop(0)虽然可以实现队列的“出队”但性能也是 O(n)。如果你频繁从头部弹出建议用deque.popleft()。这也是我在生产代码里从没用pop(0)来消费队列的原因。有个细节del不但能删元素还能删除整个变量。执行del lst后这个变量名就不存在了再次访问会得到NameError。通常我们只删列表的一部分很少真的删整个变量但需要清理大列表占用的内存时del lst之后内存引用就释放了这在长时间运行的脚本里很有价值。4.3 查找与统计操作index(value)返回第一个匹配值的下标找不到会抛ValueError这一点跟remove一样需要做防护。count(value)计算指定值出现的次数。如果只判断“某个元素是否存在”用value in lst就够了这个操作的平均时间复杂度是 O(n)。在大列表里反复执行in查询时整体性能会很差。比如我处理过一份十万条记录的用户名单需要过滤掉其中不存在的 ID如果用id_list的循环内再加一层if id in id_list复杂度直接变成 O(n^2)。我当时的做法是把名单先转成set查询复杂度降到 O(1)整个脚本从跑几十秒变成一瞬间。这些知识虽然讲的是列表但真正工作中你要学会判断什么时候该“换容器”。4.4 排序与反转lst.sort()会原地排序返回Nonesorted(lst)则返回一个新的有序列表原列表不变。这两者对应的场景很鲜明如果不再需要原顺序就原地排序省内存如果还要保留原列表就用sorted。排序的高级用法体现在key参数上。比如按字符串长度排序、按字典中的某个字段排序、按元组第二元素排序words [banana, apple, cherry, date] words.sort(keylen) # [date, apple, banana, cherry] students [ {name: zhang, score: 82}, {name: li, score: 95}, {name: wang, score: 78}, ] students.sort(keylambda s: s[score], reverseTrue) # 按分数从高到低排序key参数的高阶玩法是使用operator.itemgetter或operator.attrgetter效率会比lambda略高而且代码可读性也好。我个人在给字典列表排序时会直接用itemgetter(score)既干净又不啰嗦。reverse()只是原地反转当前顺序不是排序。想要逆序排序可以sort(reverseTrue)这样更直观。反转列表还有一种办法就是前面讲过的切片lst[::-1]但那是生成新对象。4.5 拷贝的深浅之分拷贝这部分值得单独列一小节因为太多人在这里翻船了。看这个例子a [1, 2, [3, 4]] b a[:] # 浅拷贝 b[2][0] 99 print(a) # [1, 2, [99, 4]] ← 原列表被修改了b a[:]只复制了外层a[2]里的那个内层列表a和b仍然共享。想要完全隔离必须用copy.deepcopy(a)。但深拷贝不是免费的开销大得多所以我在实际项目里遵循的原则是如果列表里的元素都是不可变类型数字、字符串、元组用浅拷贝已经足够只有明确包含可变嵌套结构时才考虑深拷贝。如果你在写递归回溯算法比如求子集、全排列这类经典问题通常要把待选列表做成拷贝再传入回溯函数。这时候如果只是浅拷贝可能因为共享子列表而产生“状态互相污染”的诡异结果。我当时花了一个下午调试一个子集生成器最后发现就是浅拷贝惹的祸心里那叫一个懊恼。5. 列表推导式与高阶应用5.1 条件筛选与嵌套循环列表推导式不只是用来生成简单序列它能在创建时直接完成筛选、变换、展开。举几个实际场景# 筛选出列表中所有长度大于3的字符串 tags [py, python, list, a] long_tags [t for t in tags if len(t) 3] # 将列表中的数字字符串转成整数忽略无法转换的 raw [1, 2, bad, 3, ] nums [int(x) for x in raw if x.isdigit()] # 双层循环展开矩阵 matrix [[1, 2], [3, 4]] flat [num for row in matrix for num in row] # [1, 2, 3, 4]关于双层循环推导式的读法我的经验是把for row in matrix for num in row顺序读就像嵌套 for 循环的展开外层循环在前内层循环在后。千万别理解成先把内层拍平再取外层。这个读法你要是第一次用建议在纸上亲手写两次展开等价代码后面就会顺很多。还可以用推导式生成字典和集合语法上只是把括号换成花括号squares_dict {x: x * x for x in range(5)} # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} unique_set {x % 3 for x in range(10)} # {0, 1, 2}这些“兄弟语法”虽然题目只对列表讲但真正写代码时三者经常连用。尤其在做数据去重时先通过集合推导式过滤、再转回列表效率会高很多。5.2 推导式的性能与可读性边界列表推导式相比等价的for循环性能通常有小幅提升原因是它在字节码层面专门优化过LIST_APPEND指令少了几次属性查找。但提升幅度一般不超过 20%~30%别指望靠推导式救一个本来就很慢的算法。真正要警惕的不是性能而是可读性被推土机碾平。我见过有人把“对两个列表的元素两两组合且满足某条件的元素封装成元组再过滤再格式化”写成一行推导式长达两百多个字符。那种代码从维护角度讲基本就是废了我自己写的时候也容易在某个复杂的推导式里多打一个括号然后消耗五分钟排查括号配对问题。所以我的经验是三到四个条件以内的推导式直接用超过这个复杂度拆成普通循环或者自定义函数然后在一行里调用它。代码是写给下一个维护者往往就是三个月后的自己看的你不是在参加“最小化代码行数大赛”。6. 实战场景列表在数据处理中的典型例子6.1 列表去重并保持原顺序列表本身没有直接去重的方法。最常见的错误写法是# 不推荐的错误做法顺序被破坏 unique list(set(lst))集合会丢失元素顺序如果你对顺序没有要求这个写法最简洁快速。但如果需要保持原列表顺序就要借助“有序去重”的组合拳items [apple, banana, apple, orange, banana] seen set() unique [] for item in items: if item not in seen: seen.add(item) unique.append(item) # [apple, banana, orange]这里的关键在于seen集合负责快速判断是否出现过列表unique负责保留顺序。在 Python 3.7 以后其实直接用dict.fromkeys(items)也能达到同样效果因为字典天然有序且按键去重unique list(dict.fromkeys(items))这算一个比较优雅的高级写法不过在面试或团队协作中如果你不确定别人能不能看懂还是老老实实用循环集合比较稳妥。代码清晰比代码聪明更重要。6.2 按条件拆分列表工作里经常遇到“把用户列表里成年人和未成年人分开”这种需求。低效率写法是遍历一次分别判断、分别append这没错但还可以用列表推导式更简洁ages [12, 34, 18, 17, 56, 20] adults [a for a in ages if a 18] minors [a for a in ages if a 18]这里有个隐含代价你遍历了两次列表。如果列表规模特别大遍历两次的开销不可忽略。这种情况下用一次循环分别收集更合适。我一般在十万条数据以下直接写两个推导式简洁优先超过百万条才考虑一次遍历优化。这种“数据规模阈值”的把握能力是一个工程师的隐形素养不要总追求单次遍历也不要上来就盲目追求“写得短”。6.3 嵌套列表展开为一维题目里常说的“多维列表拍平”其实也是推导式的一个经典例子前面我已经提到。但如果嵌套的层数不固定比如列表里既有列表又有普通元素用固定两层的推导式就不够了需要递归处理def flatten(nested): result [] for item in nested: if isinstance(item, list): result.extend(flatten(item)) else: result.append(item) return result data [1, [2, [3, 4]], 5, [6]] print(flatten(data)) # [1, 2, 3, 4, 5, 6]递归展开的代价是深度很大时可能爆栈但工作中碰到的嵌套深度一般也就三四层完全够用。6.4 两列表求交集、并集、差集这个场景在推荐系统、权限校验、抽奖去重中非常常见。把列表转成集合然后利用集合运算几乎可以一行搞定a [1, 2, 3, 4] b [3, 4, 5, 6] intersection list(set(a) set(b)) # 交集 [3, 4] union list(set(a) | set(b)) # 并集 [1, 2, 3, 4, 5, 6] diff_a_b list(set(a) - set(b)) # 差集 [1, 2] sym_diff list(set(a) ^ set(b)) # 对称差集 [1, 2, 5, 6]如果你要保持列表顺序就需要再次使用上面的“有序去重”思想。集合运算很快但顺序取决于集合的内部哈希结果本质上无序。对顺序敏感的数据处理永远别直接用set输出最终结果它适合做中间过滤。7. 常见报错与性能避坑7.1 高频报错速查列表相关的报错就那几种我列个速查表方便你放心怀疑人生报错出现原因解决方案IndexError: list index out of range访问下标超过列表长度先len(lst)判断或用try处理ValueError: list.remove(x): x not in listremove删除不存在的元素先if x in lst再删ValueError: x is not in listindex找不到对应值同样先判断或捕获异常TypeError: int object is not subscriptable对不可下标类型用了索引检查数据结构是否真的嵌套列表TypeError: list object is not callable变量名把内置list覆盖了不要用list、sum这种内置名当变量AttributeError: _List object has no attribute append可能是用了collections里某个类型或变量名被绑定到意外对象查变量赋值来源7.2 遍历时修改列表的大坑这是列表操作里面最经典、最隐蔽、最让人血压飙升的一个坑。看代码lst [1, 2, 3, 4, 5] for x in lst: if x % 2 0: lst.remove(x) print(lst) # [1, 3, 5] 恰好碰对上面的例子碰巧输出正确但很多人会遇到“明明删了却还剩一个没删掉”的诡异现象。原因在于遍历过程中删除元素会让列表长度变短内部索引自动前移于是循环跳过了一个元素。试试这个例子lst [1, 2, 3, 4, 5, 6] for x in lst: if x 3: lst.remove(3)看起来只是删 3好像无所谓但一旦你的删除条件命中多个元素就会出现“漏删”的后果。更糟糕的是如果边遍历边insert可能造成无限循环或者IndexError。正确的做法有两种。第一种是遍历原列表的副本for x in lst[:]:这样删除操作作用于原列表但遍历用的是独立的浅拷贝索引不会乱。第二种是构建新列表而不是原地删除lst [1, 2, 3, 4, 5] lst [x for x in lst if x % 2 ! 0]推导式构建新列表的方式既清晰又高效几乎可以作为“需要过滤时”的默认首选。当列表非常大不想占用额外内存时才考虑倒序删除或原地遍历副本。7.3 大列表拼接的性能问题有人写代码喜欢这样累积结果result [] for i in range(1000000): result [i]对列表来说是就地扩展等价于extend它不会反复创建新对象性能其实尚可。真正的问题出在result result [i]这会产生新列表复杂度达到 O(n^2)跑一百万次直接卡到怀疑人生。同样的道理也适用于字符串拼接字符串是不可变的str 会频繁创建新对象正确做法是把片段累积到列表里最后用.join(lst)一次成形。还有一个隐蔽性能点是in操作。在大列表中判断成员身份是 O(n)如果在一个循环里对同一个大列表反复做in判断整体就是 O(n^2)。把列表转成set后查询变 O(1)对去重、过滤、白名单匹配这类任务能带来数量级的提升。列表里还有个经典老问题已知位置要频繁删除中间元素。列表的中间删除会让后半段整体移动即使只删一个复杂度也是 O(n)。如果删除操作非常密集或者队列两端频繁进出真的推荐换deque。它内部是双向链表结构两端操作都是常数级虽然随机访问不如列表但你的场景也不依赖随机访问为什么不用更合适的7.4 列表与元组的边界选择最后想再聊一下问题和主题里都反复提到的“列表与元组”的关系。列表可变元组不可变这是最表面的区别。但在实际使用中我的选择依据是这样的如果数据集合需要后续增删改选择列表没什么好犹豫的。如果数据集合作为字典的键必须用元组因为字典的键要求可哈希列表不可哈希。如果数据集合只是为了防止被意外修改用元组更安全也更能表达“这玩意儿不该动”的设计意图。从性能角度看元组可以复用缓存内存创建和访问都略快于列表但差异通常只在大量创建时才体现得出来。其实还有个容易忽略的细节元组作为不可变类型对外可以安全地直接暴露别人拿不到原始引用做修改。而如果你把列表直接暴露给外部调用者对方随手列表.append(...)就可能污染你的内部状态。这也是为什么很多库函数返回值会故意包装成元组的原因之一。我在封装内部数据结构时凡是只读返回一律用tuple可变操作才用 list。关于列表我最后想说的从创建方式到索引切片从增删改查到推导式再到实战场景和异常排查列表的这些知识点看起来零散但背后其实有一条主线列表是“动态数组 对象引用”的组合体。理解了内存布局就理解了拷贝、切片、性能差异理解了引用语义就理解了遍历删除的坑、嵌套共享的隐患。所有看起来独立的细节最后都会在真实的调试现场相遇。我做开发这么久看到太多新手把精力花在记 API 名称上却忽略了列表操作的语义和性能边界。实际上API 名称随时可以查文档但“为什么这样写会出 bug”“为什么这个操作会慢”才是调试能力的真正来源。想真正把列表用明白我建议你回去写一个小工具读入一个文本文件统计每个单词出现的次数输出前十个高频词。这个需求会自然逼着你去使用分词、去重、统计、排序、切片把这些操作串在一起。写完之后你再看列表的感受一定会和我现在说的这些经验对上。行了如果后续你还想深入列表推导式的奇技淫巧、嵌套列表的递归处理、或者列表在内存和性能优化上的更多细节欢迎评论区留言。我踩过的坑都会一条条写出来不仅让你知道怎么做更让你明白为什么。