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

37.1 引言 Python数据结构在金融数据管理中的重要性

在金融数据分析和量化交易系统中,数据结构的选择是决定程序性能的关键因素。Python提供的三种基本数据结构——元组、列表和字典,各自具有独特的特性和适用场景。理解它们的底层实现原理、时间复杂度特性以及在金融领域的最佳实践,是编写高效Python金融程序的基础。

理论背景:数据结构的抽象数据类型(ADT)视角

从抽象数据类型(Abstract Data Type)的角度来看,这三种结构代表了不同的数据组织哲学:

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

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

  1. 元组(Tuple): 存储不变配置,如股票代码、交易参数
  2. 列表(List): 管理时间序列数据,如价格历史、收益率序列
  3. 字典(Dict): 建立映射关系,如股票代码到名称、日期到行情

37.2 元组(Tuple)不可变序列

理论背景:不可变性的价值

元组是Python中不可变(Immutable)序列类型。不可变性看似是限制,实则是优势:

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

补充说明:不可变性在金融系统中的意义

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

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

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

37.2.1 题目一 元组切片操作

在金融数据分析中,我们经常需要从历史价格数据中提取特定时间范围的数据进行切片分析。元组切片提供了一种高效的方式来访问序列的子集。

列表 37.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')

代码深度解析:

  1. 切片语法 [start:end:step]:

    • start: 起始索引(包含),省略则从0开始
    • end: 结束索引(不包含),省略则到末尾
    • step: 步长,省略则为1
    • 本例中: tuple[3:7] 获取索引3、4、5、6的元素
      • 索引3: ‘3975’ (3月6日)
      • 索引4: ‘3407’
      • 索引5: ‘2894’
      • 索引6: ‘3217’ (3月9日)
      • 不包含索引7
  2. 负数索引:

    tuple[-1]   # 最后一个元素('4557')
    tuple[-3:]  # 最后三个元素(['4531', '4557'])
    tuple[:-2]  # 除最后两个外的所有元素
  3. 元组的不可变性演示:

    try:
        tuple[0] = '5000'  # 尝试修改
    except TypeError as e:
        print(f'元组不可修改错误: {e}')
  4. 元组 vs 列表的选择:

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

37.3 列表(List)可变序列

理论背景:动态数组的实现原理

Python列表是基于动态数组(Dynamic Array)实现的。其核心特性:

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

数学分析:扩容策略

Python列表的扩容采用超额分配策略: - 新容量 ≈ 旧容量 × 1.125 + 常数 - 这种策略平衡了内存使用和性能

37.3.1 题目二 列表索引操作

列表是Python中最常用的可变序列类型,在金融应用中广泛用于存储和管理时间序列数据。本任务演示如何通过索引访问列表中的特定元素。

列表 37.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']

代码深度解析:

  1. 正数索引:

    • list[3] 获取索引3的元素:‘3975’(3月6日数据)
    • 索引从0开始,所以索引3是第4个元素
  2. 负数索引:

    • list[-1] 最后一个元素(‘4557’)
    • list[-3:] 最后三个元素
      • list[-3] = ‘4926’
      • list[-2] = ‘4531’
      • list[-1] = ‘4557’
  3. 列表的可变性:添加新价格:

    list.append('4800')  # 在末尾添加新元素
    print(list)  # 现在有11个元素
  4. 列表的时间复杂度: | 操作 | 时间复杂度 | 说明 | |——|———–|——| | append() | O(1)* | 平均情况,amortized | | pop() | O(1) | 弹出末尾元素 | | insert() | O(n) | 需要移动后续元素 | | del | O(n) | 同上 | | index() | O(n) | 线性搜索 | | in | O(n) | 同上 |

  5. 列表推导式的金融应用:

    # 计算日收益率
    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数组 - 混合数据(如交易记录列表) → Python列表

37.4 字典(Dict)键值对映射

理论背景:哈希表的实现原理

Python字典基于哈希表(Hash Table)实现,核心思想:

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

数学分析:时间复杂度

操作 平均情况 最坏情况 说明
插入 O(1) O(n) 哈希冲突时
查找 O(1) O(n) 同上
删除 O(1) O(n) 同上

实际应用中,Python的哈希函数设计良好,最坏情况极少出现。

补充说明:为什么字典查找如此快速?

假设我们有1000只股票: - 列表查找: 平均需要500次比较(O(n)) - 字典查找: 只需1次哈希计算(O(1))

性能差异: 500倍!

37.4.1 题目三 字典的创建与查询

字典是Python中最重要的数据结构之一,它实现了键值对(Key-Value Pair)的映射关系。在金融应用中,字典广泛用于存储股票代码与公司名称的对应关系、配置参数、技术指标设置等信息。

列表 37.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'])

代码深度解析:

  1. 字典的两种创建方法对比:

    • **方法1: 字面量 {}
      • 代码更简洁清晰
      • 一次性定义所有键值对
      • 推荐用于已知全部数据的情况
    • **方法2: dict()构造函数
      • 动态添加键值对
      • 适合逐步构建字典的场景
  2. 字典查询操作:

    # 直接访问(键不存在会报错)
    print(a['000'])  # '深证A股'
    
    # 安全访问(键不存在返回None或默认值)
    print(a.get('001', '未知'))  # '未知'
    
    # 检查键是否存在
    if '000' in a:
        print(a['000'])
  3. 字典的常用方法:

    # 获取所有键
    print(a.keys())  # dict_keys(['600', '900', '000', '200', '400'])
    
    # 获取所有值
    print(a.values())  # dict_values([...])
    
    # 获取所有键值对
    print(a.items())  # dict_items([('600', '上证A股'), ...])
  4. 字典的金融应用场景:

    • 股票代码→公司名称:

      code_to_name = {'600000': '浦发银行', '000001': '平安银行'}
    • 日期→行情数据:

      prices_by_date = {
          '2024-01-01': {'open': 10.20, 'close': 10.25},
          '2024-01-02': {'open': 10.25, 'close': 10.15}
      }
    • 技术指标→参数值:

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

易混淆概念辨析:字典 vs 列表 vs 元组

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

选择决策树:

是否需要通过键快速查找?
├─ 是 → 使用字典
└─ 否 → 数据是否需要修改?
    ├─ 是 → 使用列表
    └─ 否 → 使用元组