数据类型与结构#

糟糕的程序员关心代码,优秀的程序员关心数据结构及其关系。

—Linus Torvalds

本章介绍Python的基本数据类型(Data Types)与数据结构(Data Structures),组织如下:

"基本数据类型" on page 62

第一部分介绍基本数据类型,如 int、float、bool 和 str。

"基本数据结构" on page 75

第二部分介绍Python的基本数据结构(如列表对象),并展示控制结构、函数式编程方法和匿名函数等内容。

本章旨在对Python在数据类型与结构方面的特性进行通用介绍。具备其他编程语言(如C或Matlab)背景的读者,应当能够轻松掌握Python使用所可能带来的差异。这里介绍的主题和惯用法对后续章节至关重要。

本章涵盖以下数据类型与结构:

对象类型含义用途
int整数值自然数
float浮点数实数
bool布尔值真或假
str字符串对象字符、词语、文本
tuple不可变容器固定对象集合、记录
list可变容器可变对象集合
dict可变容器键值存储
set可变容器唯一对象的集合

基本数据类型#

Python是一种动态类型语言(Dynamically Typed Language),这意味着Python解释器在运行时推断对象的类型。相比之下,C等编译语言通常是静态类型语言(Statically Typed Language),对象的类型必须在编译前指定。1

整数#

最基本的数据类型之一是整数(Integer, int):

In [1]: a = 10
type(a)
Out[1]: int

内置函数 type 提供所有对象的类型信息,包括标准类型、内置类型,以及新创建的类和对象。对于后者,所提供的信息取决于程序员在类中存储的描述。有一种说法是"Python中一切都是对象",这意味着即使是像刚才定义的 int 对象这样的简单对象,也具有内置的方法。例如,可以通过调用 bit_length() 方法获取在内存中表示该 int 对象所需的位数:

In [2]: a.bit_length()
Out[2]: 4

所需的位数随着赋给对象的整数值增大而增加:

In [3]: a = 100000
a.bit_length()
Out[3]: 17

一般来说,方法非常多,以至于很难记住所有类和对象的所有方法。像IPython这样的高级Python环境提供了Tab补全功能,可以显示对象的所有方法。只需输入对象名后跟一个点(如 a.),然后按Tab键,就会显示可以在该对象上调用的方法集合。另外,Python内置函数 dir 可以列出任何对象的所有属性和方法。

Python的一个特性是整数可以任意大。例如,考虑古戈尔数 10100。Python处理如此大的数毫无问题:

In [4]: googol = 10 ** 100
    googol

Out[4]: 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
In [5]: googol.bit_length()
Out[5]: 333

大整数#

Python整数可以任意大。解释器只需使用所需的位数/字节数来表示数字。

整数的算术运算也很容易实现:

In [6]: 1 + 4
Out[6]: 5
In [7]: 1 / 4
Out[7]: 0.25
In [8]: type(1 / 4)
Out[8]: float

浮点数#

最后一个表达式返回数学上正确的结果 0.25,2 这引出了下一个基本数据类型——浮点数(Float)。在整数值后加上点号,如 1. 或 1.0,会使Python将该对象解释为浮点数。包含浮点数的表达式通常也会返回浮点数对象:3

In [9]: 1.6 / 4
Out[9]: 0.4
In [10]: type (1.6 / 4)
Out[10]: float

浮点数稍微复杂一些,因为有理数或实数的计算机表示通常不是精确的,这取决于具体的技术实现。为了说明这意味着什么,让我们定义另一个浮点数对象 b。像这样的浮点数对象在内部总是只能达到一定程度的精度。当将 0.1 加到 b 上时,这一点就变得很明显:

In [11]: b = 0.35
type(b)
Out[11]: float
In [12]: b + 0.1
Out[12]: 0.44999999999999996

原因在于浮点数对象内部以二进制格式表示;也就是说,十进制数 0 < n < 1 由形如 $\begin{array} {r} {n = \frac{x} {2} + \frac{y} {4} + \frac{z} {8} + \dots} \end{array}$ 的级数表示。对于某些浮点数,二进制表示可能包含大量元素,甚至可能是无穷级数。然而,由于用于表示这类数字的位数固定(即表示级数中的项数固定),精度误差便随之产生。其他数字可以完美表示,即使在可用位数有限的情况下也能精确存储。考虑以下示例:

In [13]: c = 0.5
    c.as_integer_ratio()
Out[13]: (1, 2)

二分之一,即 0.5,被精确存储,因为它有精确的(有限)二进制表示 0.5 = 1 。但对于 b = 0.35,得到的结果与预期的有理数 $\begin{array} {r} {0 . 35 = \frac{7} {20}} \end{array}$ 不同:

In [14]: b.as_integer_ratio()
Out[14]: (3152519739159347, 9007199254740992)

精度取决于用于表示数字的位数。一般来说,Python运行的所有平台都使用IEEE 754双精度标准——即64位——进行内部表示。这转化为15位相对精度。

由于这一主题在金融的多个应用领域中非常重要,有时需要确保数字的精确或至少尽可能最佳的表示。例如,在对大量数字求和时,该问题就很重要。在这种情况下,某种类型和/或程度的表示误差可能在累加后导致与基准值的显著偏差。

decimal 模块为浮点数提供了任意精度对象(Arbitrary-Precision Object),以及处理精度问题的多个选项:

In [15]: import decimal from decimal import Decimal
In [16]: decimal.getcontext()
Out[16]: Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
In [17]: d = Decimal(1) / Decimal (11)
    d
Out[17]: Decimal('0.0909090909090909090909090909091')

可以通过更改 Context 对象的相应属性值来更改表示的精度:

In [18]: decimal.getcontext().prec = 4
In [20]: decimal.getcontext().prec = 50
In [21]: f = Decimal(1) / Decimal (11)
f
Out[21]: Decimal('0.090909090909090909090909090909090909090909090909091')

低于默认精度的精度。

高于默认精度的精度。

如果需要,可以这样将精度调整为手头的具体问题,并可以操作具有不同精度的浮点数对象:

In [22]: g = d + e + f
g
Out[22]: Decimal('0.2727281818181818181818181819090909090909090909')

任意精度浮点数#

decimal 模块提供任意精度浮点数对象。在金融领域,有时可能需要确保高精度,超越64位双精度标准。

布尔值#

在编程中,计算比较或逻辑表达式(如 4 > 3、4.5 <= 3.25 或 (4 > 3) and (3 > 2))会输出 True 或 False,这是两个重要的Python关键字。其他关键字例如 def、for 和 if。完整的Python关键字列表可以在 keyword 模块中找到:

In [23]: import keyword

In [24]: keyword.kwlist
Out[24]: ['False',
    'None',
    'True',
    'and',
    'as',
    'assert',
    'async',
    'await',
    'break',
    'class',
    'continue',
    'def',
    'del',
    'elif',
    'else',
    'except',
    'finally',
    'for',
    'from',
    'global',
    'if',
    'import',
    'in',
    'is',
    'lambda',
    'nonlocal',
    'not',
    'or',
    'pass',
    'raise',
    'return',
    'try',
    'while',
    'with',
    'yield']

True 和 False 属于 bool 数据类型,代表布尔值(Boolean Value)。以下代码展示了Python的比较运算符应用于相同操作数时生成的 bool 对象:

In [25]: 4 > 3
Out[25]: True
In [26]: type(4 > 3)
Out[26]: bool
In [27]: type(False)
Out[27]: bool
In [28]: 4 >= 3
Out[28]: True
In [29]: 4 < 3
Out[29]: False
In [30]: 4 <= 3
Out[30]: False
In [31]: 4 == 3
Out[31]: False
In [32]: 4 != 3
Out[32]: True

大于。

大于或等于。

小于。

小于或等于。

等于。

不等于。

通常,逻辑运算符应用于 bool 对象,进而生成另一个 bool 对象:

In [33]: True and True
Out[33]: True
In [34]: True and False
Out[34]: False
In [35]: False and False
Out[35]: False
In [36]: True or True
Out[36]: True
In [37]: True or False
Out[37]: True

In [38]: False or False
Out[38]: False

In [39]: not True
Out[39]: False

In [40]: not False
Out[40]: True

当然,两种类型的运算符经常组合使用:

In [41]: (4 > 3) and (2 > 3)
Out[41]: False
In [42]: (4 == 3) or (2 != 3)
Out[42]: True
In [43]: not (4 != 4)
Out[43]: True
In [44]: (not (4 != 4)) and (2 == 3)
Out[44]: False

一个主要应用领域是通过其他Python关键字(如 if 或 while)来控制代码流程(本章后面有更多示例):

In [45]: if 4 > 3: ①
    print('condition true') ②
    condition true

In [46]: i = 0 ③
    while i < 4: ④
    print('condition true, i = ', i) ⑤
    i += 1 ⑥
    condition true, i = 0
    condition true, i = 1
    condition true, i = 2
    condition true, i = 3

如果条件成立,执行后续代码。

条件成立时要执行的代码。

将参数 i 初始化为 0。

只要条件成立,就执行并重复后续代码。

打印文本和参数 i 的值。

将参数值增加 1;i += 1 等同于 i = i + 1。

数值上,Python将 False 视为 0,将 True 视为 1。通过 bool() 函数将数字转换为 bool 对象时,0 得到 False,而所有其他数字得到 True:

In [47]: int(True)
Out[47]: 1

In [48]: int(False)
Out[48]: 0

In [49]: float(True)
Out[49]: 1.0

In [50]: float(False)
Out[50]: 0.0

In [51]: bool(0)
Out[51]: False

In [52]: bool(0.0)
Out[52]: False

In [53]: bool(1)
Out[53]: True

In [54]: bool(10.5)
Out[54]: True

In [55]: bool(-2)
Out[55]: True

字符串#

现在自然数和浮点数都可以表示,接下来我们讨论文本。Python中表示文本的基本数据类型是 str。str 对象有许多有用的内置方法。事实上,Python通常被认为是处理各种类型和规模文本及文本文件的不错选择。str 对象通常由单引号或双引号定义,或通过 str() 函数转换其他对象(即使用对象的标准或用户定义的 str 表示)来定义:

In [56]: t = 'this is a string object'

关于内置方法,例如,可以将该对象中的第一个单词大写:

In [57]: t.capitalize()
Out[57]: 'This is a string object'

或者可以将其拆分为单个单词,得到一个包含所有单词的列表对象(稍后会详细介绍列表对象):

In [58]: t.split()
Out[58]: ['this', 'is', 'a', 'string', 'object']

还可以搜索一个单词,如果找到,则返回该单词第一个字母的位置(即索引值):

In [59]: t.find('string')
Out[59]: 10

如果该单词不在 str 对象中,该方法返回 -1:

In [60]: t.find('Python')
Out[60]: -1

替换字符串中的字符是典型任务,可以使用 replace() 方法轻松完成:

In [61]: t.replace(' ', ' |')
Out[61]: 'this|is|a|string|object'

字符串的剥离(Strip)——即删除某些前导/尾随字符——也经常需要:

In [62]: 'http://www.python.org'.strip('http:/')
Out[62]: 'www.python.org'

表3-1列出了 str 对象的一些有用方法。

表3-1. 字符串常用方法

方法参数返回/结果
capitalize()首字母大写的字符串副本
count(sub[, start[, end]])子串出现次数
encode([encoding[, errors]])字符串的编码版本
find(sub[, start[, end]])找到子串的(最低)索引
join(seq)将序列 seq 中的字符串连接
replace(old, new[, count])将 old 替换为 new,前 count 次
split([sep[, maxsplit]])以 sep 为分隔符的单词列表
splitlines([keepends])分离的行;如果 keepends 为 True,则保留行结束符
strip(chars)移除 chars 中的前导/尾随字符后的副本
upper()所有字母大写的副本

Unicode字符串#

从 Python 2.7(本书第一版使用)到 Python 3.7(本书第二版使用)的一个根本性变化是字符串对象的编码和解码以及Unicode的引入。本章不深入讨论此领域的诸多细节;对于本书的目的(主要处理数值数据和包含英语单词的标准字符串),这种省略似乎是合理的。

专题:打印与字符串替换#

打印 str 对象或其他Python对象的字符串表示通常使用 print() 函数完成:

In [63]: print('Python for Finance') ①
    Python for Finance

In [64]: print(t) ②
    this is a string object

In [65]: i = 0
    while i < 4:
    print(i) ③
    i += 10123

In [66]: i = 0
    while i < 4:
    print(i, end='|') ④
    i += 10|1|2|3|

打印一个 str 对象。

打印由变量名引用的 str 对象。

打印 int 对象的字符串表示。

指定打印时的结束字符;默认为换行符(\n),如之前所见。

Python提供了强大的字符串替换操作。有旧的方式(通过 % 字符)和新的方式(通过花括号 {} 和 format())。两者在实际中仍在使用。本节无法详尽说明所有选项,但以下代码片段展示了一些重要的用法。首先,旧的方式:

In [67]: 'this is an integer %d' % 15
Out[67]: 'this is an integer 15'

In [68]: 'this is an integer %4d' % 15
Out[68]: 'this is an integer   15'

In [69]: 'this is an integer %04d' % 15
Out[69]: 'this is an integer 0015'

In [70]: 'this is a float %f' % 15.3456
Out[70]: 'this is a float 15.345600'

In [71]: 'this is a float %.2f' % 15.3456
Out[71]: 'this is a float 15.35'

In [72]: 'this is a float %8f' % 15.3456
Out[72]: 'this is a float 15.345600'

In [73]: 'this is a float %8.2f' % 15.3456
Out[73]: 'this is a float    15.35'

In [74]: 'this is a float %08.2f' % 15.3456
Out[74]: 'this is a float 00015.35'

In [75]: 'this is a string %s' % 'Python'
Out[75]: 'this is a string Python'

In [76]: 'this is a string %10s' % 'Python'
Out[76]: 'this is a string     Python'

int 对象替换。

固定字符数。

必要时前导补零。

float 对象替换。

固定小数位数。

固定字符数(并补足小数位)。

固定字符数和小数位数……

……以及必要时前导补零。

str 对象替换。

固定字符数。

现在是用新方式实现的相同示例。注意某些地方输出的细微差别:

In [77]: 'this is an integer {:d}'.format(15)
Out[77]: 'this is an integer 15'

In [78]: 'this is an integer {:4d}'.format(15)
Out[78]: 'this is an integer   15'

In [79]: 'this is an integer {:04d}'.format(15)
Out[79]: 'this is an integer 0015'

In [80]: 'this is a float {:f}'.format(15.3456)
Out[80]: 'this is a float 15.345600'

In [81]: 'this is a float {:.2f}'.format(15.3456)
Out[81]: 'this is a float 15.35'

In [82]: 'this is a float {:8f}'.format(15.3456)
Out[82]: 'this is a float 15.345600'

In [83]: 'this is a float {:8.2f}'.format(15.3456)
Out[83]: 'this is a float    15.35'

In [84]: 'this is a float {:08.2f}'.format(15.3456)
Out[84]: 'this is a float 00015.35'

In [85]: 'this is a string {:s}'.format('Python')
Out[85]: 'this is a string Python'

In [86]: 'this is a string {:10s}'.format('Python')
Out[86]: 'this is a string Python'

在多个打印操作中,当打印的数据被更新时(例如在 while 循环中),字符串替换特别有用:

In [87]: i = 0
    while i < 4:
    print('the number is %d' % i)
    i += 1
    the number is 0
    the number is 1
    the number is 2
    the number is 3

In [88]: i = 0
    while i < 4:
    print('the number is {:d}'.format(i))
    i += 1
    the number is 0
    the number is 1
    the number is 2
    the number is 3

专题:正则表达式#

处理 str 对象时一个强大的工具是正则表达式(Regular Expressions)。Python在 re 模块中提供了这样的功能:

In [89]: import re

假设一位金融分析师面对一个大型文本文件,例如CSV文件,其中包含某些时间序列和相应的日期时间信息。通常,这些信息以Python无法直接解释的格式提供。然而,日期时间信息通常可以用正则表达式描述。考虑以下 str 对象,包含三个日期时间元素、三个整数和三个字符串。注意,三重引号允许跨多行定义 str 对象:

In [90]: series = ""
    '01/18/201413:00:00', 100, '1st';
    '01/18/201413:30:00', 110, '2nd';
    '01/18/201414:00:00', 120, '3rd'
    """

以下正则表达式描述了 str 对象中日期时间信息的格式:4

In [91]: dt = re.compile("'[0-9/:\s]+") # 日期时间

有了这个正则表达式,就可以继续查找所有日期时间元素。一般来说,将正则表达式应用于 str 对象还可以提高典型解析任务的性能:

In [92]: result = dt.findall(series)
result
Out[92]: ["01/18/201413:00:00'", "01/18/201413:30:00", "01/18/201414:00:00"]

正则表达式#

在解析 str 对象时,考虑使用正则表达式,这可以带来便利性和性能上的提升。

得到的 str 对象随后可以被解析以生成Python的 datetime 对象(参见附录A了解使用Python处理日期和时间数据的概述)。要解析包含日期时间信息的 str 对象,需要提供如何解析的信息——同样以 str 对象的形式:

In [93]: from datetime import datetime
pydt = datetime.strptime(result[0].replace(" '', ""),
    '%m/%d/%Y %H:%M:%S')
pydt
Out[93]: datetime.datetime(2014, 1, 18, 13, 0)
In [94]: print(pydt)
2014-01-1813:00:00
In [95]: print(type(pydt))
    <class 'datetime.datetime'>

后续章节将提供关于日期时间数据、此类数据的处理以及 datetime 对象及其方法的更多信息。这里仅作为金融领域这一重要主题的引子。

基本数据结构#

一般来说,数据结构是包含可能大量其他对象的对象。Python作为内置结构提供的有:

元组(Tuple)

一个不可变的任意对象集合;只有少数方法可用

列表(List)

一个可变的任意对象集合;有许多方法可用

字典(Dict)

一个键值存储对象

集合(Set)

一个用于其他唯一对象的无序集合对象

元组#

元组是一种高级数据结构,但仍然相当简单且应用有限。它通过在括号中提供对象来定义:

In [96]: t = (1, 2.5, 'data')
type(t)
Out[96]: tuple

甚至可以省略括号,只提供多个由逗号分隔的对象:

In [97]: t = 1, 2.5, 'data'
type(t)
Out[97]: tuple

与Python中几乎所有数据结构一样,元组具有内置索引,可以借助索引检索元组中的单个或多个元素。重要的是要记住,Python使用从零开始的编号(Zero-Based Numbering),因此元组的第三个元素位于索引位置2:

In [98]: t[2]
Out[98]: 'data'
In [99]: type(t[2])
Out[99]: str

从零开始的编号#

与Matlab等其他编程语言不同,Python使用从零开始的编号方案。例如,元组对象的第一个元素的索引值为0。

该对象类型只提供两个特殊方法:count() 和 index()。第一个计算某个对象出现的次数,第二个给出它首次出现时的索引值:

In [100]: t.count('data')
Out[100]: 1
In [101]: t.index(1)
Out[101]: 0

元组对象是不可变对象(Immutable Objects)。这意味着一旦定义,就不能轻易更改。

列表#

list 类型的对象与元组对象相比更加灵活和强大。从金融的角度来看,仅使用列表对象就可以实现很多功能,例如存储股票价格报价和追加新数据。列表对象通过方括号定义,其基本能力和行为与元组对象类似:

In [102]: l = [1, 2.5, 'data']
    l[2]
Out[102]: 'data'

列表对象也可以通过使用 list() 函数来定义或转换。以下代码通过转换前一个示例中的元组对象来生成一个新的列表对象:

In [103]: l = list(t)
    l
Out[103]: [1, 2.5, 'data']
In [104]: type(l)
Out[104]: list

除了元组对象的特性之外,列表对象还可以通过各种方法进行扩展和缩减。换句话说,str 和 tuple 对象是不可变的序列对象(具有索引),一旦创建就不能更改,而列表对象是可变的(Mutable),可以通过不同的操作进行更改。可以将列表对象追加到现有列表对象,以及更多操作:

In [105]: l.append([4, 3]) ①
    l
Out[105]: [1, 2.5, 'data', [4, 3]]

In [106]: l.extend([1.0, 1.5, 2.0]) ②
    l
Out[106]: [1, 2.5, 'data', [4, 3], 1.0, 1.5, 2.0]

In [107]: l.insert(1, 'insert') ③
    l
Out[107]: [1, 'insert', 2.5, 'data', [4, 3], 1.0, 1.5, 2.0]

In [108]: l.remove('data') ④
    l
Out[108]: [1, 'insert', 2.5, [4, 3], 1.0, 1.5, 2.0]

In [109]: p = l.pop(3) ⑤
    print(l, p)
    [1, 'insert', 2.5, 1.0, 1.5, 2.0] [4, 3]

在末尾追加列表对象。

追加列表对象的元素。

在索引位置之前插入对象。

移除对象的首次出现。

移除并返回索引位置处的对象。

切片(Slicing)也很容易实现。这里,切片是指将数据集分解为(感兴趣的)较小部分的操作:

In [110]: l[2:5] ①
Out[110]: [2.5, 1.0, 1.5]

返回第三到第五个元素。

表3-2总结了列表对象的一些常用操作和方法。

表3-2. 列表对象的常用操作和方法

方法参数返回/结果
l[i] = x[i]将第 i 个元素替换为 x
l[i:j:k] = s[i:j:k]将从 i 到 j-1 的每隔 k 个元素替换为 s
append(x)将 x 追加到对象
count(x)对象 x 出现的次数
del l[i:j:k][i:j:k]删除索引值 i 到 j-1、步长为 k 的元素
extend(s)将 s 的所有元素追加到对象
index(x[, i[, j]])x 在元素 i 到 j-1 之间的第一个索引
insert(i, x)在索引 i 处/之前插入 x
remove(x)移除首次匹配的元素 x
pop(i)移除索引 i 处的元素并返回它
reverse()原地反转所有元素
sort([cmp[, key[, reverse]]])原地排序所有元素

专题:控制结构#

虽然控制结构(Control Structures)本身是一个大话题,但像 for 循环这样的控制结构也许最好基于列表对象在Python中介绍。这是因为循环通常遍历列表对象,这与其他语言中的标准做法有很大不同。看一下下面的例子。for 循环遍历列表对象 l 中索引值从2到4的元素,并打印相应元素的平方。注意第二行中缩进(空白)的重要性:

In [111]: for element in l[2:5]:
    print(element ** 2)
6.251.02.25

与典型的基于计数器的循环相比,这提供了非常高的灵活性。基于计数器的循环在Python中也是一个选项,但通过 range 对象实现:

In [112]: r = range(0, 8, 1)
    r
Out[112]: range(0, 8)

In [113]: type(r)
Out[113]: range

参数是起始值、结束值和步长。

为了比较,使用 range() 实现相同的循环如下:

In [114]: for i in range(2, 5):
    print(l[i] ** 2)
6.251.02.25

遍历列表#

在Python中,可以遍历任意列表对象,无论对象的内容是什么。这通常避免了引入计数器。

Python还提供了典型的(条件)控制元素 if、elif 和 else。它们的用法与其他语言类似:

In [115]: for i in range(1, 10):
    if i % 2 == 0:
    print("%d is even" % i)
    elif i % 3 == 0:
    print("%d is multiple of 3" % i)
    else:
    print("%d is odd" % i)
    1 is odd
    2 is even
    3 is multiple of 34 is even
    5 is odd
    6 is even
    7 is odd
    8 is even
    9 is multiple of 3

% 表示取模运算。#

类似地,while 提供了另一种控制流程的方式:

In [116]: total = 0
    while total < 100:
    total += 1
    print(total)
    100

Python的一个特性是列表推导式(List Comprehensions)。这种方法不是遍历现有的列表对象,而是通过一种相当紧凑的方式使用循环生成列表对象:

In [117]: m = [i ** 2 for i in range(5)]
    m
Out[117]: [0, 1, 4, 9, 16]

在某种意义上,这已经提供了第一种生成"类似于"向量化代码的方式,因为循环是隐式的而不是显式的(向量化将在第4章和第5章中更详细地讨论)。

专题:函数式编程#

Python也提供了许多函数式编程(Functional Programming)支持的工具——即将函数应用于一组完整的输入(在我们的例子中是列表对象)。这些工具包括 filter()、map() 和 reduce()。然而,首先需要定义一个函数。从一个非常简单的开始,考虑一个函数 f(),它返回输入 x 的平方:

In [118]: def f(x):
    return x ** 2
    f(2)
Out[118]: 4

当然,函数可以任意复杂,具有多个输入/参数对象,甚至多个输出(返回对象)。然而,考虑以下函数:

In [119]: def even(x):
    return x % 2 == 0
    even(3)
Out[119]: False

返回对象是一个布尔值。这样的函数可以通过使用 map() 应用于整个列表对象:

In [120]: list(map(even, range(10)))
Out[120]: [True, False, True, False, True, False, True, False, True, False]

为此,也可以直接将函数定义作为参数传递给 map(),利用 lambda 或匿名函数(Anonymous Functions):

In [121]: list(map(lambda x: x ** 2, range(10)))
Out[121]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

函数也可以用来过滤列表对象。在以下示例中,filter 返回列表对象中符合 even 函数定义的布尔条件的元素:

In [122]: list(filter(even, range(15)))
Out[122]: [0, 2, 4, 6, 8, 10, 12, 14]

列表推导式、函数式编程、匿名函数#

在Python层面上尽可能避免循环被认为是一种良好实践。列表推导式和函数式编程工具(如 filter()、map() 和 reduce())提供了编写无(显式)循环代码的方式,既紧凑又通常更易阅读。lambda 或匿名函数在此情境下也是强大的工具。

字典#

dict 对象是字典(Dictionary),也是可变序列,允许通过键(例如可以是 str 对象)检索数据。它们被称为键值存储(Key-Value Store)。列表对象是有序且可排序的,而 dict 对象通常是无序且不可排序的。5 一个示例最能说明与列表对象的进一步差异。花括号定义 dict 对象:

In [123]: d = {'Name' : 'Angela Merkel',
    'Country' : 'Germany',
    'Profession' : 'Chancelor',
    'Age' : 64}
type(d)
Out[123]: dict
In [124]: print(d['Name'], d['Age'])
Angela Merkel 64

同样,这类对象有许多内置方法:

In [125]: d.keys()
Out[125]: dict_keys(['Name', 'Country', 'Profession', 'Age'])

In [126]: d.values()
Out[126]: dict_values(['Angela Merkel', 'Germany', 'Chancelor', 64])

In [127]: d.items()
Out[127]: dict_items(['Name', 'Angela Merkel'), ('Country', 'Germany'), ('Profession', 'Chancelor'), ('Age', 64],])

In [128]: birthday = True
    if birthday:
    d['Age'] += 1
    print(d['Age'])
    65

有几种方法可以从 dict 对象获取迭代器对象。迭代器对象在被迭代时行为类似列表对象:

In [129]: for item in d.items():
    print(item)
    ('Name', 'Angela Merkel')
    ('Country', 'Germany')
    ('Profession', 'Chancelor')
    ('Age', 65)

In [130]: for value in d.values():
    print(type(value))
    <class 'str'>
    <class 'str'>
    <class 'str'>
    <class 'int'>

表3-3提供了 dict 对象的一些常用操作和方法。

表3-3. dict 对象的常用操作和方法

Method Arguments Returns/result
d[k] [k] Item of d with key k
d[k] = x [k] Sets item key k to x
del d[k] [k] Deletes item with key k
clear () Removes all items
copy () Makes a copy
items () Iterator over all items
keys () Iterator over all keys
values () Iterator over all values
popitem (k) Returns and removes item with key k
update ([e]) Updates items with items from e

集合#

本节介绍的最后一个数据结构是集合(Set)对象。虽然集合论是数学的基石,也是金融理论的基石,但集合对象在实际应用中的机会并不多。这些对象是其他对象的无序集合,每个元素只出现一次:

In [131]: s = set(['u', 'd', 'ud', 'du', 'd', 'du'])
s
Out[131]: {'d', 'du', 'u', 'ud'}
In [132]: t = set(['d', 'dd', 'uu', 'u'])

使用集合对象,可以实现数学集合论中的基本集合运算。例如,可以生成并集、交集和差集:

In [133]: s.union(t) ①
Out[133]: {'d', 'dd', 'du', 'u', 'ud', 'uu'}
In [134]: s.intersection(t) ②
Out[134]: {'d', 'u'}
In [135]: s.difference(t) ③
Out[135]: {'du', 'ud'}
In [136]: t.difference(s) ④
Out[136]: {'dd', 'uu'}
In [137]: s.symmetric_difference(t) ⑤
Out[137]: {'dd', 'du', 'ud', 'uu'}

s 和 t 的所有元素。

s 和 t 中共有的元素。

s 中有但 t 中没有的元素。

t 中有但 s 中没有的元素。

s 或 t 中任意一个所有但不同时拥有的元素。

集合对象的一个应用是去除列表对象中的重复项:

In [138]: from random import randint
    l = [randint(0, 10) for i in range(1000)]
    len(l)
Out[138]: 1000

In [139]: l[:20]
Out[139]: [4, 2, 10, 2, 1, 10, 0, 6, 0, 8, 10, 9, 2, 4, 7, 8, 10, 8, 8, 2]

In [140]: s = set(l)
    s
Out[140]: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

生成0到10之间的1000个随机整数。

l 中的元素数量。

结语#

基本的Python解释器提供了丰富且灵活的数据结构。从金融角度来看,以下是最重要的:

基本数据类型#

在Python以及金融领域中,int、float、bool 和 str 这些类提供了原子数据类型。

标准数据结构#

tuple、list、dict 和 set 类在金融中有许多应用领域,其中 list 是一个灵活的万能工具,适用于许多用例。

延伸资源#

关于数据类型和结构,本章重点介绍了那些可能对金融算法和应用特别重要的主题。然而,它只是探索Python数据结构和数据建模的起点。

有许多有价值的资源可以从此处深入学习。Python数据结构的官方文档位于 https://docs.python.org/3/tutorial/datastructures.html。

推荐的书籍参考资料包括:

• Goodrich, Michael, et al. (2013). Data Structures and Algorithms in Python. Hoboken, NJ: John Wiley & Sons.

• Harrison, Matt (2017). Illustrated Guide to Python 3. CreateSpace Treading on Python Series.

• Ramalho, Luciano (2016). Fluent Python. Sebastopol, CA: O'Reilly.

有关正则表达式的介绍,请参见:

• Fitzgerald, Michael (2012). Introducing Regular Expressions. Sebastopol, CA: O'Reilly.