Python入门:列表、元组、字典、集合核心用法与避坑指南
2026/9/23 14:24:06 网站建设 项目流程

先扯点实在的。你写Python写了两三周,变量、分支、循环都会了,但一到真正处理数据就卡住:手里有几百个用户ID要查重,有一堆键值对要统计,有几段文本要对比差异——这时候你才发现,光靠单个变量根本没法干活。Python里的序列、字典、集合,就是专门解决这类问题的第一梯队工具,也是入门阶段最值得花时间吃透的三块内容。这篇继续咱们的Python入门系列,我用最容易理解的方式,把列表、元组、字典、集合一次讲明白,包括它们的底层逻辑、常用操作和实战中会踩的坑。适合刚学完流程控制、想开始写点真实小脚本的入门读者,也可以当作复习材料快速过一遍。

1. 数据的容器思维:序列、字典、集合到底在解决什么问题

1.1 为什么学完语法就要立刻学容器

很多人学编程有个误区,觉得语法学完了就能写程序了。实际上,程序处理的核心对象永远是数据,而数据几乎不会是孤零零的一个值。回想一下你写过的练习:判断一个数是不是质数、打印九九乘法表、计算斐波那契数列,这些题目里数据量都很小,几个变量就够用了。

但真实场景完全不是这样。我举个例子:你爬了一个网页,拿到500条用户评论,现在要统计每条评论里出现频率最高的词。你不可能写500个变量去存。你需要一种方式,把“一坨数据”装进一个变量里,然后对这个整体做操作。这就是容器的价值。

Python官方文档里,序列、字典、集合这几种类型统称“内置类型”,直白点说就是语言自带的数据容器。序列的典型代表是字符串、列表、元组,字典是键值对的映射容器,集合则专注于去重和数学上的集合运算。三者承担的角色天然不同:序列管“顺序”,字典管“查找”,集合管“关系”。把这句话刻在脑子里,后面所有操作都不会迷路。

1.2 用生活场景理解三类容器的差异

为了把抽象概念落地,我用三个生活物件做类比,这套类比我在教新人的时候反复用,效果一直不错。

序列像是书橱里的书。书有第一本、第二本、第三本,你可以按位置取第几本,也可以从中间抽一截出来看,可以往中间插一本,也可以把某一本换掉。你关心的是“排列顺序”和“位置”。Python里的列表就是这样一个能改的书橱,元组则像是封了塑封的书,不能换不能插,但读起来很快。

字典像是学生档案柜。每个抽屉外面贴着学号,里面放着对应的信息。你不需要知道“第几号抽屉”在哪个位置,你只需要喊出学号,档案管理员直接带你到对应抽屉前面。这就是“按键取值”,查找效率非常高,数据量越大,优势越明显。

集合像是景点的门票核销系统。它只关心一件事:某个游客来没来过。同一张身份证号不会重复入园,你也没法说“请把第三位游客给我看看”,因为集合里没有顺序。它擅长的是判断“在不在里面”“这批人和那批人重了多少”。

理解了这三个定位,你再去看官方文档里那些方法列表,就不会觉得枯燥了——每种容器的方法,本质上都是在回答你“想对这个整体数据做什么”。

2. 序列三兄弟:字符串、列表、元组的使用细节

2.1 列表是有序序列吗?——“有序”的真实含义

先回答新人问得最多的问题:列表到底是不是有序序列?答案是肯定的,但要分清这里的“有序”是什么意思。列表的“有序”指的是元素按照你插入的顺序被保存,并且每一个元素都有确定的下标位置,而不是说列表会自动把元素从小到大排序。

我来演示一下这个区别:

nums = [5, 2, 8, 1] print(nums[0]) # 输出5,说明位置是确定的 print(sorted(nums)) # 输出[1, 2, 5, 8],sorted返回新列表完成排序 print(nums) # 输出[5, 2, 8, 1],原列表顺序没变

初次接触的人经常在这上面犯迷糊:列表不是“有序”的吗?怎么sort一下才变有序?我建议你把“有序”理解成“有顺序”而不是“已排序”。列表负责稳定地记住顺序,至于排序,那是对数据的一种操作,得你主动调用方法或函数去做。

字符串也是序列,它内部的每个字符同样有位置。元组也类似。所以当你听到“序列类型支持索引、切片、加和、乘、成员判断”这些话时,意味着字符串、列表、元组三兄弟共享一套操作逻辑。这也是为什么学Python入门不推荐跳过元组直接学字典——序列的通用规律搞明白了,三个类型一次性拿下。

2.2 可变与不可变:序列的核心分水岭

Python序列要分成可变和不可变两大类,这个属性决定了你能对数据做什么操作,也决定了它能不能被放进字典当键、放进集合当元素。

类型是否可变典型场景能不能当字典键
字符串不可变文本处理、拼接、格式化可以
列表可变动态增删数据、按位置存取不可以
元组不可变定长记录、函数多返回值可以(元素也得可哈希)

为什么可变性这么重要?因为Python里字典和集合依靠“哈希值”来定位数据,而哈希值是由内容算出来的。如果内容可以变,哈希值就得跟着变,存进去之后你再改它,字典就找不到它了。所以Python干脆规定:只有不可变类型(数字、字符串、元组)才能当字典键或集合元素。

实用上的影响是:你想用一个坐标点(x, y)当字典的键,用列表表示坐标会报错,写成元组就没事。代码对比一下:

# 错误示范 point1 = [1, 2] # d = {point1: "位置A"} # TypeError: unhashable type: 'list' # 正确做法 point2 = (1, 2) d = {point2: "位置A"} print(d[(1, 2)]) # 输出“位置A”

这个知识点看起来细,但在实际编码中经常成为拦路虎。入门阶段只要记住:如果你希望一块数据能作为“标签”去查另外的内容,就把它弄成元组而不是列表。

2.3 序列通用操作与切片避坑

既然说序列共享一套操作,我列几个最高频的通用操作,并演示它们在三类序列上的表现:

s = "python" lst = [3, 1, 4, 1, 5] tup = (10, 20, 30) # 长度、最大值、最小值、成员判断 print(len(s)) # 6 print(max(lst)) # 5 print(30 in tup) # True # 切片:起始:结束:步长,结束位置取不到 print(s[0:3]) # pyt print(lst[::-1]) # [5, 1, 4, 1, 3] 反转 print(tup[:2]) # (10, 20) # 拼接与重复 print([1, 2] + [3]) # [1, 2, 3] print("ab" * 2) # abab

切片这套规则,值得单独说一下,因为入门阶段报错和不达预期的很大一部分来自这里。第一,切片的结束位置是取不到的s[0:3]只有索引0、1、2三个元素。第二,切片可以越界,Python不会报错,比如s[0:999]只是返回整个字符串,这一点和用下标直接访问不一样——用lst[10]访问不存在的下标会直接抛IndexError,但lst[0:10]安全返回。第三,如果你发现切片结果不对劲,先检查步长是正还是负,默认是正1,从前往后切;要倒着取,必须写[::-1]或明确给负步长。

我实际写脚本时,切片最常用的三个场景就是:去末尾(data[:-1])、反转(data[::-1])、分段取数(data[::2]取偶数位置)。这些写在数据处理脚本里非常顺手。

3. 字典:打通键值对,告别“用列表硬查”的笨办法

3.1 字典的基本操作全景

字典在Python入门阶段的核心价值,就是给你一种“按名字找内容”的查找方式,而不是“按位置翻内容”。建立一个字典的方式很灵活:

# 方式一:花括号 info = {"name": "张三", "age": 25, "city": "北京"} # 方式二:dict()函数 info2 = dict(name="李四", age=30) # 方式三:从键值对序列转 info3 = dict([("name", "王五"), ("age", 28)])

增删改查四个基础操作覆盖90%的日常需求:

info = {"name": "张三", "age": 25} # 增:直接给不存在的键赋值 info["city"] = "北京" # 改:给已存在的键赋值 info["age"] = 26 # 查:用方括号或get方法 print(info["name"]) # 张三 print(info.get("phone")) # None,键不存在返回None而不是报错 print(info.get("phone", "未填写")) # 未填写,可指定默认值 # 删:pop弹出并返回,del直接删除 age = info.pop("age") print(age) # 26 del info["city"]

这里最值得养成习惯的是get()方法。新手图省事普遍直接用info["key"],问题是键不存在时程序直接崩溃。在真实的脚本里,你经常面对的是“这个键可能有也可能没有”的数据,比如JSON接口返回的字段。用info.get("key", 默认值)就能优雅处理。我再补一个高频组合:判断键是否存在用if "name" in info,记住字典的in判断的是,不是值。

批量更新字典用update()方法,它能把另一个字典或键值对一次性合入当前字典,这在合并配置项时非常实用:

config = {"debug": True, "port": 8080} new_config = {"port": 9090, "workers": 4} config.update(new_config) print(config) # {'debug': True, 'port': 9090, 'workers': 4}

3.2 字典的遍历与推导式

数据量小的时候,怎么取都能凑合;数据量一上来,遍历字典变成常规操作。Python提供了三种遍历视角:

info = {"name": "张三", "age": 25, "city": "北京"} # 遍历键(默认行为) for key in info: print(key) # 遍历键值对 for key, value in info.items(): print(key, value) # 只遍历值 for value in info.values(): print(value)

items()是最常用的,因为它一次性把键和值都给你,避免了在循环体里再写一次info[key]去取值。性能上,遍历键再查值会多一次哈希查找,虽然差距微乎其微,但写items()语义更清晰。

字典推导式是入门阶段容易忽略但非常提效的语法。它和列表推导式长得像,只是用花括号包起来,并且写“键: 值”:

# 把列表变成字典,元素作为键,平方作为值 nums = [1, 2, 3, 4] squares = {n: n ** 2 for n in nums} print(squares) # {1: 1, 2: 4, 3: 9, 4: 4} # 过滤出符合条件的键值对 scores = {"语文": 88, "数学": 95, "英语": 72} passed = {subject: score for subject, score in scores.items() if score >= 80} print(passed) # {'语文': 88, '数学': 95}

3.3 字典的常见坑位与进阶技巧

字典这部分有几个坑,几乎每个初学者都会踩,我集中列出来。

第一个坑:键必须是不可变类型。前面已经讲过,列表不能当键,但新手很容易在微信昵称、临时数据里犯这个错。解法就是把列表转成元组。

第二个坑:{ }创建的是空字典,不是空集合。想创建空集合必须用set()。这一点放在集合章节详细说。

第三个坑:键重复时后赋值的覆盖前面。这其实不是坑,是设计,但新手经常在循环构建字典时发现数据变少,往往就是键被覆盖了。如果你需要“一个键对应多个值”,可以配合列表:

from collections import defaultdict groups = defaultdict(list) groups["甲组"].append("张三") groups["甲组"].append("李四") groups["乙组"].append("王五") print(groups) # defaultdict(<class 'list'>, {'甲组': ['张三', '李四'], '乙组': ['王五']})

defaultdictcollections模块里的实用工具,特点是访问不存在的键时,不会再抛KeyError,而是自动创建一个默认值容器。上面例子中默认值是空列表,所以直接append就行。如果你不想引第三方(其实它是标准库),也可以用dict.setdefault

groups = {} groups.setdefault("甲组", []).append("张三")

这两招在处理“分组”“归类”“统计”类需求时极其好用,建议入门阶段就记下来。

4. 集合:自动去重和集合运算,数据处理省下的力气

4.1 集合的建立与去重实战

集合最朴素的用途就是去重:把可哈希的元素丢进集合,重复的会自动消失。建立一个集合同样有几种写法:

# 正确:set()函数,传入序列 bag = set([1, 2, 2, 3, 3, 3]) print(bag) # {1, 2, 3} # 正确:直接写花括号内容 bag2 = {1, 2, 3} # 错误:空花括号是字典 empty = {} print(type(empty)) # <class 'dict'> # 正确:空集合要这样 empty_set = set() print(type(empty_set)) # <class 'set'>

去重的实际场景太常见了,我随便举几个:统计一篇文章里出现了多少个不同的词;从用户ID列表里去掉重复ID;从日志文件里提取出现过的IP集合。代码都是一行搞定:

user_ids = [101, 102, 101, 103, 102, 104] unique_ids = list(set(user_ids)) # 注意:这样得到的列表顺序是不确定的

这里要提醒一个新手经常困惑的现象:集合去重后,顺序可能和原来不一样。因为集合内部使用哈希表存储,它只负责快速判断“在不在”,不负责维持插入顺序。如果你既想去重又要保持原顺序,可以使用一个小技巧:

user_ids = [101, 102, 101, 103, 102, 104] unique_ids = list(dict.fromkeys(user_ids)) print(unique_ids) # [101, 102, 103, 104]

原理是利用字典“键不重复且插入顺序被保留”的特性。这个技巧在Python 3.7+里是稳定有效的,建议写进你的工具箱。

4.2 交并差补四种运算

如果说去重是集合的“甜点”,那集合运算才是它的“主菜”。四个操作是必须掌握的:

  • 并集:两拨数据合起来有多少要素,符号|
  • 交集:两边共有的内容,符号&
  • 差集:在前者但不在后者的内容,符号-
  • 对称差集:只属于其中一边的内容,符号^

直接上代码:

a = {"张", "李", "王"} b = {"李", "赵"} print(a | b) # {'张', '李', '王', '赵'} 并集 print(a & b) # {'李'} 交集 print(a - b) # {'张', '王'} 差集 print(a ^ b) # {'张', '王', '赵'} 对称差集

符号记不住的话,也有对应的方法名:union()intersection()difference()symmetric_difference()。我个人写代码时习惯用符号,因为简洁;但做教学时会强调方法名,因为含义更明确。

集合还有两个判断关系的常用方法:issubset()判断是否子集,isdisjoint()判断是否完全没有交集。

x = {1, 2} y = {1, 2, 3, 4} print(x.issubset(y)) # True,x是y的子集 print({1}.isdisjoint({2})) # True,没有共同元素

4.3 集合运算的实战场景

光讲语法不提用法,等于白学。我挑几个典型的应用场景,你看看这些运算落到真实数据上有多顺手。

场景一:统计两个社交账号的共同好友。把两个账号的好友ID各转成一个集合,交集结果就是共同好友。

my_friends = {"张三", "李四", "王五"} other_friends = {"李四", "陈六"} common = my_friends & other_friends print(common) # {'李四'}

场景二:检查一批规则中有多少重叠的标签。比如你在做文本分类,一篇文章打了“科技”和“互联网”两个标签,另一篇打了“互联网”和“教育”,两个标签集合的交集能告诉你它们的相关程度。

场景三:对比两份名单的差异。旧名单和新名单分别是两个集合,新来的成员是新名单 - 旧名单,离开的成员是旧名单 - 新名单。做数据对账的时候,这套操作比循环加判断简洁太多。

我在实际的数据清洗脚本里就常用集合做“白名单过滤”:把允许通过的ID放进一个集合,然后遍历数据,用if id in allowed_set判断。这里值得说一句,集合的成员判断速度非常快,比列表的in要快很多——列表是逐个扫描,集合是直接算哈希定位。数据量在几千以上时差距就很明显了,这属于底层数据结构带来的性能红利。

5. 三剑客的选型配合:一篇实例看懂什么时候用哪个

5.1 选型决策表

讲了三种容器,很多人到最后就会问:我到底该用哪个?我给你一个可以直接套用的选型表:

需求场景推荐容器原因
按顺序保存、按位置访问的数据列表有序、可变、可切片
定长、不需要修改的记录元组不可变、可哈希、语义清晰
按名称/ID/键找对应值字典按键查找快,映射关系明确
去重、判断成员、集合关系运算集合天然去重,哈希查找快
保存一句话/一串字符字符串本身就是序列,文本处理方法多

这不只是一个“类型选择”问题,更是一种数据建模思维。拿到需求,先问三个问题:数据有没有顺序?需不需要按键找值?要不要去重或求关系?回答完,容器类型自然就出来了。

5.2 综合案例:统计一篇文章的词频

为了让你看到三者如何协同工作,我做一个小案例:统计一段英文文本里各单词出现的次数,按频率由高到低输出前5名。这个案例是很多教程里的经典题,但我会明确标出每一步用到了哪种容器、解决了什么问题。

text = "apple banana apple cherry banana apple" # 1. 分词:拿到一个列表(序列的典型应用) words = text.split() print(words) # ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple'] # 2. 去重:拿到词汇表(集合的典型应用) unique_words = set(words) print(unique_words) # {'apple', 'banana', 'cherry'} # 3. 计数:用字典建立词到次数的映射(字典的典型应用) count = {} for word in words: count[word] = count.get(word, 0) + 1 print(count) # {'apple': 3, 'banana': 2, 'cherry': 1} # 4. 排序:把字典的键值对转成列表,再按值排序 ranked = sorted(count.items(), key=lambda item: item[1], reverse=True) print(ranked[:5]) # [('apple', 3), ('banana', 2), ('cherry', 1)]

这一步一步下来,你会发现每一步换一种容器都是水到渠成:分词产生列表,去重需要集合,计数使用字典,排序又把字典变成列表去处理。没有哪一步是多余的,也没有哪一步是为了炫技。这就是我在文章开头说的“容器思维”——你操作的始终是“一整批数据”,不同的操作需求对应不同的数据结构。

如果数据量再大一点,或者单词本身需要清洗(去掉标点、转为小写),就在第1步前加一个列表推导式完成预处理。这些都是同样的思路。

6. 常见问题与排查技巧实录

最后这部分,我把自己在入门阶段和带新人过程中反复遇到的报错和困惑整理成了一份速查表。每一项都是真实验证过的场景,你可以直接当避坑指南用。

现象/报错原因解决方案
TypeError: unhashable type: 'list'试图把列表当作字典键或集合元素把列表转成元组再使用
字典赋值/查询时KeyError键不存在,用方括号访问了改用get()方法,或先用in判断
{ }创建的变量类型是dict空花括号被解释为空字典空集合用set()创建
集合去重后顺序发生变化集合用哈希存储,不维护顺序dict.fromkeys(列表)保留顺序
切片结果和预期不一致没有注意结束位置取不到、步长方向
检查[start:end:step]三个参数,尤其是end
字典遍历顺序和插入顺序不同误以为字典无序Python 3.7+字典已按插入顺序保存,有序是默认行为
list.remove(x)删不掉元素直接按值删除,只删第一个匹配项;不确定是否存在会报错if x in lst判断,或用列表推导式过滤
count[word] = count[word] + 1报KeyError第一次遇到新单词时字典里没有键count.get(word, 0) + 1先取默认值

再补一个我在实际项目中经常提醒组员的细节:列表的引用陷阱

a = [1, 2, 3] b = a b.append(4) print(a) # [1, 2, 3, 4]

这里b = a不是复制列表,而是让两个变量指向同一个列表对象。修改ba也会变。如果你想复制一份互不影响,要用a.copy()a[:]。新手在这个问题上栽跟头的概率极高,尤其是当列表作为函数参数传进传出的场景。

a = [1, 2, 3] b = a.copy() # 真正的新列表 b.append(4) print(a) # [1, 2, 3] print(b) # [1, 2, 3, 4]

另外一个和它相关的深层坑是“浅拷贝”。如果你的列表里嵌套了可变对象,比如a = [[1], [2]],那么a.copy()只复制了外层,内层子列表还是共享的。这种情况如果想深度复制,就用import copy; copy.deepcopy(a)。入门阶段先记住“复制列表用copy方法,嵌套多就用deepcopy”,后面学到面向对象和复杂数据结构时会更理解背后的对象模型。

结尾

最后聊点体会。我带过不少新人,观察到一个规律:语法学得最快的人,往往也是容器用得最熟的。因为容器就是组织数据的单元,数据组织得好,算法和逻辑才有施展空间。我自己的习惯是这样:拿到数据先想清楚它是“有顺序的序列”还是“需要按键找值的映射”或者“只需判断在不在的集合”,想清楚再动手写代码,几乎不会走弯路。

还有个小技巧想分享给你:练这部分内容,不要只做书本上的填空题。找一份真实的数据,比如你手机的通讯录导出、一份CSV日志、一篇英文文章,尝试用今天讲的列表、字典、集合去完成“去重、分组、统计、对比”这几个动作。你练得越多,越能体会到为什么Python把这些数据结构做成内置类型——因为它们真的是日常开发的地基,不是绕不开的理论,而是用了就离不开的工具。

下一期可以顺着这条路,把字符串处理里的常用方法继续补全,也能讲讲列表和字典的嵌套结构怎么组织复杂数据。先把今天这些操作敲熟,后面很多内容都会变得很顺。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询