第六章:面向对象编程
软件工程的目的是控制复杂性,而不是创建它。
Pamela Zave
介绍
面向对象编程(OOP)是当今最流行的编程范式之一。正确使用时,它与过程式编程相比提供了许多优势。在许多情况下,OOP 似乎特别适用于金融建模和实施金融算法。然而,也有许多对 OOP 持批评态度的人,对 OOP 的单个方面甚至整个范式表示怀疑。本章对此持中立态度,认为 OOP 是一个重要的工具,可能不是每个问题的最佳解决方案,但应该是程序员和从事金融工作的量化人员的手头工具之一。
随着 OOP 的出现,一些新的术语也随之而来。本书和本章的最重要术语是(更多细节如下):
类
对象类的抽象定义。例如,一个人类。
属性
类的特性(类属性)或类的实例(实例属性)的一个特征。例如,是哺乳动物或眼睛的颜色。
方法
可以在类上实现的操作。例如,行走。
参数
一个方法接受的输入参数以影响其行为。例如,三个步骤。
对象
类的一个实例。例如,有蓝眼睛的 Sandra。
实例化
创建基于抽象类的特定对象的过程。
转换为 Python 代码,实现人类示例的简单类可能如下所示。
In [1]: class HumanBeing(object): # ① def __init__(self, first_name, eye_color): # ② self.first_name = first_name # ③ self.eye_color = eye_color # ④ self.position = 0 # ⑤ def walk_steps(self, steps): # ⑥ self.position += steps # ⑦
①
类定义语句。
②
在实例化时调用的特殊方法。
③
名字属性初始化为参数值。
④
眼睛颜色属性初始化为参数值。
⑤
位置属性初始化为 0。
⑥
使用steps
作为参数的步行方法定义。
⑦
给定steps
值后改变位置的代码。
根据类定义,可以实例化并使用一个新的 Python 对象。
In [2]: Sandra = HumanBeing('Sandra', 'blue') # ① In [3]: Sandra.first_name # ② Out[3]: 'Sandra' In [4]: Sandra.position # ② Out[4]: 0 In [5]: Sandra.walk_steps(5) # ③ In [6]: Sandra.position # ④ Out[6]: 5
①
实例化。
②
访问属性值。
③
调用方法。
④
访问更新后的position
值。
有几个人类方面可能支持使用 OOP:
自然的思考方式
人类思维通常围绕着现实世界或抽象对象展开,比如汽车或金融工具。面向对象编程适合模拟具有其特征的这类对象。
降低复杂性
通过不同的方法,面向对象编程有助于降低问题或算法的复杂性,并逐个特征进行建模。
更好的用户界面
在许多情况下,面向对象编程可以实现更美观的用户界面和更紧凑的代码。例如,当查看NumPy
的ndarray
类或pandas
的DataFrame
类时,这一点变得显而易见。
Python 建模的方式
独立于面向对象编程的优缺点,它只是 Python 中的主导范式。这也是“在 Python 中一切皆为对象。”这句话的由来。面向对象编程还允许程序员构建自定义类,其实例的行为与标准 Python 类的任何其他实例相同。
也有一些技术方面可能支持面向对象编程:
抽象化
使用属性和方法可以构建对象的抽象、灵活的模型——重点放在相关的内容上,忽略不需要的内容。在金融领域,这可能意味着拥有一个以抽象方式模拟金融工具的通用类。这种类的实例将是由投资银行设计和提供的具体金融产品,例如。
模块化
面向对象编程简化了将代码拆分为多个模块的过程,然后将这些模块链接起来形成完整的代码基础。例如,可以通过一个类或两个类来建模股票上的欧式期权,一个用于基础股票,另一个用于期权本身。
继承
继承指的是一个类可以从另一个类继承属性和方法的概念。在金融领域,从一个通用的金融工具开始,下一个级别可能是一个通用的衍生金融工具,然后是一个欧式期权,再然后是一个欧式看涨期权。每个类都可以从更高级别的类中继承属性和方法。
聚合
聚合指的是一个对象至少部分由多个其他对象组成,这些对象可能是独立存在的。模拟欧式看涨期权的类可能具有其他对象的属性,例如基础股票和用于贴现的相关短期利率。表示股票和短期利率的对象也可以被其他对象独立使用。
组合
组合与聚合类似,但是这里的单个对象不能独立存在。考虑一个定制的固定利率互换合同和一个浮动利率互换合同。这两个腿不能独立于互换合同本身存在。
多态性
多态性可以呈现多种形式。在 Python 上下文中特别重要的是所谓的鸭子类型。这指的是可以在许多不同类及其实例上实现标准操作,而不需要准确知道正在处理的特定对象是什么。对于金融工具类,这可能意味着可以调用一个名为 get_current_price()
的方法,而不管对象的具体类型是什么(股票、期权、互换等)。
封装
此概念指的是仅通过公共方法访问类内部数据的方法。模拟股票的类可能有一个属性 current_stock_price
。封装将通过方法 get_current_stock_price()
提供对属性值的访问,并将数据隐藏(使其私有化)。这种方法可能通过仅使用和可能更改属性值来避免意外效果。但是,对于如何使数据在 Python 类中私有化存在限制。
在更高层面上,软件工程中的两个主要目标可以总结如下:
可重用性
继承和多态等概念提高了代码的可重用性,增加了程序员的效率和生产力。它们还简化了代码的维护。
非冗余性
与此同时,这些方法允许构建几乎不冗余的代码,避免双重实现工作,减少调试和测试工作以及维护工作量。它还可能导致更小的总体代码基础。
本章按如下方式组织:
“Python 对象概览”
下一节将通过面向对象编程的视角简要介绍一些 Python 对象。
“Python 类基础”
本节介绍了 Python 中面向对象编程的核心要素,并以金融工具和投资组合头寸为主要示例。
“Python 数据模型”
本节讨论了 Python 数据模型的重要元素以及某些特殊方法所起的作用。
Python 对象概览
本节通过面向对象编程程序员的眼光简要介绍了一些标准对象,这些对象在前一节中已经遇到过。
int
为了简单起见,考虑一个整数对象。即使对于这样一个简单的 Python 对象,主要的面向对象编程(OOP)特征也是存在的。
In [7]: n = 5 # ① In [8]: type(n) # ② Out[8]: int In [9]: n.numerator # ③ Out[9]: 5 In [10]: n.bit_length() # ④ Out[10]: 3 In [11]: n + n # ⑤ Out[11]: 10 In [12]: 2 * n # ⑥ Out[12]: 10 In [13]: n.__sizeof__() # ⑦ Out[13]: 28
①
新实例 n
。
②
对象的类型。
③
一个属性。
④
一个方法。
⑤
应用 + 运算符(加法)。
⑥
应用 * 运算符(乘法)。
⑦
调用特殊方法__sizeof__()
以获取内存使用情况(以字节为单位)。¹
列表
list
对象有一些额外的方法,但基本上表现方式相同。
In [14]: l = [1, 2, 3, 4] # ① In [15]: type(l) # ② Out[15]: list In [16]: l[0] # ③ Out[16]: 1 In [17]: l.append(10) # ④ In [18]: l + l # ⑤ Out[18]: [1, 2, 3, 4, 10, 1, 2, 3, 4, 10] In [19]: 2 * l # ⑥ Out[19]: [1, 2, 3, 4, 10, 1, 2, 3, 4, 10] In [20]: sum(l) # ⑦ Out[20]: 20 In [21]: l.__sizeof__() # ⑧ Out[21]: 104
①
新实例l
。
②
对象的类型。
③
通过索引选择元素。
④
一个方法。
⑤
应用+运算符(连接)。
⑥
应用*运算符(连接)。
⑦
应用标准 Python 函数sum()
。
⑧
调用特殊方法__sizeof__()
以获取内存使用情况(以字节为单位)。
ndarray
int
和list
对象是标准的 Python 对象。NumPy
的ndarray
对象是一个来自开源包的“自定义”对象。
In [22]: import numpy as np # ① In [23]: a = np.arange(16).reshape((4, 4)) # ② In [24]: a # ② Out[24]: array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [12, 13, 14, 15]]) In [25]: type(a) # ③ Out[25]: numpy.ndarray
①
导入numpy
。
②
新实例a
。
③
对象的类型。
尽管ndarray
对象不是标准对象,但在许多情况下表现得就像是一个标准对象——这要归功于下文中解释的 Python 数据模型。
In [26]: a.nbytes # ① Out[26]: 128 In [27]: a.sum() # ② Out[27]: 120 In [28]: a.cumsum(axis=0) # ③ Out[28]: array([[ 0, 1, 2, 3], [ 4, 6, 8, 10], [12, 15, 18, 21], [24, 28, 32, 36]]) In [29]: a + a # ④ Out[29]: array([[ 0, 2, 4, 6], [ 8, 10, 12, 14], [16, 18, 20, 22], [24, 26, 28, 30]]) In [30]: 2 * a # ⑤ Out[30]: array([[ 0, 2, 4, 6], [ 8, 10, 12, 14], [16, 18, 20, 22], [24, 26, 28, 30]]) In [31]: sum(a) # ⑥ Out[31]: array([24, 28, 32, 36]) In [32]: np.sum(a) # ⑦ Out[32]: 120 In [33]: a.__sizeof__() # ⑧ Out[33]: 112
①
一个属性。
②
一个方法(聚合)。
③
一个方法(没有聚合)。
④
应用+运算符(加法)。
⑤
应用*运算符(乘法)。
⑥
应用标准 Python 函数sum()
。
⑦
应用NumPy
通用函数np.sum()
。
⑧
调用特殊方法__sizeof__()
以获取内存使用情况(以字节为单位)。
DataFrame
最后,快速查看pandas
的DataFrame
对象,因为其行为大多与ndarray
对象相同。首先,基于ndarray
对象实例化DataFrame
对象。
In [34]: import pandas as pd # ① In [35]: df = pd.DataFrame(a, columns=list('abcd')) # ② In [36]: type(df) # ③ Out[36]: pandas.core.frame.DataFrame
①
导入pandas
。
②
新实例df
。
③
对象的类型。
其次,查看属性、方法和操作。
In [37]: df.columns # ① Out[37]: Index(['a', 'b', 'c', 'd'], dtype='object') In [38]: df.sum() # ② Out[38]: a 24 b 28 c 32 d 36 dtype: int64 In [39]: df.cumsum() # ③ Out[39]: a b c d 0 0 1 2 3 1 4 6 8 10 2 12 15 18 21 3 24 28 32 36 In [40]: df + df # ④ Out[40]: a b c d 0 0 2 4 6 1 8 10 12 14 2 16 18 20 22 3 24 26 28 30 In [41]: 2 * df # ⑤ Out[41]: a b c d 0 0 2 4 6 1 8 10 12 14 2 16 18 20 22 3 24 26 28 30 In [42]: np.sum(df) # ⑥ Out[42]: a 24 b 28 c 32 d 36 dtype: int64 In [43]: df.__sizeof__() # ⑦ Out[43]: 208
①
一个属性。
②
一个方法(聚合)。
③
一个方法(无聚合)。
④
应用+运算符(加法)。
⑤
应用*运算符(乘法)。
⑥
应用NumPy
通用函数np.sum()
。
⑦
调用特殊方法__sizeof__()
以获取以字节为单位的内存使用情况。
Python 金融编程第二版(三)(2)https://developer.aliyun.com/article/1559412