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

本章学习目标

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

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

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

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

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

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

元组(Tuple):不可变序列

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

不可变性的四大优势:

  • 线程安全:多线程环境下无需加锁
  • 哈希能力:可作为字典的键或集合的元素
  • 内存效率:Python 可对不可变对象进行优化
  • 契约保证:保证数据不会被意外修改

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

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

# 不可变订单(推荐)
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)

⭐ 题目一:元组切片操作

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

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

# 场景说明:
# 某股票在连续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')
('3975', '3407', '2894', '3217')

题目一代码解析

  • 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) 同上

⭐ 题目二:列表索引操作

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

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

# 场景说明:
# 存储某股票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']
3975
['4926', '4531', '4557']

题目二代码解析

正数索引

  • 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))
  • 字典查找:只需 1 次哈希计算(O(1))
操作 平均情况 最坏情况
插入 O(1) O(n)
查找 O(1) O(n)
删除 O(1) O(n)

性能差异可达 500 倍

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

Listing 3
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#题目三
#{}方法创建字典
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'])
深证A股

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

方法一:字面量 {}

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

方法二: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)
内存占用 较大 中等 最小
典型用途 键值映射 动态序列 固定配置