news 2026/9/15 6:11:29

Python容器详解:列表、元组、字典与集合从入门到实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python容器详解:列表、元组、字典与集合从入门到实战

我先说个场景,零基础学Python最容易卡住的地方,不是if不是for,而是突然听到“容器”这个词。其实你完全不用把它想得多高深。生活里你用收纳盒装衣服、用抽屉装杂物,Python里的list、tuple、dict、set,就是编程世界里那几种不同结构的收纳盒。第四章把这几样东西吃透,后面写循环、写函数、写爬虫脚本,手感和之前完全是两个层次。

如果你还没把Python环境装好,也别急着往后看。先去官网下一个安装包,装的时候勾选“Add Python to PATH”,然后在VS Code里装好Python插件,新建一个.py文件跑通print("hello"),全程大概十分钟。下面所有代码,你都照着敲一遍,最好再自己改动几个参数看结果,这比光读文章有用得多。

1. 容器是什么,为什么非学不可

1.1 从变量到容器的思维转变

变量只能存一个值,比如name = "小明"age = 18。但真实场景哪有这么简单?你手里有三门课的成绩,你得存三个变量;你有全班五十个人的名字,难道写五十个变量?这显然不现实。容器的价值就在于:用单个变量名,就能把一堆数据装进去,然后通过索引、循环、方法去批量处理。

很多教材把容器叫“数据结构”,把零基础的读者直接吓跑。我讲课的时候更喜欢叫它“数据盒子”。你只要先记住一个结论:凡是碰到“一个东西的多个数据”,第一反应就应该是容器,而不是拼命声明变量。比如要统计一篇文章里哪些字出现次数最多,你得先把所有字符装进一个结构里,再逐一遍历统计。没有容器,这一步根本没法做。

刚入门不需要背概念,需要建立的是“能用容器装数据”的直觉。你每写一段代码前,先问自己一句话:我手上是单个数据,还是多个数据?多个数据,核心思路就是用容器把它们组织起来。这个思维一转变,你就从“只能处理一个数”进入了“可以处理一批数”的阶段。

1.2 Python内置的四大容器类型

Python自带四种最核心的容器,我习惯用一张表先让学员建立整体印象:

类型名称是否有序是否可修改是否允许重复典型场景
list列表有序可修改允许序列数据、待处理任务
tuple元组有序不可修改允许固定配置、坐标、返回值
dict字典有序(3.7+)可修改键不允许重复键值映射、配置项
set集合无序可修改不允许重复去重、集合运算

这四种就是Python容器的主力军。列表像一条排队的长龙,每个位置都有编号;元组像一条被焊死的铁轨,不能随便改动;字典像一本通讯录,靠“姓名”直接翻到“电话”;集合像一个大麻袋,往里丢东西时自动把重复的抖掉。

还有一个容易忽略的重点:从Python 3.7开始,字典会保持键的插入顺序。3.6之前的字典是无序的,很多老教程还在强调“字典无序”,如果你的Python版本是3.8以上,完全可以依赖插入顺序来遍历。集合则始终无序,哪怕你打印时看到好像有顺序,那也只是哈希分布的结果,千万别依赖它。

2. 列表:最常用的有序容器

2.1 列表的增删改查

列表用方括号定义,元素之间用逗号隔开:

scores = [89, 92, 77, 85] names = ["张三", "李四", "王五"] mixed = [1, "hello", 3.14, True]

列表可以装任意类型,甚至可以嵌套,比如[[1, 2], [3, 4]]。但我建议新手尽量让一个列表里的元素保持同类型,否则后续处理容易出幺蛾子。查某个元素直接用下标,下标从0开始:scores[0]是89,scores[-1]是85,负号表示从尾部往前数,这是Python非常舒服的一个设计。

增删改查是列表的基础操作,我直接给你一套最快上手的动作:

scores = [89, 92, 77, 85] # 增 scores.append(60) # 尾部追加,最常用 scores.insert(1, 100) # 在索引1处插入 scores.extend([70, 80]) # 把另一个列表的元素逐个追加 # 删 scores.remove(77) # 按值删除,删第一个匹配项 popped = scores.pop() # 弹出尾部元素,也可以pop(索引) del scores[0] # 按下标删除 # 改 scores[1] = 99 # 直接覆盖 # 查 index = scores.index(85) # 找值对应的下标 count = scores.count(85) # 统计值出现次数

这里有个新手特别爱犯的错:用append往里加一个列表,结果变成嵌套列表而不是拼接。比如result.append([1, 2]),得到的是[[1, 2]]而不是[1, 2]。想合并两个列表,用extend或者+运算符。另外,remove删除的是第一个匹配值,如果列表里有多个相同值,只删一个;值不存在会直接抛ValueError,所以删除前最好先判断一下。

2.2 切片和遍历

切片是列表的精髓,语法是list[start:stop:step],左闭右开,意思是包含start位置,不包含stop位置。这个规则刚接触会觉得反直觉,但你只要记住“取头不取尾”,配合几个例子几分钟就适应:

nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] nums[:3] # [0, 1, 2],从头取到索引2 nums[3:] # [3, 4, 5, 6, 7, 8, 9],从索引3取到末尾 nums[::2] # [0, 2, 4, 6, 8],每隔一个取一个 nums[::-1] # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],反转列表

第三行和第四行是高频操作,尤其是[::-1]反转,实测下来比单独写循环方便太多。切片还有个隐藏技能:它返回的是一个新列表,所以你可以用切片快速复制一层列表,比如copy_nums = nums[:],这比直接用b = a安全得多,因为b = a只是让两个变量指向同一个列表,改一个另一个也跟着变。

遍历列表时,如果只关心元素值,直接for item in my_list即可;如果既想要下标又想要值,用enumerate

for idx, value in enumerate(scores): print(idx, value)

enumerate还能指定起始值,比如scores是学号索引,你想让编号从1开始显示,就写enumerate(scores, start=1)

2.3 列表推导式:入门最香的语法糖

列表推导式是Python非常受欢迎的一个特性,它能把“创建一个新列表”的循环压缩成一行。先看传统写法:

squares = [] for n in range(10): squares.append(n * n)

用推导式,三行变一行:

squares = [n * n for n in range(10)]

再叠加一个if条件:

even_squares = [n * n for n in range(10) if n % 2 == 0]

完整拆解一下这个语法:最外层是方括号,里面先写“要生成什么”,再写“从哪循环”,最后写“过滤条件”。顺序是[表达式 for 变量 in 可迭代对象 if 条件],这个顺序千万别搞反,新手最容易把forif的位置写颠倒。

列表推导式用好了效率极高,它不仅短,运行速度通常也比手动append快。我拍板建议:凡是“遍历一个列表,生成另一个新列表”的场景,优先考虑推导式。但有一个度,别套太多层,比如[[x * y for x in range(3)] for y in range(3)]还可以接受,三层以上嵌套推导式,可读性断崖式下降,别人维护代码时会想骂人。

3. 元组:那个“不能改”的兄弟

3.1 怎么创建元组,注意这几个坑

元组用圆括号定义,但真正标识元组的其实不是括号,而是逗号:

point = (3, 5) point2 = 3, 5 # 这样也是元组 single = (1) # 这是整数1,不是元组! single_tuple = (1,) # 这才是单元素元组

那个单元素元组的逗号,是新手极其容易踩的坑。你写data = (10),以为拿到元组,实际上Python直接把它当成整数10,后续一调用元组方法,立刻AttributeError。记住口诀:括号不是重点,逗号才是。

元组不可修改,指的是它不能添加、删除、替换元素。这一点我觉得特别适合用来放那些“不应该变”的数据。比如一个点的x、y坐标,一旦确定了就不希望被后面代码误改;再比如函数返回多个结果时,本质返回的就是一个元组:

def get_user(): name = "张三" age = 18 return name, age # 返回的是元组 ret = get_user() print(ret[0]) # 张三

另一个容易被忽略的地方:元组如果内部放了可变对象,那份可变对象本身还是能被修改的。比如t = (1, [2, 3]),你没法把t[1]换成别的东西,但可以t[1].append(4),元组内容就变成了(1, [2, 3, 4])。很多教程管这叫“元组里的可变元素陷阱”,面试题常考,理解一句话就行:元组保证的是“引用不变”,不是“引用指向的对象不变”。

3.2 解包与星号表达式

元组最顺手的功能是解包。一对一的场景:

point = (3, 5) x, y = point print(x, y) # 3 5

这个特性在交换变量时极其好用。其他语言换个变量值,得开一个临时变量,Python直接这样:

a, b = 10, 20 a, b = b, a print(a, b) # 20 10

星号表达式可以处理数量不定的解包,这种场景处理数据列表时非常实用:

first, *rest = [100, 200, 300, 400] print(first) # 100 print(rest) # [200, 300, 400]

再比如只要头尾,不要中间:

head, *mid, tail = [1, 2, 3, 4, 5]

变量mid会自动变成一个列表,收集中间剩余的所有元素。解包不仅限于元组,列表、字符串也都支持,因为只要它是可迭代对象就可以。配合函数参数,就变成了*args可变参数,这是函数章节的内容,但你提前理解了星号收集剩余元素,后面学函数会顺很多。

4. 字典:键值对查找是性价比之王

4.1 字典的创建与常用方法

字典用的是键值对(key-value)结构,和列表最大的区别是:列表靠下标找值,字典靠键找值。创建字典有几种方式:

person = {"name": "张三", "age": 18} # 用 dict() 传关键字参数 person2 = dict(name="李四", age=20) # 用 zip 把两个列表组合成字典 keys = ["name", "age"] values = ["王五", 25] person3 = dict(zip(keys, values))

第三种方式在数据处理里特别常用,两个列表天然就有关联关系,zip一拼就是一个字典。查字典里某个值,最直观的是person["name"],但键不存在时会直接抛KeyError。想安全一点,用get方法:

age = person.get("age", 0) # 键不存在时返回默认值0 phone = person.get("phone", "未填写")

get是字典里我使用频率最高的方法,写脚本处理外部数据时,你永远不能假设数据里每个键都存在。举个例子,解析接口返回的JSON时,某个字段可能缺失,用get(key, default)能避免一长串try/except

新增和修改的语法是一样的:

person["address"] = "北京" # 键不存在就是新增 person["age"] = 19 # 键已存在就是修改

删除用的是pop(key),它会返回被删除的值。还有popitem()会删除并返回最后一个键值对,适合需要按插入顺序倒序处理的场景。

4.2 三种遍历方式

很多新手刚接触字典时会困惑:我知道怎么取单个值,但怎么把整个字典过一遍?其实字典遍历有三种常见姿势,对应三个方法:

person = {"name": "张三", "age": 18, "city": "北京"} # 1. 只遍历键 for key in person: print(key) # 2. 遍历键值对 for key, value in person.items(): print(key, value) # 3. 只遍历值 for value in person.values(): print(value)

for key in personfor key in person.keys()等价,直接遍历字典时拿到的就是键。需要同时用键和值时,一定要用items(),它返回一个键值对的视图,每次迭代时解包成两个变量,代码是最清晰、效率也最高的方式。

这里提醒一个坑:在遍历字典时,如果同时修改字典的键(增加或删除),会直接报错:RuntimeError: dictionary changed size during iteration。解决办法通常是把要删除的键先收集成一个列表,遍历完列表再逐个删除,或者直接创建一个新字典来过滤。

4.3 默认值处理与字典合并

“统计频率”是字典最经典的用法。统计一串字符里每个字母出现多少次,新手通常这样写:

word_count = {} for char in "helloworld": if char in word_count: word_count[char] += 1 else: word_count[char] = 1

逻辑没问题,但每次都要判断键是否存在太啰嗦,用setdefault一行搞定:

for char in "helloworld": word_count[char] = word_count.setdefault(char, 0) + 1

setdefault(key, default)的逻辑是:键存在就返回原值,键不存在就先把默认值写入,再返回默认值。配合加1赋值,就把“有则加一,无则创建为1”压缩成一行了。

还有更优雅的方案是collections.defaultdict,它能在键不存在时自动创建默认值:

from collections import defaultdict word_count = defaultdict(int) for char in "helloworld": word_count[char] += 1

defaultdict(int)会为每个新键自动调用int()生成0,于是你只管加1就行。这个方式我强烈推荐,可读性比setdefault还顺。

字典合并也有讲究。Python 3.9以上可以直接用|运算符:

d1 = {"a": 1, "b": 2} d2 = {"b": 3, "c": 4} merged = d1 | d2 # {"a": 1, "b": 3, "c": 4}

3.8及以下版本用update方法或{**d1, **d2}。注意合并时如果键冲突,右侧字典的值会覆盖左侧的值。key冲突时,“后到的胜出”,这个规则在任何合并方式里都一样。

5. 集合:去重与关系运算两把抓

5.1 去重与增删

集合定义用花括号,但和字典的区别是:集合没有冒号,元素直接展开。空集合不能写{},因为那是空字典,得用set()

s = {1, 2, 3} empty = set()

集合最大的特性是元素不重复。去重是它实践价值最高的场景,一行代码解决:

nums = [1, 2, 2, 3, 3, 3, 4] unique_nums = list(set(nums)) print(unique_nums) # [1, 2, 3, 4]

这个操作在数据处理里太常用了。比如你从两份名单里收集到一批用户ID,里面可能有重复,直接set一下,重复的自动就没了。但去重时要注意:集合是无序的,去重后的结果不保证保持原顺序。如果你既要去重又要保持原来的相对顺序,就得换一种写法:

seen = set() result = [] for num in nums: if num not in seen: seen.add(num) result.append(num)

这段代码的原理是:集合的in判断速度远快于列表的in判断,因为集合底层是哈希表,查找时间是O(1),列表需要逐个比较,是O(n)。数据量小还不明显,列表里有几万条数据时,性能差距就出来了。

集合的增删方法,注意和列表、字典的区别:

s = {1, 2, 3} s.add(4) # 添加元素,已存在则不生效 s.remove(4) # 删除元素,不存在则抛KeyError s.discard(5) # 删除元素,不存在也不报错(推荐) popped = s.pop() # 弹出任意元素(因为无序,别指望“最后一个”) s.clear() # 清空集合

5.2 集合运算:交集并集差集

集合操作往往比手工循环高效得多。很多时候你会发现,原本要写好几层循环的判断,用集合运算一行就结束了。比如你有A班学生和B班学生,想知道两个班都报过哪些课,这是典型的交集场景:

java_course = {"张三", "李四", "王五"} python_course = {"李四", "王五", "赵六"} # 交集:两门课都报的人 both = java_course & python_course # {"李四", "王五"} # 并集:报名了任意一门课的人 all_students = java_course | python_course # 差集:只报了Java没报Python的人 only_java = java_course - python_course # 对称差集:只报了一门的人 one_course = java_course ^ python_course

运算符版本写起来很短,但可读性略低,也可以用方法版本:intersection()union()difference()symmetric_difference()。两种写法我建议都记住,看别人代码时两种风格都很常见。

集合还能做包含关系判断:

a = {1, 2} b = {1, 2, 3, 4} a <= b # True,a是b的子集 b >= a # True,b是a的超集 {1, 5}.isdisjoint({2, 3}) # True,两个集合没有交集

这些运算在权限系统、标签筛选、历史记录对比这些场景里都是主力。

5.3 frozenset 与可哈希问题

集合里的元素必须是可哈希的,说白了就是“不可变类型”才能进集合。数字、字符串、元组可以进集合,列表、字典、集合本身都不行,因为集合内部靠哈希值定位元素,元素一变,哈希位置就乱了。想验证一下,执行{[1, 2]},立刻报TypeError: unhashable type: 'list'

这就引出一个工具frozenset,它是不可变集合。普通集合没法放进另一个集合作元素,但frozenset可以:

s1 = frozenset([1, 2, 3]) s2 = frozenset([4, 5]) s = {s1, s2} # 正常

frozenset还能当字典的键使用。比如你要对多个集合分组统计,每个集合作为字典的键,这时候就得用frozenset,因为普通set不可哈希。这个知识点平时用得少,但在算法题、复杂数据处理里是个利器,面试时能答上来也是加分项。

6. 容器互转与类型转换

6.1 常用互转对照表

容器的真正威力在于它们之间可以灵活转换,这也是Python类型转换里非常核心的一块。最常见的几个互转函数,我整理成一张速查表:

目标类型转换函数作用
列表list(可迭代对象)字符串、元组、集合、字典键转列表
元组tuple(可迭代对象)列表、集合、字符串转元组
集合set(可迭代对象)序列去重、字典键转集合
字典dict(zip(keys, values))两个序列合成字典

字符串也能转列表,最常用的是split

text = "hello world python" words = text.split() # ['hello', 'world', 'python'] chars = list(text) # ['h', 'e', 'l', 'l', 'o', ...]

反过来,把列表转回字符串要用join,注意语法是“字符串去join列表”,不是“列表.join字符串”:

words = ['hello', 'world', 'python'] result = " ".join(words) # 'hello world python' result2 = ",".join(words) # 'hello,world,python'

这里有个常见的坑:join要求列表里的元素全是字符串,如果列表里有数字,比如[1, 2, 3],直接用" ".join([1, 2, 3])会报TypeError。解决办法是先转成字符串再join:

nums = [1, 2, 3] result = " ".join(map(str, nums)) # '1 2 3'

还有一个反向操作:字典转列表时,list(some_dict)拿到的是所有键的列表,而不是键值对。如果想拿到键值对列表,用list(some_dict.items());如果想拿值列表,用list(some_dict.values())。这几个区别在新手期也经常混淆,建议自己敲一遍对比一下输出。

6.2 实战:一行代码统计字符频率

容器之间的转换不只是“为了转而转”,很多时候是为了借用目标容器的能力。我举一个综合性很强的例子,统计一段文本中每个字符出现的次数并按频率排序输出:

text = "hello python, python is everywhere!" # 第1步:建一个空字典用于存频率 freq = {} # 第2步:遍历字符串,统计每个字符出现次数 for char in text: freq[char] = freq.get(char, 0) + 1 # 第3步:把字典转成列表,方便排序 items = list(freq.items()) # 第4步:按频率倒序排列 items.sort(key=lambda x: x[1], reverse=True) # 第5步:打印前5个最常见的字符 for char, count in items[:5]: print(f"{char!r}: {count}")

这个例子把字典、列表、元组解包、get默认值、sort排序、字符串格式化全串起来了,是容器章节最值得吃透的入门综合题。freq.items()返回的是一系列键值对元组,所以遍历时能直接解包成char, count两个变量。sortkey参数指定按照元组的第二个元素排序,reverse=True表示从大到小。

如果你只想统计“单词”的频率,而不是单个字符,先做一步text.split()把字符串变成单词列表,然后再套用同样的统计逻辑。你会发现代码主体完全不用变,只需要改变“输入容器里装的是什么”,这正好体现了容器类型的抽象价值:统计逻辑和数据类型解耦了。

7. 我踩过的容器坑:排查记录

7.1 可变默认参数:写函数时的大坑

这个坑是所有Python函数讲解里都会划重点的,因为它是真实项目里的高频翻车点。先看错误示范:

def add_item(item, items=[]): items.append(item) return items

第一次调用add_item(1)返回[1],第二次调用add_item(2)返回[1, 2],看起来似乎没问题?但如果你在不同场景下多次调用这个函数,会发现默认列表在函数调用之间被共享了。函数定义时,默认参数items=[]只创建一次,之后所有调用都复用同一个列表。

这个问题最隐蔽的地方在于:当你不传第二参数时,数据会在所有调用之间累积。正确做法是用None占位:

def add_item(item, items=None): if items is None: items = [] items.append(item) return items

这样每次不传items时,都会创建全新列表。这个规则同样适用于默认参数为字典、集合的情况,凡是可变对象都不能直接当默认参数。

7.2 遍历时修改容器:跳着删的诡异现象

循环里删除列表元素,看起来天经地义,但结果往往让人看不懂。你写:

my_list = [1, 2, 3, 4, 5, 6] for num in my_list: if num % 2 == 0: my_list.remove(num) print(my_list)

你以为删掉偶数,剩下[1, 3, 5],结果实际输出[1, 3, 5]?这道题我见过太多次,答案是:在特定数据组合下结果是对的,但换一组数据就错了。比如my_list = [1, 2, 3, 4, 5, 6, 8],结果会残留8。原因是在遍历过程中删除元素,列表的索引发声了前移,循环又按原索引继续走,于是跳过了紧挨着被删除元素的下一个元素。

解决方案有三个:一是遍历副本for num in my_list[:]:,这样原列表怎么改都不影响遍历逻辑;二是用列表推导式创建新列表:[num for num in my_list if num % 2 != 0];三是从后往前遍历。我个人推荐列表推导式,因为它的意图最明确,代码也最干净。记住一个原则:遍历容器时不要修改容器本身,要么遍历副本,要么生成新的容器。

7.3 浅拷贝与深拷贝:b = a只是多了个名字

另一个让人非常容易困惑的问题:列表复制。很多新手写:

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

明明改的是b,为什么a也变了?因为b = a根本没有复制数据,它只是给同一个列表多加了一个名字。两个变量指向内存里同一个对象,任何修改都会互相影响。如果你想要一个独立的副本,用切片或copy方法:

b = a[:] b = a.copy()

但这两个都是浅拷贝。什么叫浅?拷贝出来的新列表,内部嵌套的可变对象仍然和原列表共享。比如:

b = a.copy() b[2].append(99) print(a) # [1, 2, [3, 4, 99]],a也被改了

如果你希望完全独立,包括嵌套的内部列表,需要用copy.deepcopy

from copy import deepcopy b = deepcopy(a) b[2].append(99) print(a) # [1, 2, [3, 4]],a没变

这个知识点在刚开始用列表嵌套时会踩得比较痛,建议专门写一小段代码验证一下浅拷贝和深拷贝的区别。字典和集合的复制也是同样的道理:dict1.copy()是浅拷贝,遇到嵌套的列表或字典,仍然共享内部对象。判断“是否需要完全独立”的思路很简单:数据里有没有嵌套的可变对象?没有的话浅拷贝就够了,有的话用deepcopy。

8. 综合实战:用容器类型写通讯录

8.1 需求设计

学到这里,四种容器你已经都见过了。怎么把它们组合起来用?我建议做一个迷你通讯录,需求很接地气:支持添加联系人、按姓名查找电话、显示所有联系人、检查电话号码是否重复。

这个项目用字典做主存储结构,键是姓名,值是电话,天然满足“按名字查电话”的需求:

contacts = {} def add_contact(name, phone): contacts[name] = phone def find_contact(name): return contacts.get(name, "联系人不存在") def show_all(): for name, phone in contacts.items(): print(f"{name}: {phone}") def check_duplicate_phone(): phones = list(contacts.values()) return len(phones) != len(set(phones))

contacts就是字典容器;contacts.values()取出所有电话组成列表;set(phones)去重后长度变短了,说明存在重复电话。这行判断正是集合适用于去重的体现。如果你想按姓名排序显示,可以用sorted(contacts),它返回排好序的键列表,再遍历输出。

8.2 核心代码与运行效果

我们把上面的功能串起来,写一个最简单的交互循环,不用界面,纯命令行就能跑:

def main(): contacts = {} while True: print("\n1. 添加联系人 2. 查找联系人 3. 显示全部 4. 检查重复 5. 退出") choice = input("请选择操作:") if choice == "1": name = input("姓名:") phone = input("电话:") contacts[name] = phone elif choice == "2": name = input("姓名:") print(contacts.get(name, "联系人不存在")) elif choice == "3": for name, phone in contacts.items(): print(f"{name}: {phone}") elif choice == "4": phones = list(contacts.values()) if len(phones) != len(set(phones)): print("存在重复号码") else: print("没有重复号码") elif choice == "5": break if __name__ == "__main__": main()

这段代码里,变相用到了四种容器的配合:contacts是字典,contacts.items()返回键值对元组,list(contacts.values())转成列表,set(phones)用于去重判断。虽然代码简单,但容器协作的思路全在里面。

运行效果大概是:

1. 添加联系人 2. 查找联系人 3. 显示全部 4. 检查重复 5. 退出 请选择操作:1 姓名:张三 电话:13800138000 请选择操作:3 张三: 13800138000

如果你想更贴近真实需求,可以把contacts初始化为一个已有的字典,避免每次启动都从空开始。比如:

contacts = {"张三": "13800138000", "李四": "13900139000"}

添加新联系人时,如果姓名已存在,contacts[name] = phone就会覆盖旧号码。想改成“已存在则提示”,加一个判断即可。这个判断同样用到了in运算符对字典键的快速查找:

if name in contacts: print("联系人已存在,覆盖电话")

8.3 还能怎么扩展

通讯录做到这一步,已经足够你理解容器。但它只存在于内存里,程序一关闭数据就没了。想持久化,最简单的思路是用文件存储,把字典写成文本文件,下次启动时再解析回来。这一过程本身又会用到类型转换和容器操作:

def save(contacts, filename="contacts.txt"): with open(filename, "w", encoding="utf-8") as f: for name, phone in contacts.items(): f.write(f"{name},{phone}\n") def load(filename="contacts.txt"): contacts = {} try: with open(filename, "r", encoding="utf-8") as f: for line in f: name, phone = line.strip().split(",") contacts[name] = phone except FileNotFoundError: pass return contacts

保存时把键值对一行一行写进文件,加载时用split(",")把一行文本切成姓名和电话,再重新组装成字典。你看,split返回列表,列表的元素又能解包成两个变量,最后变成字典的键和值——类型转换在那张速查表里都有。

如果你还要检查重复姓名而不是重复电话,用字典就已经天然保证了键不重复,所以不需要额外判断。如果需求变成“一个姓名可以对应多个电话”,那存储结构就不能用简单的字典,得用字典的键对应一个列表,这就是嵌套容器了:

contacts = {"张三": ["13800138000"]} def add_phone(name, phone): if name not in contacts: contacts[name] = [] contacts[name].append(phone)

这个扩展思路值得你仔细琢磨一下,它解释了一个核心问题:为什么说容器可以组合使用?因为现实数据不是单层结构,一个“联系人”本身就可以是一个字典,多个联系人组成一个列表,列表又能变成集合去重。把基础容器组合起来,能表达的数据模型几乎是无限的。

我带过不少零基础的学员,大家最容易卡住的不是语法,而是想不到用什么容器。我的一个习惯是:写代码前先用中文把数据的形状描述出来。比如这句话,“我有一个电话本,里面每个人对应一个号码”,它天然就是字典。“我有一批成绩,按顺序排列”,天然就是列表。先用自然语言描述完数据,再决定用哪种容器,整个过程会顺很多。这一章学完,建议你把每个容器的增删改查都手敲一遍,再照着综合实战改造一个属于自己的小工具,比如记账本、成绩单统计、购物车。容器不是背出来的,是用出来的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 6:11:09

Spark交通智能分析实战:从实时流计算到集群性能调优

简介&#xff1a;面向毕业设计与课程作业的Spark交通智能分析系统项目&#xff0c;以Apache Spark分布式计算框架为核心&#xff0c;完整覆盖从交通数据采集、预处理、车流量统计到异常检测与调度决策的闭环流程&#xff0c;适合大数据相关专业学生、毕业设计选题者以及希望快速…

作者头像 李华
网站建设 2026/9/15 6:10:42

递归对抗动力学:AI系统自我博弈的认知进化机制

1. 递归对抗动力学&#xff1a;当系统开始自我博弈在认知科学和复杂系统研究的交叉地带&#xff0c;一种名为"递归对抗动力学"的理论框架正在引发学界关注。这个由世毫九实验室提出的原创理论&#xff0c;核心思想是通过系统内部的自指结构和矛盾张力来驱动认知能力的…

作者头像 李华
网站建设 2026/9/15 6:10:32

工业智能体开发为什么必须场景驱动?避开技术自嗨的落地方法论

1. 从“做模型”到“做系统”&#xff1a;工业智能体真正的门槛在哪里这两年“工业智能体”这个概念被炒得很热&#xff0c;但我观察到的一个现实是&#xff1a;不少团队把大量精力花在了算法调优、模型选型、算力堆砌上&#xff0c;结果项目落地时却卡在了车间里最不起眼的环节…

作者头像 李华
网站建设 2026/9/15 6:09:07

提示词工程实战:10个技巧让大语言模型输出更精准

很多朋友第一次接触提示词工程&#xff0c;都会把它理解成“怎么向AI提问”。这个理解不算错&#xff0c;但远远不够。我自己做了大半年大模型应用相关的工作&#xff0c;最深的感受是&#xff1a;提示词工程本质上不是话术技巧&#xff0c;而是需求表达能力的升级。你给模型的…

作者头像 李华
网站建设 2026/9/15 6:08:39

外贸网站用户体验优化的核心策略与实战技巧

1. 用户体验成为外贸网站核心竞争力的底层逻辑十年前的外贸网站只需要展示产品图片和联系方式就能获得询盘&#xff0c;如今这种简陋的页面连Google都难以收录。我在帮客户做网站诊断时发现&#xff0c;加载超过3秒的B2B网站&#xff0c;询盘转化率直接腰斩。这不是偶然现象——…

作者头像 李华
网站建设 2026/9/15 6:05:44

DDoS攻击溯源实战:三步用IP查询锁定攻击源ASN与地理位置

凌晨两点接到电话&#xff0c;一台出口防火墙被攻击流量打满&#xff0c;业务监控连续告警&#xff0c;带宽直接飙到接近8Gbps。登录设备一看&#xff0c;TCP SYN包像潮水一样涌进来&#xff0c;几百万条半连接堆在连接表里&#xff0c;正常用户根本排不上队。那一刻真正体会到…

作者头像 李华