2  列表、字典和元组

2.1 引言Python复合数据类型概述

在金融数据分析中,我们经常需要处理成组的相关数据。Python提供了三种内置的复合数据类型:列表(List)、字典(Dictionary)和元组(Tuple)。这些类型使得数据的组织和操作变得更加高效和直观。

理论背景:数据结构的选择

从计算机科学的角度来看,这三种数据类型代表了不同的抽象层次: - 列表: 动态数组,提供O(1)的随机访问和O(n)的插入/删除 - 字典: 哈希表(Hash Table),提供平均O(1)的查找、插入和删除 - 元组: 不可变序列,适用于存储固定配置和作为字典键

选择合适的数据结构对程序性能有决定性影响,尤其是在处理大规模金融数据时。

2.2 列表(List)

列表是Python中最常用的数据结构之一,它是可变的(Mutable)、有序的(Ordered)序列。在金融应用中,列表常用于存储时间序列数据、投资组合成分等。

2.2.1 列表的创建与基本操作

列表 2.1
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#列表应用
x=['finance','risk management','金融风险管理',8.88]
print(x)  # 输出混合类型列表的内容(含字符串、数值等多种数据类型)

代码深度解析:

  1. 列表的异构性: Python列表可以包含不同类型的元素,这在金融数据预处理中非常有用。例如,可以将股票代码(字符串)、价格(浮点数)和成交量(整数)存储在同一个列表中。

  2. 内存模型: 列表存储的是对象的引用而非对象本身。这意味着:

    • 修改可变对象会影响所有引用它的列表
    • 列表赋值是浅拷贝(Shallow Copy)
  3. 时间复杂度分析:

    • x[i]: O(1) - 直接索引访问
    • x[a:b]: O(k) - k为切片长度
    • len(x): O(1) - 列表对象存储长度信息
    • x.append(item): 均摊O(1) - 动态数组扩容

2.2.2 列表在金融投资组合中的应用

列表 2.2
# =============================================================================
# 题目:管理股票投资组合
# =============================================================================
# 本任务演示如何使用列表存储和管理投资组合中的股票代码
# 列表的可变性使得我们可以动态添加、删除持仓股票

# ==================== 创建初始投资组合 ====================
portfolio = ['600519.SH', '000858.SZ', '600036.SH', '000002.SZ']  # 存储股票代码列表
# 这些是A股市场的股票代码格式:
# 6位数字.SH(上海证券交易所)或.SZ(深圳证券交易所)

# ==================== 添加新股票到投资组合 ====================
portfolio.append('601318.SH')  # 在列表末尾添加元素:'601318.SH'(中国平安)
# append()方法直接修改原列表,不返回新列表
# 时间复杂度:均摊O(1),大多数情况下非常快

# ==================== 从投资组合中移除股票 ====================
portfolio.remove('000002.SZ')  # 移除指定的元素:'000002.SZ'(万科A)
# remove()方法删除第一个匹配的元素
# 如果元素不存在会抛出ValueError异常
# 时间复杂度:O(n),需要遍历查找

# ==================== 统计持仓股票数量 ====================
holdings = len(portfolio)  # 计算列表长度:4
print(f"当前持仓股票数量: {holdings}")  # 输出:当前持仓股票数量: 4
# f-string是Python 3.6+的格式化字符串,用{}插入变量值

# ==================== 遍历投资组合 ====================
print("当前持仓:")  # 输出标题
for i, stock in enumerate(portfolio, start=1):  # 遍历列表,同时获取索引和元素
    print(f"  {i}. {stock}")  # 格式化输出:序号. 股票代码
# enumerate()函数返回(索引, 元素)对
# start=1参数让索引从1开始而不是0

2.3 字典(Dictionary)

字典是Python中最重要的数据结构之一,它是可变的无序的(Python 3.7+保持插入顺序)键值对(Key-Value Pair)集合。在金融应用中,字典非常适合存储结构化数据,如股票信息、交易记录等。

2.3.1 字典的创建与基本操作

列表 2.3
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#字典应用
dict1={'指数名称':'沪深300',
      '证券代码':'000300',  # 沪深300指数的证券代码
      '交易日期':'2022-09-14',  # 交易日期
      '涨跌幅':0.42  # 当日涨跌幅(百分比)
    }  # 创建沪深300指数信息字典
print(dict1)  # 输出沪深300指数信息字典

代码深度解析:

  1. 哈希表的实现原理: Python字典底层使用哈希表(Hash Table)实现:

    • 通过hash(key)计算哈希值
    • 哈希值决定存储位置(桶)
    • 处理哈希冲突:开放寻址法或链地址法

    这使得字典的查找、插入和删除操作的平均时间复杂度为O(1)

  2. 键的要求:

    • 必须是不可变类型(int, float, str, tuple)
    • 不能是可变类型(list, dict, set)
    • 原因:可变对象的哈希值会变化,破坏字典结构
  3. 时间复杂度分析:

    • d[key]: 平均O(1), 最坏O(n)(哈希冲突时)
    • d.get(key): 平均O(1)
    • key in d: 平均O(1)
    • d.items(): O(n)

2.3.2 字典在股票信息管理中的应用

列表 2.4
# =============================================================================
# 题目:存储和操作股票信息
# =============================================================================
# 本任务演示如何用字典存储股票的多维信息,并进行计算和更新操作
# 字典非常适合存储结构化数据,如股票的代码、名称、价格、成交量等

# ==================== 创建股票信息字典 ====================
stock_info = {
    'code': '600519.SH',        # 股票代码(贵州茅台)
    'name': '贵州茅台',          # 股票名称
    'price': 1850.00,           # 股价(元)
    'volume': 2500000,          # 成交量(股)
    'turnover': 4625000000.00   # 成交额(元)
}

# ==================== 计算换手率 ====================
turnover_rate = stock_info['volume'] / stock_info['turnover'] * 100  # 计算换手率(%)
# 换手率 = 成交量 / 成交额 × 100
# 换手率反映股票的流动性,换手率高说明交易活跃
print(f"换手率: {turnover_rate:.2f}%")  # 输出:换手率: 0.05%
# :.2f表示保留2位小数的浮点数格式化

# ==================== 更新股价 ====================
stock_info['price'] = 1860.00  # 修改键'price'对应的值
# 字典是可变的,可以直接修改键值对
# 如果键不存在,会添加新的键值对

# ==================== 添加新字段 ====================
stock_info['PE_ratio'] = 45.5  # 添加市盈率字段
# 市盈率(P/E)是股票估值的重要指标
# PE_ratio = 股价 / 每股收益

# ==================== 检查字段是否存在 ====================
if 'market_cap' in stock_info:  # 检查键'market_cap'是否在字典中
    print(f"市值: {stock_info['market_cap']}")  # 如果存在则输出市值
else:
    print("市值信息缺失")  # 如果不存在则输出提示信息
# in运算符用于检查键是否存在于字典中,时间复杂度O(1)

# ==================== 批量更新字段 ====================
stock_info.update({
    'price': 1870.00,    # 更新股价为1870元
    'volume': 2600000    # 更新成交量为260万股
})
# update()方法可以批量更新字典
# 参数是一个字典,包含要更新的键值对
# 如果键已存在则更新,不存在则添加

补充说明:字典视图对象

dict.keys(), dict.values(), dict.items()返回的是视图对象(View Objects),它们: - 是动态的:反映字典的实时变化 - 支持集合操作:交集、并集等 - 不能直接索引:需转换为list

列表 2.5
# =============================================================================
# 题目:演示字典视图对象的集合操作
# =============================================================================
# 字典视图对象支持类似集合的操作,如交集、并集等
# 这在比较两个字典的键或值时非常有用

# ==================== 创建两个字典 ====================
dict_a = {'a': 1, 'b': 2, 'c': 3}  # 字典A
dict_b = {'b': 2, 'c': 3, 'd': 4}  # 字典B

# ==================== 计算键的交集 ====================
common_keys = dict_a.keys() & dict_b.keys()  # 使用&运算符计算交集
print(common_keys)  # 输出:{'b', 'c'}
# 交集是两个字典共有的键:'b'和'c'
# 字典视图支持集合运算符:&(交集)、|(并集)、-(差集)、^(对称差)

# ==================== 计算值的交集 ====================
common_values = set(dict_a.values()) & set(dict_b.values())  # 将值转为集合后再计算交集
print(common_values)  # 输出:{2, 3}
# 两个字典都有值2和3
# 注意:dict.values()不直接支持集合运算,需先转换为set

2.4 元组(Tuple)

元组是不可变的(Immutable)有序序列。在金融应用中,元组常用于存储固定配置、数据库记录返回值等。

2.4.1 元组的创建与基本操作

列表 2.6
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#元组应用
tup=('finance','风险管理',2022,88.88)
print(tup[1:3])  # 输出元组切片(索引1到2的元素:'风险管理'和2022)

代码深度解析:

  1. 不可变性的意义:
    • 线程安全: 多线程环境下无需加锁
    • 哈希支持: 可作为字典键或集合元素
    • 性能优化: Python可以进行内存优化
    • 数据完整性: 防止意外修改
  2. 命名元组(NamedTuple): 标准元组只能通过索引访问,降低了代码可读性。collections.namedtuple提供了命名字段的元组:
   #| label: lst-namedtuple
   #| lst-cap: '命名元组在金融数据中的应用'

   # =============================================================================
   # 题目:使用命名元组存储股票信息
   # =============================================================================
   # 命名元组是元组的子类,允许通过属性名而不是索引访问元素
   # 这使得代码更可读、更易维护

   # ==================== 导入namedtuple ====================
   from collections import namedtuple  # 从collections模块导入namedtuple工厂函数

   # ==================== 定义命名元组类型 ====================
   Stock = namedtuple('Stock', ['code', 'name', 'price', 'volume'])
   # namedtuple(类型名, 字段名列表)
   # 返回一个新的类型类Stock,可以创建实例
   # 字段名列表也可以是用空格或逗号分隔的字符串:'code name price volume'

   # ==================== 创建命名元组实例 ====================
   stock = Stock('600519.SH', '贵州茅台', 1850.00, 2500000)
   # Stock类型构造函数接受4个参数,对应4个字段

   # ==================== 通过字段名访问(更直观)====================
   print(stock.name)  # 输出:'贵州茅台'
   # 可以像访问对象属性一样使用.语法访问字段
   # 这比使用索引stock[1]更清晰

   print(stock.price)  # 输出:1850.0
   # 不需要记住字段的位置顺序

   # ==================== 仍然支持索引访问 ====================
   print(stock[0])  # 输出:'600519.SH'
   # 命名元组仍然是元组,支持所有元组操作
   # 这保证了向后兼容性
  1. 时间复杂度:
    • 与列表相同,因为底层都是动态数组
    • t[i]: O(1)
    • t[a:b]: O(k)

2.5 数据类型选择指南

在金融编程中,选择合适的数据类型至关重要:

表:数据类型选择决策表

场景 推荐类型 理由 示例
时间序列数据 列表/NumPy数组 有序、可变 股价历史
键值对数据 字典 快速查找 股票信息
固定配置 元组 不可变、安全 交易参数
去重数据 集合 自动去重 唯一股票代码
矩阵运算 NumPy数组 高性能数值计算 相关性矩阵

补充说明:性能考虑

在处理大规模金融数据时,性能差异变得显著:

列表 2.7
# =============================================================================
# 题目:比较列表和字典的查找性能
# =============================================================================
# 本任务通过实验演示列表和字典在查找操作上的性能差异
# 列表查找是O(n),字典查找是O(1),在大数据量时差异巨大

# ==================== 导入时间模块 ====================
import time  # Python标准库,用于时间相关操作

# ==================== 生成测试数据 ====================
n = 1000000  # 数据量:100万元素
test_list = list(range(n))  # 创建包含0到999999的列表
test_dict = {i: f"value_{i}" for i in range(n)}  # 创建字典,键是0到999999
# 字典推导式:{key: value for key in iterable}
# 每个键对应的值是字符串"value_<key>"

# ==================== 测试列表查找性能(O(n))====================
start = time.time()  # 记录开始时间
result = 999999 in test_list  # 在列表中查找999999,需要遍历整个列表
list_time = time.time() - start  # 计算耗时
# in运算符对列表需要逐个比较,平均需要检查n/2个元素

# ==================== 测试字典查找性能(O(1))====================
start = time.time()  # 记录开始时间
result = 999999 in test_dict  # 在字典中查找999999,通过哈希直接定位
dict_time = time.time() - start  # 计算耗时
# 字典通过哈希表实现,查找时间几乎不随数据量增长

# ==================== 输出性能对比结果 ====================
print(f"列表查找时间: {list_time:.6f}秒")  # 输出列表查找耗时
print(f"字典查找时间: {dict_time:.6f}秒")  # 输出字典查找耗时
speedup_ratio = list_time / dict_time if dict_time > 0 else float('inf')  # 防止除零错误
print(f"性能提升: {speedup_ratio:.1f}倍" if speedup_ratio != float('inf') else "性能提升: 字典查找时间趋近于零,提升倍数极大")  # 输出性能提升倍数
# 在100万元素下,字典通常比列表快数千倍甚至上万倍

2.6 深拷贝与浅拷贝

理解Python的拷贝机制对避免数据污染错误至关重要:

列表 2.8
# =============================================================================
# 题目:演示浅拷贝和深拷贝的区别
# =============================================================================
# 浅拷贝只复制外层容器,内层对象仍然共享
# 深拷贝递归复制所有层级,完全独立
# 这对于处理嵌套数据结构(如投资组合列表)非常重要

# ==================== 导入copy模块 ====================
import copy  # Python标准库,提供拷贝操作

# ==================== 创建原始嵌套列表 ====================
portfolio = [
    ['600519.SH', 100, 1850.00],  # 持仓1:股票代码、持有数量、成本价
    ['000858.SZ', 200, 85.50]     # 持仓2:股票代码、持有数量、成本价
]
# 这是一个嵌套列表:外层是列表,内层每个元素也是列表
# 类似二维数组或矩阵结构

# ==================== 创建浅拷贝 ====================
portfolio_shallow = portfolio.copy()  # copy()方法创建浅拷贝
# 或者使用切片:portfolio_shallow = portfolio[:]
# 浅拷贝创建新列表,但内层列表元素仍然是引用

# ==================== 创建深拷贝 ====================
portfolio_deep = copy.deepcopy(portfolio)  # deepcopy()递归复制所有层级
# 深拷贝创建完全独立的新对象,包括所有嵌套对象
# 无论多少层嵌套,都会被完全复制

# ==================== 修改原始数据 ====================
portfolio[0][2] = 1900.00  # 修改第一只股票的成本价:1850.00 -> 1900.00
# 注意:修改的是portfolio[0]这个内层列表的第3个元素

# ==================== 检查浅拷贝是否受影响 ====================
print("浅拷贝受影响:", portfolio_shallow[0][2])  # 输出:1900.00
# 浅拷贝的内层列表仍然指向原始对象
# 修改原始数据会影响浅拷贝,这可能导致意外的数据污染

# ==================== 检查深拷贝是否受影响 ====================
print("深拷贝不受影响:", portfolio_deep[0][2])  # 输出:1850.00
# 深拷贝是完全独立的副本
# 修改原始数据不会影响深拷贝,数据安全性高

最佳实践: - 简单列表(不含嵌套): 使用list.copy()或切片[:] - 嵌套结构: 使用copy.deepcopy() - 字典: 使用dict.copy()(浅拷贝)或copy.deepcopy()(深拷贝)