4、Python基础回顾(一):数据类型、控制流、函数定义,重点讲解生成器与装饰器在数据处理中的应用

说实话,很多做金融数据分析的朋友,Python基础其实都停留在「能跑就行」的阶段。我自己带团队时发现,真正卡住大家的往往不是那些高大上的算法,而是对基础数据结构的理解不够深。今天咱们就花点时间,把这块地基夯实了。

4.1 数据类型:选对容器,事半功倍

Python内置的数据类型,说白了就是装数据的容器。选错了容器,后面写代码会非常痛苦。

类型 特点 金融场景举例
list 有序、可修改、可重复 存储每日收盘价序列
tuple 有序、不可修改 存储交易对(BTC/USDT)
set 无序、唯一、可去重 筛选出现过的股票代码
dict 键值对、查找快 股票代码→公司名称映射

我个人习惯是:能用dict就别用list做查找。我在项目中遇到过有人用list存了10万条交易记录,每次查找都遍历一遍,结果一个接口响应时间飙到3秒。换成dict后,直接降到毫秒级。

核心原则:数据量超过1000条时,优先考虑dict或set。list适合小规模、需要保持顺序的场景。

4.2 控制流:别写「面条代码」

控制流就是if-else和循环。但很多人写着写着就变成了「面条代码」——层层嵌套,自己都看不懂。

举个例子,判断一个交易信号是否有效:

# 不好的写法
if signal == 'buy':
    if volume > 1000:
        if price < ma20:
            execute_trade()
        else:
            pass
    else:
        pass
else:
    pass

# 我推荐的写法
def should_execute(signal, volume, price, ma20):
    if signal != 'buy':
        return False
    if volume <= 1000:
        return False
    if price >= ma20:
        return False
    return True

if should_execute(signal, volume, price, ma20):
    execute_trade()

你想想看,第二种写法是不是清晰多了?提前return这个技巧,能帮你省掉无数个else。

小技巧:循环里尽量用for而不是while。for天然有边界,不容易写出死循环。我曾经在回测系统里写了个while True忘记加break,结果服务器跑了整整一宿……

4.3 函数定义:小而美才是王道

函数设计的核心就一句话:一个函数只做一件事

我见过最离谱的函数,一个函数里同时做了:数据清洗、计算指标、生成图表、发送邮件。这种函数改起来简直是噩梦。

# 好的拆分方式
def clean_data(raw_data):
    """清洗数据:去空值、去异常值"""
    pass

def calculate_ma(prices, window=20):
    """计算移动平均线"""
    pass

def plot_chart(dates, prices, ma):
    """绘制K线图"""
    pass

def send_report(email, chart_path):
    """发送邮件报告"""
    pass

每个函数控制在20行以内。如果超过20行,说明该拆了。

4.4 生成器:处理海量数据的利器

做金融数据处理,最怕的就是内存爆炸。比如你要处理一年的逐笔交易数据,几千万行,一次性读到内存里,16G的机器直接卡死。

这时候生成器就派上用场了。生成器不会一次性把所有数据加载到内存,而是用多少取多少

def read_trades(file_path):
    """逐行读取交易数据,返回生成器"""
    with open(file_path, 'r') as f:
        for line in f:
            yield parse_trade(line)

# 使用生成器处理
for trade in read_trades('trades.csv'):
    if trade['price'] > 100:
        process_trade(trade)

注意那个yield关键字。它和return的区别是:return结束函数,yield暂停函数,下次调用时从暂停处继续。

避坑指南:生成器只能遍历一次。我曾经犯过这个错——把生成器传给两个函数,第二个函数拿到的是空的。解决办法很简单:如果需要多次使用,用list()转成列表,或者重新创建生成器。

生成器还有一个妙用:无限序列。比如生成斐波那契数列,用列表存会爆内存,但生成器可以一直生成下去:

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# 取前10个
fib = fibonacci()
for _ in range(10):
    print(next(fib))

4.5 装饰器:给函数「开挂」

装饰器是Python里最优雅的特性之一。说白了,它就是在不修改原函数代码的情况下,给函数增加额外功能。

金融场景里最常用的装饰器就是计时重试

import time
from functools import wraps

def timer(func):
    """计算函数执行时间"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f'{func.__name__} 耗时 {elapsed:.2f} 秒')
        return result
    return wrapper

@timer
def backtest_strategy(data):
    """回测策略"""
    # 模拟耗时计算
    time.sleep(2)
    return {'sharpe': 1.5, 'max_drawdown': 0.1}

# 调用时自动打印耗时
result = backtest_strategy(data)

另一个实用场景是重试装饰器。做量化交易时,网络请求经常超时,手动写重试逻辑很烦:

def retry(max_attempts=3, delay=1):
    """失败自动重试"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    print(f'第{attempt+1}次失败,{delay}秒后重试...')
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=5, delay=2)
def fetch_market_data(symbol):
    """获取行情数据"""
    # 可能超时的网络请求
    pass

注意:装饰器会改变函数的元信息(如函数名、文档字符串)。记得用@wraps修复,否则调试时看到的函数名全是wrapper,非常坑。

4.6 生成器+装饰器:组合拳打天下

这两个东西组合起来,能解决很多实际问题。我举个例子:流式数据处理管道

def data_source():
    """数据源:生成器"""
    for i in range(100):
        yield i

@timer
def process_pipeline(source):
    """处理管道"""
    # 过滤
    filtered = (x for x in source if x % 2 == 0)
    # 转换
    transformed = (x * 2 for x in filtered)
    # 聚合
    result = sum(transformed)
    return result

# 整个管道都是惰性求值,内存占用极低
result = process_pipeline(data_source())
print(f'结果: {result}')

你想想看,如果数据量是1亿条,用列表推导式直接内存爆炸。但用生成器表达式(注意是圆括号不是方括号),内存占用几乎为0。

我的经验:在金融数据处理中,能惰性求值就别急着计算。生成器+装饰器的组合,让我在处理千万级逐笔数据时,内存占用始终控制在200MB以内。而同事用列表实现同样的功能,直接OOM了。

知识体系总览

下面这张图,帮你把今天讲的内容串起来:

Python基础回顾 · 知识体系 Python基础 数据类型 list / tuple / set / dict 控制流 if-else / for / while 函数定义 单一职责 / 20行原则 生成器 yield / 惰性求值 / 内存友好 装饰器 @wraps / 计时 / 重试 组合应用 流式处理管道 核心思想 选对容器 → 控制复杂度 → 函数拆分 → 惰性求值 → 优雅增强

嗯,今天的内容就到这。记住一句话:基础不牢,地动山摇。这些看似简单的知识点,在实际项目中能帮你省下大量调试时间。下次遇到数据处理性能问题,先想想是不是数据类型选错了,或者能不能用生成器优化一下。


公众号:蓝海资料掘金营,微信deep3321