← 返回模块
3.1.1.3beta 可读 · 未来付费校验通过内容版本 2026-05-26

容器类型与推导式

3.1.1 · Python 语言基础 · 编程

旁边那台的同事盯沪深300 ETF 的日内 tick 流。早上 10 点,她已经积了 8 万条 tick 行的 CSV;她的开盘脚本只是一段 Python:把每行读进来,过滤掉 volume <= 100 的,乘出 price * volume 作为成交金额(notional turnover),再按代码加总,打印前五大成交。今天早上她重写了内层循环,整段跑得快了 7 秒。诀窍不在算法——而在把二十行 for-with-append 删掉,换成两条列表推导式(list comprehension)加一次 dict.get(code, 0.0)。这就是本课要给你的工具:会按访问模式挑容器、不靠 Stack Overflow 就能切片、把开盘那段日内过滤写成一行可读表达式。

四种容器,四种用途

Python 内置四种容器,覆盖了一个单进程脚本里要用到的绝大多数场景。list 有序、可变、可重复(ordered, mutable, allows duplicates),是「我要一段逐步搭起来的序列」的默认选项。tuple 有序、不可变(ordered, immutable);用在固定记录(一个 (code, price, volume) 三元组)或需要可哈希(hashable)以便放进 set / dict 键时。dict 是哈希键映射(hash-keyed mapping),平均 O(1) 查找——表达「拿代码查 股票名称」或「按代码计数」就用它。set 是无序的唯一元素集合,平均 O(1) 成员查询,支持代数运算 |(并)、&(交)、-(差)、^(对称差)。

口诀:有序且要重复 → list;固定字段记录 → tuple;键到值 → dict;问「在不在」/「两边交集」 → set

索引与切片

liststrtuple 都支持按位置索引;负下标从右数,a[0] 是首项、a[-1] 是末项。切片 a[start:stop:step] 返回​​新对象​​,start 包含、stop 不包含,二者皆可缺省,step 为负则反向。四个值得背下来的惯用写法:

a = [10, 20, 30, 40, 50]
a[::-1]      # [50, 40, 30, 20, 10]
a[1:-1:2]    # [20, 40]
a[:3]        # [10, 20, 30]
a[-1]        # 50

切片总是新建对象——a[:] is aFalse;这也是浅复制(shallow copy)的一行写法 b = a[:]

list 的方法分两类,混淆会被 NoneType has no attribute ... 这条异常咬一次:原地修改(in-place mutation)的 lst.append(x)lst.extend(other)lst.pop(i)lst.sort()——它们改 lst 并返回 None;不修改原对象的 sorted(lst)lst + otherlst * 2——返回新列表。

字典与集合

dict 字面量长这样:{ "510300": "300ETF", "510500": "500ETF" }。键必须可哈希(hashable)——字符串、数字、可哈希元素组成的元组都行,list 不行。读取用 d[key](缺失抛 KeyError)或 d.get(key, default)(缺失返回默认值)。d[key] = d.get(key, 0) + amount 是按桶累加(bucket counter)的惯用写法;当桶里装的是列表则用 d.setdefault(key, []).append(item)。写到第一百次时你就会去查下一课的 collections.Counter。按键遍历 for k in d:,按 (键, 值) 遍历 for k, v in d.items()

set 字面量长这样:{ "600519", "510300", "159919" }s.add(x)s.remove(x) 原地修改;x in s 平均 O(1)。代数写法很省事:held & watchlist 同时持有且关注的代码;held - covered 持有但尚未对冲的头寸。

遍历助手与解包

三种模式几乎出现在每个数据循环里。enumerate(seq) 产出 (下标, 元素) 对,省掉 i = 0; i += 1 的丑陋写法。zip(seq1, seq2) 同步遍历两条序列、在较短那条用尽时停下;「从价格序列算收益率」就是一行。元组解包(tuple unpacking)让你不靠下标拿到元素:

prices_t0 = [18.30, 1820.50, 3.45]
prices_t1 = [18.55, 1825.10, 3.46]

for code_index, (p0, p1) in enumerate(zip(prices_t0, prices_t1)):
    print(code_index, (p1 - p0) / p0)

星号解包(star unpacking)偶尔好用:first, *rest = lstfirst 绑到 lst[0]rest 绑到余下。

推导式

for-with-append 实在出现得太多,Python 给它配了专门的语法。列表推导式 [expr for x in it if cond] 是单个表达式,从一个可迭代对象构造新列表,过滤可选。同一计算两种写法:

# loop form, three lines
squares = []
for x in range(10):
    if x % 2 == 0:
        squares.append(x * x)

# comprehension, one line — same result
squares = [x * x for x in range(10) if x % 2 == 0]

两段都得到 [0, 4, 16, 36, 64]。推导式不仅更短,还更快(没有每次 appendLOAD_METHODCALL),也更难写错(少了一处可能忘掉的 append)。字典推导式与集合推导式同构:{k: v for ... }{expr for ... }

值得一记的还有​​生成器表达式(generator expression)​ (expr for x in it)。圆括号(或作为单一函数实参时省掉外层括号)让它惰性求值——只有外层迭代时才逐项算出。写成 sum(x*x for x in range(10_000_000)),它把一千万个平方一一流过 sum、不在内存里建任何列表;列表推导式版本要把一千万个 int 对象全部物化(CPython 上每个小整数对象约 28 字节,整体几百 MB 起步)。默认用列表推导式;序列大、且只消费一次时换成生成器表达式。

把开头那段日内过滤压缩成一行:

ticks = [
    {"code": "600519", "price": 1820.50, "volume": 200},
    {"code": "000001", "price": 12.30,   "volume": 80},
    {"code": "510300", "price": 4.05,    "volume": 1200},
    {"code": "600519", "price": 1820.30, "volume": 300},
    {"code": "510300", "price": 4.06,    "volume": 900},
]

notional = [t["price"] * t["volume"] for t in ticks if t["volume"] > 100]
# [364100.0, 4860.0, 546090.0, 3654.0]

二十行删到四行,懂语法的同事五秒就能审完——这是要追的标准。

练习

Exercise

Given prices = [100.0, 101.5, 99.0, 102.0, 103.5, 100.5], write a one-line list comprehension that produces the list of simple returns [(prices[i] - prices[i-1]) / prices[i-1] for i in range(1, len(prices))]. Confirm the first entry rounds to 0.015 and the list has length 5.

提示
推导式形态:[(prices[i] - prices[i-1]) / prices[i-1] for i in range(1, len(prices))]range 从 1 开始,因为下标 0 没有前一价;这也是结果长度等于 len(prices) - 1 的原因。
提示
第一项手算:(101.5 - 100.0) / 100.0 = 0.015。长度:range(1, 6) 给出 5 个值 1, 2, 3, 4, 5,因此推导式产出 5 个收益。

衔接下一课

你已能挑容器、做切片、并发遍历、把十行循环压成一行推导式。但一个真实程序的外壳还差几样:怎么把代码拆到两个文件、互相 import;怎么处理严格校验函数抛出的 ValueError;怎么用 with open(...) 读那个开盘脚本真正在打开的 CSV。下一课覆盖 Python 的导入模型、try / except / else / finallywith 上下文管理器(context manager),以及 math / datetime / collections / pathlib 四个 stdlib 模块——你每个工作日都会拿出来用的那几样。延伸阅读:廖雪峰列表 / 元组 / dict / set / 迭代 / 生成器各节,《流畅的 Python》第 2 版第 2、3 章。