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')会抛ValueError,100 / 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 本章知识体系
下面这张图,帮你把这一节的知识点串起来。你看,文件读写是数据入口,异常处理是安全网,面向对象是组织代码的骨架,装饰器是功能增强器,生成器是内存优化器。五者配合,才能写出健壮的量化系统。
好了,这一节的内容就到这。文件读写让你能存取数据,异常处理让你代码更健壮,面向对象让代码更好组织,装饰器让功能扩展更优雅,生成器让内存使用更高效。这五个工具,你在后面的量化系统开发中会反复用到。
记住,代码是写给人看的,顺便让机器执行。把这些基础打牢,后面写策略、搭回测框架时,你会感谢今天自己的努力。