37.数据基本结构(元组、列表、字典)

本章概要

  • 学习材料:同一金融记录的元组、列表和字典表示。
  • 本章任务:比较位置访问、可变更新与键查找,完成“题目二代码解析”。
  • 完成后你将得到:三种结构的访问/更新结果和选择决策表。
  • 自我检查:检查字典键可哈希与元组元素可哈希边界;结构不匹配任务时先检查原因。
  • 拓展练习:把结构选择拓展应用到另一份投资组合记录。

本章学习目标

掌握 Python 三大基本数据结构的核心用法:

  • 元组 (Tuple):不可变序列,适合存储固定配置
  • 列表 (List):可变序列,适合管理动态数据
  • 字典 (Dict):键值映射,适合快速查找与关联

为什么金融数据分析需要理解这三种结构?

数据结构 符号 核心特性 金融应用场景
元组 () 不可变 股票代码、交易参数
列表 [] 可变 价格历史、收益率序列
字典 {} 键值映射 代码→名称、日期→行情

三种结构的时间复杂度对比

数据结构 数学模型 核心操作 时间复杂度
元组 有序序列 索引访问、切片 O(1)
列表 可变序列 增删改查 O(1)~O(n)
字典 键值映射 哈希查找 O(1) 平均

元组(Tuple):不可变序列

元组是 Python 中不可变(Immutable)的序列类型。

不可变性的四大优势:

  • 并发边界:槽位不可改不等于复合操作线程安全,共享可变对象仍需同步
  • 条件哈希:仅当每个成员都可哈希时,元组才可作为字典键或集合元素
  • 内存效率:Python 可对不可变对象进行优化
  • 浅层要求:元组槽位不可增删改,但槽位引用的列表等可变对象仍可变化

反例mutable_inside=([1],) 的内部列表仍可 append(2)hash(mutable_inside) 会抛出 TypeError

不可变性在金融系统中的意义

在交易系统中,订单一旦创建就不应被修改。元组的不可变性正好契合这一需求:

# 不可变订单(推荐)
order = ('600000.SH', 1000, 10.25)  # (代码, 数量, 价格)

# 可变订单(危险)
order = ['600000.SH', 1000, 10.25]
order[1] = 2000  # 意外修改!可能造成重大损失

元组切片语法详解

切片语法:tuple[start:end:step]

  • start:起始索引(包含),省略则从 0 开始
  • end:结束索引(不包含),省略则到末尾
  • step:步长,省略则为 1
data = (10, 20, 30, 40, 50, 60)
data[1:4]   # → (20, 30, 40)
data[-3:]   # → (40, 50, 60)
data[::2]   # → (10, 30, 50)

运行前预测|题目一:元组切片操作

  • 输入预测:运行前先写出 tuple 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到tuple 的结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“题目一:元组切片操作”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 题目一:元组切片操作

# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确

# =============================================================================
# 题目一:元组切片操作
# =============================================================================
# 本任务演示如何使用元组存储股票价格数据,并通过切片操作提取特定日期范围的数据

# 场景说明:
# 某股票在连续10个交易日的收盘价数据(单位:元)
# 数据存储在元组中,表示这些历史数据不应该被修改

# 创建元组,存储10个交易日的股票收盘价
# 注意:这里的变量名tuple与Python内置函数tuple()重名,这不是最佳实践
# 但为了与教学平台保持一致,我们使用这个变量名
tuple=('4102','4382','4922','3975','3407','2894','3217','4926','4531','4557')
# 元组索引:  0     1     2     3     4     5     6     7     8     9
# 日期说明:  3/1   3/2   3/3   3/4   3/5   3/6   3/7   3/8   3/9   3/10

# 利用切片访问3月6日-3月9日的股票分价表数据
# 切片语法详解: [start:end:step]
#   - start: 起始索引(包含),如果省略则从0开始
#   - end: 结束索引(不包含),如果省略则到序列末尾
#   - step: 步长,如果省略则为1
#
# 本题要求:提取3月6日到3月9日的数据
#   - 3月6日对应索引3(值'2894')
#   - 3月9日对应索引6(值'3217')
#   - 因此切片范围为[3:7],包含索引3,不包含索引7
#
# 切片结果将包含索引3,4,5,6的元素,即'2894','3217','4926','4531'
print(tuple[3:7])
# 运行结果: ('2894', '3217', '4926', '4531')

任务复盘|题目一:元组切片操作

运行后核对:核对 tuple 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。

题目一代码解析

  • tuple[3:7] 获取索引 3、4、5、6 的元素
  • 索引 3 → '3975'(3/4),索引 6 → '3217'(3/7)
  • 结束索引 7 不包含在结果中

负数索引示例

tuple[-1]   # 最后一个元素 '4557'
tuple[-3:]  # 最后三个 ('4926', '4531', '4557')
tuple[:-2]  # 除最后两个外的所有

元组不可变性演示

尝试修改元组会引发 TypeError

try:
    tuple[0] = '5000'  # 尝试修改
except TypeError as e:
    print(f'元组不可修改错误: {e}')

选择建议

  • 用元组:数据不应被修改(常量配置、历史数据)
  • 用列表:数据需要动态变化(实时价格更新)

列表(List):可变序列

Python 列表是基于动态数组(Dynamic Array)实现的:

  • 自动扩容:空间不足时自动分配更大内存块
  • amortized O(1):追加操作平均时间复杂度为 O(1)
  • 内存布局:连续存储,缓存友好

列表常用操作的时间复杂度

操作 时间复杂度 说明
append() O(1)* 平均情况
pop() O(1) 弹出末尾元素
insert() O(n) 需要移动后续元素
del O(n) 同上
index() O(n) 线性搜索
in O(n) 同上

运行前预测|题目二:列表索引操作

  • 输入预测:运行前先写出 list 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到list 的结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“题目二:列表索引操作”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 题目二:列表索引操作

# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确

# =============================================================================
# 题目二:列表索引操作
# =============================================================================
# 本任务演示如何使用正数索引和负数索引访问列表元素

# 场景说明:
# 存储某股票10个交易日的收盘价数据
# 与元组不同,列表是可变的,可以随时添加、删除或修改元素

# 创建列表,存储股票价格数据
# 注意:变量名list与Python内置函数list()重名,这不是最佳实践
# 但为了与教学平台保持一致,我们使用这个变量名
list=['4102','4382','4922','3975','3407','2894','3217','4926','4531','4557']
# 列表索引:  0     1     2     3     4     5     6     7     8     9
# 日期说明:  3/1   3/2   3/3   3/4   3/5   3/6   3/7   3/8   3/9   3/10

# 利用索引访问3月6日的股票分时成交明细数据
# 索引语法: list[index]
#   - index是元素在列表中的位置,从0开始计数
#   - list[3]获取索引为3的元素,即第4个元素(索引0,1,2,3)
#   - 这里返回'3975',表示3月4日的收盘价
print(list[3])
# 运行结果: 3975

# 利用负数索引访问最后三天的股票分时成交明细数据
# 负数索引语法: list[-n]
#   - -1表示最后一个元素
#   - -2表示倒数第二个元素
#   - -3表示倒数第三个元素
#   - 以此类推...
#
# 切片语法: list[-3:]
#   - -3作为起始位置,表示从倒数第3个元素开始
#   - 冒号后面省略,表示一直取到列表末尾
#   - 结果包含索引-3,-2,-1对应的元素
#   - 即'4926','4531','4557',分别对应3月8日、3月9日、3月10日的收盘价
print(list[-3:])
# 运行结果: ['4926', '4531', '4557']

任务复盘|题目二:列表索引操作

运行后核对:核对 list 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。

题目二代码解析

正数索引

  • list[3] → 第 4 个元素 '3975'(索引从 0 开始)

负数索引

  • list[-1] → 最后一个元素 '4557'
  • list[-3:] → 最后三个元素 ['4926', '4531', '4557']

列表的可变性:添加与修改

# 添加新元素
list.append('4800')  # 末尾追加
print(len(list))     # 现在有 11 个元素

# 修改元素
list[0] = '4200'     # 直接修改第一个元素

# 删除元素
list.pop()           # 弹出最后一个元素
del list[2]          # 删除索引 2 的元素

列表推导式的金融应用

# 计算日收益率
prices = [10.20, 10.35, 10.15, 10.40, 10.30]
returns = [
    (prices[i] - prices[i-1]) / prices[i-1]
    for i in range(1, len(prices))
]
print(f'日收益率: {returns}')

列表推导式语法简洁,适合批量数据变换。

列表 vs NumPy 数组

特性 Python 列表 NumPy 数组
元素类型 可混合 必须相同
性能 较慢 极快(向量化)
内存 较大 紧凑
金融应用 存储对象 价格/收益率计算

选择建议:数值计算用 NumPy,混合数据用列表。

字典(Dict):键值对映射

Python 字典基于哈希表(Hash Table)实现:

  • 哈希函数hash(key) → 整数
  • 数组存储array[hash % size]
  • 冲突解决:开放寻址法或链表法

为什么字典查找如此快速?

假设我们有 1000 只股票:

  • 列表查找:平均需要 500 次比较(O(n))
  • 字典查找:平均/摊销复杂度为 O(1),仍需哈希、探测与键比较
操作 平均情况 最坏情况
插入 平均/摊销 O(1) O(n)
查找 平均 O(1) O(n)
删除 平均/摊销 O(1) O(n)

具体耗时差异依赖规模、哈希质量、硬件与实现,只能用当前环境基准验证。

运行前预测|题目三:字典的创建与查询

  • 输入预测:运行前先写出 ab 的业务含义、数据类型或取值范围,并判断哪一个输入最可能改变结果。
  • 结果预测:不展开答案,先预测将得到“000”相关结果;同时写出方向、数量级或表格/图形结构。
  • 完成要求:能独立说明本任务从输入到“题目三:字典的创建与查询”结果的关键步骤,原样录入平台代码并得到可核对的运行结果。

⭐ 题目三:字典的创建与查询

# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#题目三
#{}方法创建字典
a = {'600':"上证A股",'900':"上证B股",'000':"深证A股",'200':"深证B股",'400':"三板市场股票"}

#dict()方法
b=dict()
b["600"] = "上证A股"  # 设置b["600"]为"上证A股"
b["900"] = "上证B股"  # 设置b["900"]为"上证B股"
b["000"] = "深证A股"  # 设置b["000"]为"深证A股"
b["200"] = "深证B股"  # 设置b["200"]为"深证B股"
b["400"] = "三板市场股票"  # 设置b["400"]为"三板市场股票"
#查询"000"开头对应股票
print(a['000'])

任务复盘|题目三:字典的创建与查询

运行后核对:核对 ab 是否按预测参与运算,实际输出是否与预测一致;若不一致,先检查类型、单位、索引/字段和运算顺序。

拓展练习:只改变一个关键输入或业务场景,先预测输出如何变化,再运行验证并解释变化原因。

题目三代码解析:两种创建方式

方法一:字面量 {}

  • 代码简洁,一次性定义所有键值对
  • 推荐用于已知全部数据的场景

方法二:dict() 构造函数

  • 动态添加键值对
  • 适合逐步构建字典的场景

查询结果:a['000']'深证A股'

字典的安全访问方式

# 直接访问(键不存在会报错)
print(a['000'])      # '深证A股'

# 安全访问(键不存在返回默认值)
print(a.get('001', '未知'))  # '未知'

# 检查键是否存在
if '000' in a:
    print(a['000'])

建议:生产代码中优先使用 .get() 方法,避免 KeyError

字典的常用方法

# 获取所有键
print(a.keys())
# dict_keys(['600', '900', '000', '200', '400'])

# 获取所有值
print(a.values())

# 获取所有键值对
print(a.items())
# dict_items([('600', '上证A股'), ...])

字典的金融应用场景

股票代码→公司名称

code_to_name = {'600000': '浦发银行', '000001': '平安银行'}

日期→行情数据(嵌套字典)

prices_by_date = {
    '2024-01-02': {'open': 10.20, 'close': 10.25},
    '2024-01-03': {'open': 10.25, 'close': 10.15}
}

技术指标→参数值

indicators = {'MA_short': 5, 'MA_long': 20, 'RSI_period': 14}

三种数据结构选择决策

数据结构选择决策树 一棵决策树,通过两个问题引导用户选择元组、列表或字典。 数据结构选择决策树 需要通过键快速查找? 字典 {} 数据是否需要修改? 列表 [] 元组 ()

总结:三种数据结构对比

特性 字典 {} 列表 [] 元组 ()
可变性 可变 可变 不可变
索引方式 键 (key) 位置 (0,1,2…) 位置
有序性 3.7+ 有序 有序 有序
查找速度 O(1) O(n) O(n)
内存占用 较大 中等 最小
典型用途 键值映射 动态序列 固定配置

随堂练习

  • 问题 1|需要准备哪些数据?:同一金融记录的元组、列表和字典表示。
  • 问题 2|需要完成哪些操作?:比较位置访问、可变更新与键查找,完成“题目二代码解析”。
  • 问题 3|应得到哪些结果?:三种结构的访问/更新结果和选择决策表。
  • 问题 4|怎样确认结果可靠?:检查字典键可哈希与元组元素可哈希边界;结构不匹配任务时先检查原因。
  • 问题 5|换一个情境,怎样继续应用?:把结构选择拓展应用到另一份投资组合记录。
  • 作答提示:请依次写清所用数据、分析过程、所得结果、核对方法和拓展思考。课程所需数据见前言中的下载入口;教学平台固定题按页面说明完成。

教师参考解答|答案与说明 1

  • 所用数据与字段:证券记录含 code:str, close:float, date:str;配置用元组、序列用列表、代码映射用字典。

教师参考解答|代码 1

展开代码(代码区可独立滚动)
market_config=('CNY','Asia/Shanghai')  # 固定币种与时区配置元组
closing_prices=[10.2,10.5,10.1]  # 建立有序收盘价列表示例
security_record={'code':'600000.SH','close':closing_prices[-1],'date':'2025-12-31'}  # 建立有序收盘价列表示例
record_copy=security_record.copy(); removed_date=record_copy.pop('date')  # 用字典绑定证券代码、日期与收盘价
assert market_config[0]=='CNY' and closing_prices[-1]==10.1  # 固定币种与时区配置元组
assert removed_date=='2025-12-31' and 'date' not in record_copy and 'date' in security_record  # 用字典绑定证券代码、日期与收盘价
print(market_config,closing_prices,security_record,record_copy)  # 固定币种与时区配置元组

教师参考解答|答案与说明 2

  • 解释答案:元组适合固定配置、列表保存有序可变序列、字典保存字段到值的映射;选择依据是结构语义和是否需要修改,不是简单比较运行速度。
  • 拓展应用答案:拓展应用到订单记录时,用元组固定币种与时区、列表保存状态轨迹、字典保存订单字段;用副本删除字段并检查原记录未被改写。
  • 参考结果:四个对象及原字典未变。另行核对:尝试 hash((1,[])) 应报 TypeError,证明“元组”不自动等于“可哈希”。
  • 常见错误:把列表作字典键;把浅拷贝当深拷贝;认为元组内部对象都不可变或天然线程安全。