跳到正文
本地阅读 · 容器、字符串与切片

容器、字符串与切片

计算机基础 · Python基础

前置知识:类型、运算及控制流

本章目录

本章学习目标

  • 按用途选择 list、tuple、dict、set
  • 解释切片和可变对象别名
  • 用容器完成订单去重与分组

一、四种容器的职责

列表 list 表达有序序列,可以增删改;元组 tuple 适合固定结构,例如一个坐标或一条返回结果,但元组内部若装着列表,内部列表仍可变。字典 dict 是键到值的映射,适合按用户编号寻找信息;集合 set 表达成员唯一性,适合去重和集合差。不要为了展示“会用所有类型”在同一任务中混用,先问需要顺序、唯一性还是按键查找。

字典键必须可哈希,列表不能直接作键;字符串、整数和只包含可哈希对象的元组常可作键。字典保留插入顺序,但按业务顺序输出仍应明确排序;集合不要依赖展示顺序。对字典 key in mapping 检查的是键,不是值。查找可能缺失的键用 get 可设置默认值,但默认零只适合“确实没有累计”的场景,不能把缺失用户信息误当成合法零值。

二、索引与切片是半开区间

Python 序列索引从零开始,负一表示最后一个元素。切片 a[start:stop:step] 包含 start,不包含 stop;例如 [10,20,30,40][1:3] 得到二十和三十。半开区间使长度通常等于 stop 减 start,并能让相邻片段自然拼接。越界单个索引会抛 IndexError,但切片通常会截断到合法范围,因此空切片本身不一定是异常。

字符串不可变,name[0]='A' 不合法;应构造新字符串。清洗股票代码时 strip 去除两端空白,upper 统一大小写,split 切分字段。注意 strip 的参数是字符集合,不是需要精确删除的前后缀;需要处理确切前缀时用 removeprefix。只凭拆分逗号来读取 CSV 不可靠,因为字段内可能有逗号或换行,文件章节将使用 csv 模块。

三、名字绑定与浅拷贝

b=a 不会创建第二份列表,只给同一个对象多一个名字;b.append(4) 后 a 也能看到变化。a.copy() 或 a[:] 建立新的外层列表,适合单层标量列表。若 a 是嵌套列表,浅拷贝后的内层对象仍共享,比如 a=[[1],[2]]; b=a.copy(); b[0].append(9) 也改变 a 的第一项。独立嵌套结构需要明确构造新内层对象,或在确实适用时使用 deepcopy。

rows=[[0]*3]*2 同样危险:乘法重复的是对同一内层列表的引用,修改一个单元格可能改变两行。应使用 [[0]*3 for _ in range(2)]。这些机制会直接影响清洗和特征构造,尤其当代码把“原始数据”和“处理后数据”绑定到同一对象时。不要把 Python 列表切片的新外层与 NumPy 基本切片的视图混为一谈,后者在下一模块单独解释。

四、用字典分组而不是反复搜索

示例订单为用户一的十元、用户二的二十元、用户一的五元。字典初始为空,逐笔执行 totals[uid]=totals.get(uid,0)+amount,过程依次得到 {1:10}、{1:10,2:20}、{1:15,2:20}。这就是简单的按键聚合,后面 SQL GROUP BY 和 pandas groupby 会替你完成同一类工作,但现在要能手动推演。

如果同时需要订单数,维护第二个计数字典或使用结构化记录;平均金额由累计金额除以累计笔数。去重先说明键:按用户去重会丢失多笔真实订单,按 order_id 才可能去掉重复导入。集合用于检测已见订单编号,遇到同编号不同金额应报冲突而不是无条件忽略。为了让测试稳定,打印字典时按键排序,输出用户一十五、用户二二十。

五、推导复杂度与可靠清洗

列表成员判断通常需要顺序比较,集合和字典在常见情况下提供近似常数时间查找,但哈希结构有内存开销,也不能替代排序后的范围搜索。处理大量订单时,先建用户到属性的字典通常比每笔订单重新扫描完整用户列表更合适。不要把平均复杂度当成任何输入都保证的最坏情况。

配套脚本打印切片、别名、独立二维列表和聚合结果。验证时加入空列表、重复编号、空字符串以及两端有空格的代码,观察规则是否一致。题目要求“去重保序”时不能只写 list(set(values)),应维护 seen 集合并顺序构造结果。数据工作里保留原始输入很有价值:清洗后构造新容器,记录修改数量和原因,再检查行数变化是否符合预期。

完整示例与实际输出

orders=[(1,10),(2,20),(1,5)]
totals={}
for uid,amount in orders:
    totals[uid]=totals.get(uid,0)+amount
assert totals=={1:15,2:20}
print("totals",sorted(totals.items()))
values=[10,20,30,40]
assert values[1:3]==[20,30]
print("slice",values[1:3])
alias=values; alias.append(50)
assert values[-1]==50
rows=[[0]*3 for _ in range(2)]; rows[0][0]=9
assert rows[1][0]==0
codes=[" aa ","BB","aa"]
seen=set(); clean=[]
for item in codes:
    item=item.strip().upper()
    if item not in seen:
        seen.add(item); clean.append(item)
assert clean==["AA","BB"]
print("rows",rows,"unique codes",clean)

实际运行通过CPU Python execution.

totals [(1, 15), (2, 20)]
slice [20, 30]
rows [[9, 0, 0], [0, 0, 0]] unique codes ['AA', 'BB']

五道自测:先回答,再核对

01 · [10,20,30,40][1:3] 是什么?

查看提示

右端不包含。

查看参考答案

[20,30]。

02 · 字典中的 in 默认查键还是查值?

查看提示

映射靠键访问。

查看参考答案

查键;值成员判断需要 mapping.values()。

03 · a.copy() 能完全隔离嵌套列表吗?

查看提示

外层和内层分别考虑。

查看参考答案

不能;只复制外层,内层引用仍共享。

04 · [[0]*2]*3 为什么容易出错?

查看提示

乘法复制对象还是引用?

查看参考答案

三行引用同一内层列表,改一行会影响另外两行。

05 · 按用户去重为何不等于按订单去重?

查看提示

一名用户可以多次购买。

查看参考答案

键对应的数据粒度不同;按用户去重会丢掉真实订单。

两项动手任务

任务 1 · 对 ['b','a','b','c','a'] 去重并保留首次出现顺序。

提示

seen 用集合,输出用列表。

完整参考解答与检查方式

结果应为 b、a、c;不依赖集合遍历顺序。

values=['b','a','b','c','a']; seen=set(); result=[]
for v in values:
    if v not in seen:
        seen.add(v); result.append(v)
assert result==['b','a','c']
print(result)

任务 2 · 按用户聚合 [(1,10),(1,30),(2,20)] 的笔数和均值。

提示

金额与笔数分别累计。

完整参考解答与检查方式

用户一两笔均值二十;用户二一笔均值二十。

rows=[(1,10),(1,30),(2,20)]; sums={}; counts={}
for uid,amount in rows:
    sums[uid]=sums.get(uid,0)+amount
    counts[uid]=counts.get(uid,0)+1
result={uid:(counts[uid],sums[uid]/counts[uid]) for uid in sums}
assert result=={1:(2,20),2:(1,20)}
print(sorted(result.items()))

官方资料与掌握状态

完成两项任务后,按独立完成程度记录。状态会尽量保存在当前浏览器,可导出备份。

动手任务记录

掌握状态与复习

关掉资料独立完成后记 A;提示后完成记 B;需要重学记 C。

记录尽量保存在当前浏览器,建议定期导出备份。