Python列表、元组、集合实验报告
Python列表、元组、集合实验报告
软工24-3班黄甘露 3242052051056
一、实验名称
Python列表、元组、集合特性与综合数据处理实验
二、实验目的
- 掌握列表、元组、集合的可变性与可哈希判定规则,区分“引用不可变”与“元素内容不可变”;
- 理解切片删除按原始索引一次性计算的执行逻辑,能分析循环删除列表元素的漏删问题;
- 熟练运用集合交、并、差、对称差运算及集合推导式;
- 能判断嵌套容器是否可哈希,会用 frozenset 实现集合嵌套;
- 对比无序去重与保序去重方案,能实现保序去重;
- 根据需求选择容器完成小型综合数据处理任务。
三、实验环境
• Python 3.X 及以上
• 开发工具:PyCharm
• 无第三方库依赖
四、实验原理 - list:动态数组,存储对象引用;切片生成新序列,del 列表[切片] 原地批量删除;遍历中删除元素易漏删。
- tuple:不可变指元组内部存储的引用地址不可修改,不限制引用指向的可变对象内部修改;元组内任一元素不可哈希,则整个元组不可哈希。
- set:哈希表实现;支持 & | - ^ 运算;普通 set 可变不能嵌套,frozenset 不可变可哈希,可作集合元素。
- 哈希判定:生命周期内 hash 值固定的对象才可哈希。
- 去重:list(set(ls)) 简洁但丢序;保序去重用循环判断或 dict.fromkeys。
五、实验步骤与源代码
任务1 切片删除深度探究
预判
语句 预判结果
del [::3](0~9) 删索引0,3,6,9,剩 [1,2,4,5,7,8]
del [1::3](0~9) 删索引1,4,7,剩 [0,2,3,5,6,8,9]
del [::-2](0~5) 从尾隔2删索引5,3,1,剩 [0,2,4]
循环删偶数 预期删净偶数,实际可能漏删
源代码:
python
print("--- 任务1:切片删除行为验证 ---")
def show_delete(origin, sl, label):
lst = origin.copy()
before = len(lst)
del lst[sl]
print(f"{label}: 删除前{before}个 -> 删除后{len(lst)}个 -> {lst}")
return lst
base = list(range(10))
show_delete(base, slice(None, None, 3), "del [::3]")
show_delete(base, slice(1, None, 3), "del [1::3]")
base2 = list(range(6))
show_delete(base2, slice(None, None, -2), "del [::-2]")
nums = [1,2,3,4,5,6]
for x in nums[:]:
if x % 2 == 0:
nums.remove(x)
print("遍历副本删除偶数后:", nums)
nums2 = [1,2,3,4,5,6]
for x in nums2:
if x % 2 == 0:
nums2.remove(x)
print("直接遍历删除偶数后(可能漏删):", nums2)
运行截图:

预判与结果对比分析:
• del [::3] 实际为 [1,2,4,5,7,8],与预判一致。切片删除按删除前原始索引一次性计算,索引0、3、6、9被选中删除,不是删一个后重新计算索引。
• del [1::3] 实际为 [0,2,3,5,6,8,9],删的是原始索引1、4、7。
• del [::-2] 实际为 [0,2,4],负步长从尾部向前隔2个选中索引5、3、1。
• 循环删除:遍历副本 nums[:] 能删净偶数得 [1,3,5];直接遍历 nums2 时,remove 使列表变短、后续元素前移,for 索引继续+1导致跳过元素,出现漏删。安全方案:遍历副本、列表推导式重建、或倒序遍历删除。
任务2 嵌套容器与可哈希辨析
源代码:
python
print("--- 任务2:可哈希性逐项测试 ---")
def try_add(container, item, desc):
try:
container.add(item)
print(f"[成功] {desc} -> 当前集合: {container}")
except TypeError as e:
print(f"[报错] {desc} -> TypeError: {e}")
bag = set()
try_add(bag, (1,2,3), "元组(1,2,3)")
try_add(bag, (1,[2,3]), "元组(1,[2,3])")
try_add(bag, [1,2], "列表[1,2]")
try_add(bag, frozenset([1,2]), "frozenset([1,2])")
print("最终集合:", bag)
inner1 = frozenset({1,2})
inner2 = frozenset({3,4})
outer = {inner1, inner2}
print("用frozenset实现集合嵌套:", outer)
运行截图:

逐条说明:
语句 结果 理由
add((1,2,3)) 成功 元组内全为可哈希int,整体可哈希
add((1,[2,3])) 报错 unhashable type: 'list' 元组含list,list不可哈希,整个元组不可哈希
add([1,2]) 报错 list本身不可哈希
add(frozenset([1,2])) 成功 frozenset不可变、可哈希,可作集合元素
frozenset作用:普通set可变不可哈希,不能作集合元素;frozenset固定不可变、可哈希,用于实现“集合套集合”。
任务3 集合运算与集合推导式
源代码:
python
print("--- 任务3:集合运算与推导式 ---")
X = {2,4,6,8,10,12}
Y = {3,6,9,12,15}
ops = {
"交集 X∩Y": X & Y,
"并集 X∪Y": X | Y,
"差集 X-Y": X - Y,
"差集 Y-X": Y - X,
"对称差 X△Y": X ^ Y,
}
for name, val in ops.items():
print(f"{name} = {val}")
s1 = {n for n in range(1,31) if n % 4 == 0}
s2 = set(filter(lambda n: n % 4 == 0, range(1,31)))
print("集合推导式结果:", s1)
print("filter实现结果:", s2)
print("两者相等:", s1 == s2)
运行截图:

结果说明:交集 {6,12},并集 {2,3,4,6,8,9,10,12,15},X-Y={2,4,8,10},Y-X={3,9,15},对称差为只在一边出现的元素。集合推导式与filter结果均为 {4,8,12,16,20,24,28}。
任务4 去重算法对比
源代码:
python
print("--- 任务4:去重方案对比 ---")
raw = [5,2,7,2,5,9,7,3,9,1]
print("原始:", raw)
def dedup_set(seq):
return list(set(seq))
def dedup_loop(seq):
out = []
for v in seq:
if v not in out:
out.append(v)
return out
def dedup_dict(seq):
return list(dict.fromkeys(seq))
r_set = dedup_set(raw)
r_loop = dedup_loop(raw)
r_dict = dedup_dict(raw)
print("set去重(乱序):", r_set)
print("循环保序去重:", r_loop)
print("dict.fromkeys保序:", r_dict)
print("循环与dict结果一致:", r_loop == r_dict)
print("set结果元素相同但顺序可能不同:", set(r_set) == set(r_loop))
运行截图:

两种方案优缺点对比:
方案 优点 缺点
list(set(data)) 简洁、速度快 丢失原始顺序,输出顺序不可控
循环判断append 保留首次出现顺序、易理解 时间复杂度O(n²),大数据慢
dict.fromkeys(data) 一行保序、效率较好 需理解dict键唯一且有序(3.7+)
任务5 综合设计题
源代码:
python
print("--- 任务5:选课数据综合处理 ---")
c1 = [101,103,105,107,109,103,101]
c2 = [102,104,106,108,109,107,102]
def ordered_unique(seq):
seen = set()
out = []
for v in seq:
if v not in seen:
seen.add(v)
out.append(v)
return out
u1 = ordered_unique(c1)
u2 = ordered_unique(c2)
print("① 1班保序去重:", u1)
print(" 2班保序去重:", u2)
s1, s2 = set(u1), set(u2)
print("② 同时选两门(交集):", s1 & s2)
print("③ 只选一门(对称差):", s1 ^ s2)
print("④ 只选1班(差集):", s1 - s2)
inter = s1 & s2
print("⑤ 交集转元组:", tuple(inter))
print(" 说明: 交集元素均为int可哈希,元组内全可哈希故元组可哈希,可再放入集合。")
print(" 验证: 把元组放进集合 ->", {tuple(inter)})
print("--- 拓展:嵌套列表去重 ---")
nest = [[1,2],[3,4],[1,2],[5,6]]
mark = set()
res = []
for sub in nest:
key = tuple(sub)
if key not in mark:
mark.add(key)
res.append(sub)
print("去重前:", nest)
print("去重后:", res)
运行截图:

[[[]()]()]()
每步文字解释:
• ① 1班保序去重 [101,103,105,107,109],2班 [102,104,106,108,109,107],保留首次出现顺序。
• ② 交集 {107,109},即同时选两门课的学生编号。
• ③ 对称差 {101,102,103,104,105,106,108},只选其中一门的学生。
• ④ 差集 {101,103,105},选了1班未选2班。
• ⑤ 交集元素均为int可哈希,元组内全可哈希故元组可哈希,可再放入集合;验证 {(107,109)} 可成功构造。
• 拓展:内层list不可哈希,先 tuple(sub) 转元组判重,保留原list形式输出,去重后 [[1,2],[3,4],[5,6]]。
六、必答思考题
- del ls[::3]按删除前索引还是动态索引?
按删除前原始索引一次性计算。例 [0..9] 删索引0,3,6,9得 [1,2,4,5,7,8],不是删一个后重算。 - t=(1,[2,3]),t[1].append(4)合法但t不能入集合?
元组不可变指内部引用地址不可改,引用指向的list内容可改,故append合法;但list不可哈希导致整个元组不可哈希,不能入集合。 - 普通set不能嵌套,frozenset可以?
set可变→不可哈希→不能作元素;frozenset不可变→可哈希→可作元素。 - for遍历同时remove为什么删不干净?安全方案?
remove后列表变短、后续元素前移,for索引继续+1跳过元素。安全方案:遍历副本 for i in ls[:]、列表推导式重建、倒序遍历删除。 - list(set(data))缺陷?保序去重方式?
缺陷是丢失原始顺序。保序方式:循环判断append、dict.fromkeys(data)、旧版 OrderedDict。
七、实验小结
• list:有序可变,动态数组存引用;适合频繁增删改、需保序场景;切片生成新列表,del原地批量删,注意循环删除漏删。
• tuple:有序不可变(引用地址不可变,内部可变对象内容仍可改);元素全可哈希时整体可哈希;适合作字典键、集合元素、固定结构数据。
• set:无序可变,哈希表实现,元素必须可哈希;适合去重、交并差运算;不能嵌套普通set,嵌套用frozenset。
• 哈希约束:生命周期内hash值固定的对象才可哈希;list/set/dict不可哈希,tuple/frozenset/int/str可哈希(tuple内全可哈希时)。
• 综合体会:容器选择要结合有序性、可变性、哈希需求;去重优先保序方案;切片删除和循环删除要理解底层索引逻辑,避免直觉陷阱。
浙公网安备 33010602011771号