4. Python基础速通(二):文件读写操作、异常处理机制、面向对象编程入门、装饰器与闭包
好,咱们接着聊。上一章我们把Python的基础语法和数据结构过了一遍,算是把工具箱里的螺丝刀和扳手认全了。这一章,我们要开始真正干活了。
做量化交易,你每天面对的是什么?是数据。数据从哪来?从文件里来,从API里来。数据脏了怎么办?得处理异常。代码写多了怎么复用?得用面向对象。想给函数加点额外功能又不破坏原有逻辑?装饰器就是干这个的。
说白了,这一章就是让你从「能写代码」进化到「能写能用的代码」。我当年刚入行时,就是卡在这一步很久——代码能跑,但一遇到真实数据就崩。嗯,咱们今天就把这些坑填上。
本章核心脉络:文件操作让你能读写数据 → 异常处理让你程序不崩 → 面向对象让你代码好维护 → 装饰器让你代码更优雅。一条线串下来,你就能写出真正能用的量化工具了。
4.1 文件读写操作:你的数据仓库
做量化交易,数据就是命根子。你想想看,每天开盘前第一件事是什么?拉数据。收盘后最后一件事是什么?存数据。文件读写,就是你和数据之间的桥梁。
4.1.1 打开文件的正确姿势
Python里打开文件,最经典的方式就是 open() 函数。但我个人习惯,永远用 with 语句。为什么?因为省心。你不用记着关文件,Python自动帮你收尾。
# 不推荐的方式
f = open('stock_data.csv', 'r')
data = f.read()
f.close() # 万一忘了关,文件句柄就泄漏了
# 推荐的方式
with open('stock_data.csv', 'r') as f:
data = f.read()
# 出了with块,文件自动关闭
我的小技巧:读大文件时别用 read(),它会一次性把文件全读到内存里。我处理过几百万行的逐笔交易数据,用 read() 直接内存爆了。后来改用 for line in f: 逐行读取,稳得很。
4.1.2 读写模式一览
| 模式 | 说明 | 量化场景 |
|---|---|---|
'r' |
只读模式 | 读取历史行情数据 |
'w' |
写入模式(覆盖) | 保存回测结果 |
'a' |
追加模式 | 记录实时交易日志 |
'r+' |
读写模式 | 更新配置文件 |
4.1.3 实战:读取股票CSV数据
做量化最常打交道的就是CSV文件。我早期做回测时,每天手动下载CSV,然后用Python解析。这里有个坑——CSV里经常有脏数据,比如空行、乱码、格式不对。
import csv
def load_stock_data(file_path):
"""加载股票CSV数据,返回列表"""
data = []
with open(file_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
# 过滤空行
if not row['date']:
continue
data.append({
'date': row['date'],
'open': float(row['open']),
'close': float(row['close']),
'volume': int(row['volume'])
})
return data
# 使用示例
stock_data = load_stock_data('sh600519.csv')
print(f"共加载 {len(stock_data)} 条数据")
注意:CSV文件的编码问题是个大坑。我曾经从某数据商拿到的文件是GBK编码,直接用UTF-8读全是乱码。建议先用 chardet 库检测编码,或者直接指定 encoding='gbk' 试试。
4.2 异常处理机制:让你的程序不崩
写代码最怕什么?最怕程序跑着跑着突然崩了。尤其是在实盘交易中,一个异常没处理好,可能就错过了最佳买卖点。异常处理,说白了就是给你的程序穿上防弹衣。
4.2.1 try-except 的基本用法
我刚开始写量化策略时,代码里全是 if...else 判断,生怕哪个数据不对。后来发现,用 try...except 更优雅——你只管写核心逻辑,异常交给专门的处理块。
def safe_divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("除数不能为0,返回None")
result = None
except TypeError:
print("类型错误,请检查输入")
result = None
else:
print("计算成功!")
finally:
print("无论是否异常,我都会执行")
return result
# 测试
print(safe_divide(10, 2)) # 正常
print(safe_divide(10, 0)) # 除零异常
print(safe_divide(10, 'a')) # 类型异常
4.2.2 量化中的异常处理实战
在真实交易中,数据源经常出问题。比如某天交易所数据延迟,或者某只股票停牌导致数据缺失。我习惯写一个健壮的数据加载函数:
def robust_data_loader(file_path):
"""健壮的数据加载器"""
try:
data = load_stock_data(file_path)
if not data:
raise ValueError(f"文件 {file_path} 为空")
return data
except FileNotFoundError:
print(f"文件不存在: {file_path}")
return []
except ValueError as e:
print(f"数据异常: {e}")
return []
except Exception as e:
print(f"未知错误: {e}")
return []
我的经验:不要滥用 except Exception,它会吞掉所有错误,包括你没想到的bug。我建议先捕获具体的异常类型,最后再用一个 except Exception 兜底,同时把错误信息打印出来方便排查。
4.3 面向对象编程入门:把代码组织起来
写量化策略,一开始你可能只是写几个函数,调来调去。但策略一多,参数一复杂,代码就乱成一锅粥。面向对象编程,说白了就是把相关的数据和操作打包在一起,让代码更清晰、更好复用。
4.3.1 类与对象:模板与实例
类就是模板,对象就是根据模板造出来的具体东西。比如「股票」是一个类,而「贵州茅台」就是一个具体的股票对象。
class Stock:
"""股票类"""
def __init__(self, code, name, price):
self.code = code # 股票代码
self.name = name # 股票名称
self.price = price # 当前价格
self.history = [] # 历史价格
def update_price(self, new_price):
"""更新价格并记录历史"""
self.history.append(self.price)
self.price = new_price
def get_return(self):
"""计算收益率"""
if len(self.history) < 1:
return 0
return (self.price - self.history[-1]) / self.history[-1]
# 创建对象
maotai = Stock('600519', '贵州茅台', 1800.0)
maotai.update_price(1820.0)
print(f"{maotai.name} 收益率: {maotai.get_return():.2%}")
4.3.2 继承:站在巨人的肩膀上
做量化时,你可能有很多种策略,但它们都有一些共同点。比如都需要初始化、都需要计算信号、都需要执行交易。用继承,你可以把公共部分提取到父类里。
class BaseStrategy:
"""策略基类"""
def __init__(self, name):
self.name = name
self.positions = {}
def generate_signal(self, data):
"""生成交易信号,子类必须实现"""
raise NotImplementedError("子类必须实现此方法")
def execute(self, data):
"""执行策略"""
signal = self.generate_signal(data)
print(f"{self.name} 生成信号: {signal}")
return signal
class MovingAverageStrategy(BaseStrategy):
"""均线策略"""
def __init__(self, short_window=5, long_window=20):
super().__init__("均线策略")
self.short_window = short_window
self.long_window = long_window
def generate_signal(self, data):
# 计算均线并生成信号
if len(data) < self.long_window:
return "hold"
short_ma = sum(data[-self.short_window:]) / self.short_window
long_ma = sum(data[-self.long_window:]) / self.long_window
return "buy" if short_ma > long_ma else "sell"
# 使用
ma_strategy = MovingAverageStrategy(5, 20)
ma_strategy.execute([100, 102, 101, 103, 105, 104, 106])
核心思想:面向对象不是炫技,是为了让代码更好维护。我见过有人一个策略写500行函数,后来要加个参数,改得头皮发麻。用类封装后,改一个属性就行。
4.4 装饰器与闭包:给函数加点料
装饰器是Python里比较高级的特性,但说白了,它就是一个「函数包装器」。你有一个函数,想在不改它代码的前提下,给它加点功能——比如打日志、算时间、做权限校验。装饰器就是干这个的。
4.4.1 闭包:函数里套函数
闭包是装饰器的基础。简单说,就是在一个函数内部定义另一个函数,并且内部函数用到了外部函数的变量。
def make_multiplier(n):
"""创建一个乘以n的函数"""
def multiplier(x):
return x * n
return multiplier
# 创建两个闭包
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
4.4.2 装饰器:@语法糖
装饰器本质上就是一个接受函数作为参数、返回新函数的闭包。在量化交易中,我经常用装饰器来记录策略执行时间、打印日志、做参数校验。
import time
def timer_decorator(func):
"""计算函数执行时间的装饰器"""
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 执行耗时: {end - start:.4f}秒")
return result
return wrapper
@timer_decorator
def backtest_strategy(data):
"""回测策略"""
time.sleep(0.5) # 模拟计算
return "回测完成"
# 调用
result = backtest_strategy([1, 2, 3])
print(result)
4.4.3 带参数的装饰器
有时候你想让装饰器更灵活,比如指定日志级别。那就再包一层:
def log_decorator(level='INFO'):
"""带参数的日志装饰器"""
def decorator(func):
def wrapper(*args, **kwargs):
print(f"[{level}] 调用函数: {func.__name__}")
result = func(*args, **kwargs)
print(f"[{level}] 函数返回: {result}")
return result
return wrapper
return decorator
@log_decorator(level='WARNING')
def calculate_ma(data):
return sum(data) / len(data)
calculate_ma([100, 102, 101])
避坑指南:使用装饰器时,被装饰函数的元信息(比如 __name__、__doc__)会被替换成 wrapper 的。解决办法是用 functools.wraps 装饰器。我曾经因为这个bug,调试了半天才发现函数名不对。
from functools import wraps
def my_decorator(func):
@wraps(func) # 保留原函数信息
def wrapper(*args, **kwargs):
print("装饰器生效")
return func(*args, **kwargs)
return wrapper
@my_decorator
def hello():
"""打招呼函数"""
print("Hello!")
print(hello.__name__) # 输出: hello(而不是wrapper)
print(hello.__doc__) # 输出: 打招呼函数
好了,这一章的内容就到这。文件读写让你能跟数据打交道,异常处理让你程序更健壮,面向对象让代码更清晰,装饰器让功能扩展更优雅。这四个技能,是写出高质量量化代码的基石。下一章,我们会把这些知识串起来,开始真正搭建一个简单的量化回测框架。