4、Python基础速通(下):文件读写操作、异常处理、面向对象编程基础、装饰器与生成器

好,咱们接着聊。上一节我们把Python的变量、流程控制、函数这些基本功过了一遍。这一节,我带你搞定真正写量化系统时天天要碰到的几个硬骨头:文件读写、异常处理、面向对象、装饰器和生成器。

说实话,这几个东西单独拎出来讲,每个都能讲一整天。但咱们的目标是「速通」——够用、能用、别踩坑就行。我当年刚入行时,就是在这几个地方摔过跟头,今天一并给你指出来。

4.1 文件读写操作:你的数据仓库入口

做量化交易,第一步就是拿数据。数据从哪来?要么从API接口拉,要么从本地文件读。文件读写,说白了就是你和硬盘之间的对话。

4.1.1 打开与关闭文件

Python内置的open()函数,是打开文件的唯一方式。模式有几种:'r'读、'w'写(覆盖)、'a'追加、'rb'读二进制。

# 最基础的写法
f = open('trade_log.txt', 'r')
content = f.read()
f.close()  # 别忘了关!

嗯,这里要注意。我见过太多新手写完代码不关文件,结果程序跑着跑着就报「Too many open files」。你想想看,一个量化策略跑一天,打开几百个文件不关,系统不崩才怪。

我曾经踩过的坑: 早期写回测引擎时,忘了在异常分支里关文件。结果程序崩溃后,文件句柄一直占着,下次启动直接报错。后来我养成了习惯——永远用 with 语句。

4.1.2 推荐写法:with 上下文管理器

with open('trade_log.txt', 'r') as f:
    content = f.read()
# 出了这个缩进块,文件自动关闭

我个人习惯,所有文件操作都用with。它不光帮你自动关文件,还能在异常发生时正确释放资源。说白了,就是给你上了个保险。

4.1.3 读取方式对比

方法 用途 适用场景
read() 一次性读全部 小文件(<100MB)
readline() 读一行 逐行处理日志
readlines() 读所有行到列表 需要随机访问行

做量化时,我最常用的是逐行读取。比如处理CSV格式的行情数据,一行就是一条tick,逐行处理内存压力小。

with open('market_data.csv', 'r') as f:
    for line in f:
        # 处理每一行行情数据
        process_tick(line.strip())

4.1.4 写入文件

with open('strategy_result.txt', 'w') as f:
    f.write('策略收益率: 15.3%\n')
    f.write('最大回撤: 8.2%\n')

注意,'w'模式会清空原文件。如果你想追加,用'a'模式。我写日志文件时,永远用'a',不然每次启动策略,历史日志就没了。

4.2 异常处理:别让程序说崩就崩

量化交易里,什么意外都可能发生。网络断了、数据格式变了、除零了……代码写得再小心,也防不住所有情况。异常处理,就是给你的程序穿上防弹衣。

4.2.1 try-except 基本结构

try:
    price = float(input_data)
    result = 100 / price
except ValueError:
    print('输入不是有效数字')
except ZeroDivisionError:
    print('价格不能为零')

为什么会这样?因为float('abc')会抛ValueError100 / 0会抛ZeroDivisionError。你不抓它,程序就挂了。

我的建议: 别用裸的 except: 抓所有异常。你想想看,连键盘中断(Ctrl+C)都被你抓了,程序想停都停不下来。至少写成 except Exception as e:,还能打印出错误信息。

4.2.2 else 和 finally

try:
    data = fetch_from_api()
except ConnectionError:
    print('网络连接失败,稍后重试')
else:
    # 没有异常时才执行
    save_to_db(data)
finally:
    # 无论有没有异常都执行
    close_connection()

else块我用的不多,但finally块是宝贝。释放数据库连接、关闭网络套接字,这些收尾工作放finally里最安全。

4.2.3 自定义异常

做量化系统时,我习惯定义自己的异常类。比如价格异常、订单被拒等,方便上层统一处理。

class PriceError(Exception):
    """价格数据异常"""
    pass

def validate_price(price):
    if price <= 0:
        raise PriceError(f'无效价格: {price}')

4.3 面向对象编程基础:把代码组织成「东西」

面向对象编程(OOP),说白了就是把数据和操作数据的方法打包在一起。做量化系统,你会遇到各种「东西」:策略、订单、账户、K线……用类来建模,代码会清晰很多。

4.3.1 类与对象

class Strategy:
    def __init__(self, name, params):
        self.name = name
        self.params = params
        self.position = 0
    
    def on_tick(self, tick_data):
        # 处理行情数据
        pass
    
    def on_order(self, order):
        # 处理订单回报
        pass

# 创建策略实例
ma_strategy = Strategy('双均线策略', {'fast': 5, 'slow': 20})

__init__是构造函数,创建对象时自动调用。self指向实例本身,每个方法第一个参数都是它。我刚开始学的时候,老忘写self,结果报错「missing 1 required positional argument」——嗯,这个坑你大概率也会踩。

4.3.2 三大特性:封装、继承、多态

封装:把数据和操作藏起来,只暴露接口。

class Account:
    def __init__(self):
        self.__balance = 0  # 私有属性,外部不能直接访问
    
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
    
    def get_balance(self):
        return self.__balance

继承:子类复用父类的代码。

class BaseStrategy:
    def init(self):
        print('初始化策略')
    
    def run(self):
        print('运行策略')

class MyStrategy(BaseStrategy):
    def run(self):
        print('我的策略逻辑')
        super().run()  # 调用父类方法

多态:同一个接口,不同实现。比如多个策略类都有run方法,但逻辑不同。

我在项目中遇到过: 写回测框架时,把所有策略都继承自同一个基类。这样回测引擎只需要调用 strategy.run(),不用管具体是哪个策略。这就是多态的好处——代码扩展性极强。

4.4 装饰器:给函数「加料」

装饰器,说白了就是一个函数,用来修改另一个函数的行为。做量化时,我常用它来做日志记录、性能计时、权限校验。

4.4.1 最简单的装饰器

def log_time(func):
    def wrapper(*args, **kwargs):
        print(f'开始执行 {func.__name__}')
        result = func(*args, **kwargs)
        print(f'执行完毕')
        return result
    return wrapper

@log_time
def calculate_ma(data):
    # 计算均线
    return sum(data) / len(data)

你看,@log_time相当于执行了calculate_ma = log_time(calculate_ma)。每次调用calculate_ma,实际上调用的是wrapper函数。

4.4.2 带参数的装饰器

def retry(max_attempts=3):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f'第{i+1}次尝试失败: {e}')
            raise Exception('重试次数用尽')
        return wrapper
    return decorator

@retry(max_attempts=5)
def fetch_price(symbol):
    # 获取价格,可能网络不稳定
    pass

我曾经写过一个数据采集程序,网络偶尔会断。加上这个重试装饰器后,代码干净多了——不用在每个函数里写重复的重试逻辑。

4.5 生成器:省内存的「懒人」神器

生成器,就是按需生成数据,而不是一次性全部算好。做量化时,处理海量行情数据,内存是稀缺资源。生成器正好派上用场。

4.5.1 用 yield 创建生成器

def read_large_file(file_path):
    with open(file_path, 'r') as f:
        for line in f:
            yield line.strip()

# 使用生成器
for row in read_large_file('huge_data.csv'):
    process_row(row)

为什么用生成器?你想想看,一个10GB的CSV文件,一次性读到内存里,机器直接卡死。用生成器,一次只处理一行,内存占用几乎为零。

4.5.2 生成器表达式

# 列表推导式(一次性生成所有)
prices = [get_price(s) for s in symbols]

# 生成器表达式(按需生成)
prices = (get_price(s) for s in symbols)

区别在哪?列表推导式返回一个列表,所有数据都在内存里。生成器表达式返回一个生成器对象,每次迭代才计算下一个值。我处理上千只股票的行情时,永远用生成器表达式。

我的经验: 生成器只能迭代一次。如果你需要反复遍历数据,老老实实转成列表。我曾经在回测循环里用了生成器,结果第二次循环时发现数据没了——排查了半天才找到原因。

4.6 本章知识体系

下面这张图,帮你把这一节的知识点串起来。你看,文件读写是数据入口,异常处理是安全网,面向对象是组织代码的骨架,装饰器是功能增强器,生成器是内存优化器。五者配合,才能写出健壮的量化系统。

Python基础速通(下) 文件读写操作 异常处理 面向对象编程 装饰器 生成器 量化交易系统核心基础设施 open/with/读写模式 try/except/finally 类/继承/封装 @语法/日志/重试 yield/惰性求值

好了,这一节的内容就到这。文件读写让你能存取数据,异常处理让你代码更健壮,面向对象让代码更好组织,装饰器让功能扩展更优雅,生成器让内存使用更高效。这五个工具,你在后面的量化系统开发中会反复用到。

记住,代码是写给人看的,顺便让机器执行。把这些基础打牢,后面写策略、搭回测框架时,你会感谢今天自己的努力。