第三章 类型注解与文档字符串:让代码自己会说话

说实话,我见过太多交易系统的代码了。有些代码写得像天书,变量名全是 a、b、c,函数参数全靠猜。你想想看,一个策略信号生成函数,传进来三个参数,你根本不知道哪个是价格、哪个是成交量、哪个是时间戳。这种代码,别说别人看不懂,过两周你自己都忘了。

这一章,我们就来解决这个问题。用类型注解让代码意图一目了然,用文档字符串让函数自己会说话。我在量化交易系统里吃过不少亏,后来养成了写类型注解和文档的习惯,代码质量提升了一个档次。

3.1 Python类型注解入门

Python 3.5 之后引入了类型注解(Type Hints)。说白了,就是给变量、参数、返回值标注上类型。Python 本身不强制检查类型,但有了注解,IDE 和静态检查工具就能帮你发现潜在问题。

核心思想:类型注解不是约束,而是文档。它告诉读代码的人:「这个参数应该是 int,那个返回值是 str」。

先看个最简单的例子:

def add(a: int, b: int) -> int:
    return a + b

这里的 a: int 表示 a 应该是整数,-> int 表示返回值也是整数。嗯,就这么简单。

我在项目中遇到过一个问题:有个同事写了个计算夏普比率的函数,参数传进来的是字符串,结果运行时直接报错。如果当时写了类型注解,这种低级错误根本不会发生。

再看几个常见场景:

# 列表类型
def calculate_returns(prices: list) -> list:
    return [prices[i] - prices[i-1] for i in range(1, len(prices))]

# 字典类型
def get_position(symbol: str, portfolio: dict) -> float:
    return portfolio.get(symbol, 0.0)

# 可选类型
def fetch_data(symbol: str, start_date: str = None) -> dict:
    if start_date is None:
        start_date = "2020-01-01"
    # ... 获取数据逻辑

我的习惯:所有公开函数都写类型注解。内部辅助函数可以酌情省略,但核心逻辑一定要有。这花不了多少时间,但能省下大量调试时间。

3.2 Typing模块详解

基础类型注解够用了,但交易系统里经常遇到更复杂的类型。比如一个函数返回「要么是 float,要么是 None」,或者参数是「字符串列表」。这时候就需要 typing 模块了。

我个人最常用的几个类型:

类型 说明 示例
Optional 可选类型,等价于 Union[X, None] def get_price(symbol: str) -> Optional[float]
Union 联合类型,可以是多种类型之一 def parse_value(data: Union[str, float]) -> float
List 列表,可指定元素类型 def filter_symbols(symbols: List[str]) -> List[str]
Dict 字典,可指定键值类型 def update_portfolio(holdings: Dict[str, float])
Tuple 元组,可指定每个位置类型 def get_best_bid_ask() -> Tuple[float, float]
Callable 可调用对象(函数) def apply_strategy(signal_fn: Callable[[float], bool])

看个实际例子。我在写回测引擎时,有个函数需要接收一个「信号生成函数」:

from typing import List, Dict, Callable, Optional

def run_backtest(
    prices: List[float],
    signal_generator: Callable[[List[float]], List[int]],
    initial_capital: float = 10000.0
) -> Dict[str, float]:
    """
    运行回测
    
    Args:
        prices: 价格序列
        signal_generator: 信号生成函数,输入价格序列,输出信号列表(1买入,-1卖出,0持有)
        initial_capital: 初始资金
    
    Returns:
        包含回测结果的字典,如 {'total_return': 0.15, 'sharpe': 1.2}
    """
    # ... 回测逻辑
    pass

你看,有了类型注解,这个函数的输入输出一目了然。别人调用时,不用翻文档就知道该传什么。

避坑指南:我曾经在代码里用了 List 但忘了从 typing 导入,结果 IDE 报错。记住:基础类型如 list、dict 可以直接用,但泛型类型如 List[int] 必须从 typing 导入。

3.3 Google风格Docstring编写

类型注解解决了「是什么类型」的问题,但没解决「这个函数是干什么的」的问题。这就需要文档字符串(Docstring)了。

文档字符串的写法有很多种,我个人最推荐 Google 风格。为什么?因为它结构清晰,可读性强,而且能被 Sphinx 自动解析生成文档。

Google 风格的基本结构:

def calculate_moving_average(
    prices: List[float],
    window: int = 20
) -> List[float]:
    """
    计算移动平均线。
    
    使用简单移动平均(SMA)方法,对价格序列进行平滑处理。
    常用于趋势跟踪策略的信号生成。
    
    Args:
        prices: 价格序列,按时间升序排列
        window: 移动窗口大小,默认20
    
    Returns:
        移动平均线序列,长度与输入相同,前 window-1 个值为 NaN
    
    Raises:
        ValueError: 当 window 小于 1 或 prices 为空时抛出
    
    Example:
        >>> prices = [1.0, 2.0, 3.0, 4.0, 5.0]
        >>> calculate_moving_average(prices, 3)
        [nan, nan, 2.0, 3.0, 4.0]
    """
    if window < 1 or not prices:
        raise ValueError("window must be >= 1 and prices must not be empty")
    
    result = [float('nan')] * (window - 1)
    for i in range(window - 1, len(prices)):
        result.append(sum(prices[i - window + 1:i + 1]) / window)
    return result

注意几个要点:

  • 第一行是简短描述,用一句话说清楚函数功能
  • 空一行后写详细描述,可以多行
  • Args 部分:每个参数一行,格式是「参数名: 描述」
  • Returns 部分:描述返回值,包括类型和含义
  • Raises 部分:列出可能抛出的异常
  • Example 部分:给出使用示例,方便别人快速理解

我的经验:写文档字符串时,把自己当成第一次看这个函数的人。想想你最想知道什么?参数含义、返回值、异常情况、使用示例。把这些写清楚就够了。

3.4 Sphinx自动生成文档

代码写好了,文档也写了,但没人愿意翻代码看文档。这时候就需要 Sphinx 了。它能自动从你的代码和文档字符串中生成漂亮的 HTML 文档。

配置 Sphinx 其实很简单:

# 1. 安装 Sphinx
pip install sphinx sphinx-autodoc

# 2. 初始化 Sphinx 项目
sphinx-quickstart docs

# 3. 在 conf.py 中启用 autodoc
extensions = ['sphinx.ext.autodoc']

# 4. 生成文档
cd docs
make html

然后你需要在 .rst 文件中引用你的模块:

.. automodule:: your_trading_module
   :members:
   :undoc-members:
   :show-inheritance:

运行 make html 后,Sphinx 会扫描你的代码,提取所有文档字符串,生成漂亮的 HTML 文档。每个函数、类、模块都有独立的页面,参数、返回值、示例都清晰展示。

我在团队里推行过这个流程。一开始大家觉得麻烦,但看到自动生成的文档后,都说「真香」。特别是新同事入职时,直接看文档就能上手,不用追着老同事问了。

核心价值:类型注解 + 文档字符串 + Sphinx = 代码即文档。你只需要维护一份代码,文档自动生成,永远不会过时。

3.5 本章知识体系

下面这张图展示了本章的核心知识结构:

类型注解与文档字符串知识体系 代码质量提升 类型注解 基础类型: int, str, float 集合类型: List, Dict, Tuple 特殊类型: Optional, Union 可调用: Callable 文档字符串 Google风格结构 Args / Returns / Raises Example示例 简短描述 + 详细说明 Sphinx文档生成 安装配置 autodoc扩展 RST文件编写 HTML文档生成 代码即文档:一次编写,自动生成,永不失效

这三者相辅相成:类型注解告诉别人「是什么」,文档字符串告诉别人「干什么」,Sphinx 把这一切变成漂亮的文档。少了任何一个环节,代码的可维护性都会打折扣。

好了,这一章的内容就到这里。记住:写类型注解和文档字符串不是浪费时间,而是在为未来的自己铺路。你想想看,三个月后你回来改代码,看到清晰的类型注解和完整的文档,那种感觉有多爽?


专注资料整理