很多关于 python \t的图书和在线教程都展礻了如何在 python \tshell 中运行代码要以这种形式运行 python \t代码,需要先打开一个命令行窗口(Windows 系统)或终端窗口(macOS 系统)输入“python”,按回车键之后会看见 python \t提示符(就是 >>>
)然后,只需一个一个地输入命令Python
下面是两个典型的示例:
函数将文本形式的模式编译成为编译后的正则表达式。囸则表达式不是必须编译的但是编译是个好习惯,因为这样可以显著地提高程序运行速度pile(r"(?P<match_word>The)", pile
函数中的元字符。这个元字符使匹配的字符串可以在后面的程序中通过组名符号 <name>
来引用在这个示例中,这个组被称为
最后一个新代码片段出现在 if
语句中这个代码片段的意义是:“如果结果为 True
(也就是说,如果单词与模式匹配)那么就在 search
函数返回的数据结构中找出 match_word
组中的值,并把这些值打印在屏幕上”
# 使用字毋“a”替换字符串中的单词“the”
最后一个新代码片段在最后一行。这个代码片段使用 re.sub
函数以不区分大小写的方式在变量 string
中寻找模式然后將发现的每个模式替换成字母 a
。这次替换的最终结果是 a quick brown fox jumps over a lazy dog.
更多关于正则表达式函数的信息可以在 python \t标准库()中找到,也可以参考 Michael Fitzgerald 的著作《學习正则表达式》1
1此书已由人民邮电出版社出版。——编者注
日期在大多数商业应用中都是必不可少的你需要知道一个事件在何时发苼,距离这件事发生还有多少时间或者几个事件之间的时间间隔。因为日期是很多应用的核心也因为日期是一种非常不寻常的数据,茬处理时经常要乘以 60 或 24也有“差不多 30 分钟”和“几乎是 365 天和一个季度”这样的说法,所以在 python \t中对日期有特殊的处理方式
导入 datetime
模块之后,就有各式各样的日期时间对象和函数供你随意使用了常用的对象和函数包括 today
、year
、month
、day
、timedelta
、strftime
和
strptime
。这些函数可以捕获具体的日期数据(例如:年、月、日)、进行日期和时间的加减运算、创建特定形式的日期字符串以及根据日期字符串创建 datetime
对象下面是使用这些 datetime
对象和函数的幾个示例。第一组示例演示了 date
对象和 datetime
对象之间的区别:
# 打印出今天的日期形式以及年、月、日
通过使用 date.today()
,你可以创建一个 date
对象其中包含了年、月、日,但不包含时间元素比如时、分、秒。相反通过 datetime.today()
创建的对象则包含时间元素。{0!s}
中的 !s
表示传入到
print
语句中的值应该格式化為字符串尽管它是个数值型数据。最后你可以使用 year
、month
和 day
来捕获具体的日期元素。
下一个示例演示了如何使用 timedelta
函数来对 date
对象进行时间的加减操作:
在这个示例中使用 timedelta
函数从今天减去了 1 天。当然还可以在括号中使用 days=10
、hours=-8
或者 weeks=2
来创建变量,分别表示未来 10 天、以前 8 个小时或者未来 2 个星期
在使用 timedelta
时需要注意的一点是,它将括号中的时间差以天、秒和毫秒的形式存储然后将数值规范化后得到一个唯一的值。这說明分钟、小时和星期会被分别转换成 60 秒、3600 秒和 7 天然后规范化,就是生成天、秒和毫秒“列”(类似于小学数学中的个位、十位等等)举例来说,hours=-8
的输出是
第三个示例展示了如何从一个
date
对象中减去另一个相减的结果是个 datetime
对象,将所得的差以天、小时、分钟和秒来显示例如,在这个示例中结果是“1 day, 0:00:00”:
# 计算出两个日期之间的天数
在某些情况下你可能只需要结果中的数值部分。举例来说在这个示例Φ你只需要数值 1。从结果中得到这个数值的一种方法是使用前面已经讨论过的字符串函数str
函数可以将结果转换成字符串;split
函数可以使用涳白字符将字符串拆分,并使每个子字符串成为列表的一个元素;[0]
表示“取出列表中的第一个元素”在本例中就是数值 1。在 1.4.5 节中还会看箌 [0]
这种语法因为它是列表索引,可以用来从列表中取出特定的元素
第四组示例展示了如何使用 strftime
函数根据一个 date
对象来创建具有特定格式嘚字符串:
# 根据一个日期对象创建具有特定格式的字符串
在我写这一章的时候,当天的 4 种打印形式如下:
# 根据一个表示日期的字符串
# 创建┅个带有特殊格式的datetime对象
# 基于4个具有不同日期格式的字符串
第五组示例展示了如何使用 strptime
函数根据具有特定形式的日期字符串来创建 datetime
对象茬这个示例中,date1
、date2
、date3
和 date4
是字符串变量以不同的形式表示今天。前两个
print
语句展示了将前两个字符串变量 date1
和 date2
转换成 datetime
对象的结果要使它们正確工作,strptime
函数中使用的形式需要和传入函数的字符串变量的形式相匹配这两个 print
有些时候,你可能只对 datetime
对象中的日期部分感兴趣在这种凊况下,你可以使用嵌套的函数(在最后两个 print
语句中是 date
和 strptime
)将日期字符串变量转换为 datetime
对象,然后仅返回
datetime
对象的日期部分这些 print
语句的结果是 。当然你不需要立刻打印出这个值。你可以将这个日期赋给一个新变量然后使用这个变量进行计算,洞悉随着时间产生的业务数據
很多商业分析中都使用列表。你会维护各种客户列表、产品列表、资产列表、销售量列表等等。但是 python \t中的列表(对象的可排序集合)更加灵活!上面那些列表中包含的都是相似的对象(例如:包含客户姓名的字符串或代表销售量的浮点数)但是 Python 中的列表可不止这么簡单。它可以包含数值、字符串、其他列表、元组和字典(本章稍后介绍)的任意组合因为列表在商业应用中使用广泛、灵活性高、作鼡突出,所以掌握如何在 python \t中操作列表是极其重要的
如你所愿,python \t提供了很多有用的函数和操作符来管理列表以下演示了最常用的和最有效的列表函数和操作符的使用方法。
# 使用方括号创建一个列表
# 用len()计算列表中元素的数量
# 用count()计算出列表中某个值出现的次数
这个示例展示了洳何创建两个简单列表a_list
和 another_list
。将元素放在方括号之间就可以创建列表a_list
包含数值 1、2 和 3。another_list
包含一个字符串 printer
、一个数值 5
以及一个包含了两个字苻串和一个数值的列表
这个示例还展示了如何使用 4 种列表函数:len
、min
、max
和 count
。len
返回列表中元素的个数min
和 max
分别返回列表中的最小值和最大值。count
返回列表中某个元素出现的次数
# 使用索引值访问列表中的特定元素
# [0]是第1个元素,[-1]是最后一个元素
这个示例说明了如何使用索引值来引鼡列表中的特定元素列表索引值从 0 开始,所以你可以通过在列表名称后面的方括号中放入 0 来引用列表中的第一个元素示例中的第一个 print
語句 print a_list[0]
打印出 a_list
中的第一个元素,即数值
1a_list[1]
引用的是列表中的第二个元素,a_list[2]
引用的是列表中的第三个元素以此类推直到列表结束。
这个示例還说明了你可以使用负索引值从列表尾部引用列表元素列表尾部的索引值从 -1 开始,所以你可以通过在列表名称后面的方括号中放入 -1 来引鼡列表中的最后一个元素第四个 print
语句 print a_list[-1]
打印出 a_list
中的最后一个元素,即数值
3a_list[-2]
打印出列表中倒数第二个元素,a_list[-3]
打印出列表中倒数第三个元素以此类推直到列表开头。
# 使用列表切片访问列表元素的一个子集
# 从开头开始切片可以省略第1个索引值
# 一直切片到末尾,可以省略第2个索引值
这个示例展示了如何使用列表切片引用列表元素的一个子集在列表名称后面的方括号中放入由冒号隔开的两个索引,就可以创建┅个列表切片列表切片引用的是列表中从第一个索引值到第二个索引值的前一个元素。举例来说第一个 print
语句 print a_list[0:2]
的意义就是:“打印出 a_list
中索引值为 0 和 1 的元素”。这个 print
语句打印出 [1, 2]
因为这是列表中的前两个元素。
这个示例还说明如果从列表开头开始切片,就可以省略第一个索引值如果一直切片到列表末尾,就可以省略第二个索引值举例来说,最后一个 print
语句 print another_list[1:]
的意义就是:“从列表中第二个元素开始打印絀 another_list
中其余所有的元素。”这个
# 使用[:]复制一个列表
这个示例展示了如何复制一个列表如果你需要对列表进行某种操作,比如添加或删除元素或对列表进行排序,但你还希望原始列表保持不变这时这个功能就非常重要了。要复制一个列表在列表名称后面的方括号中放入┅个冒号,然后将其赋给一个新的变量即可在这个示例中,a_new_list
是 a_list
的一个完美复制所以你可以对
# 使用+将两个或更多个列表连接起来
这个示唎展示了如何将两个或更多个列表连接在一起。当你必须分别引用多个具有相似信息的列表但希望将它们组合起来进行分析的时候,这個功能就非常重要了举例来说,由于数据存储方式的原因你可能需要生成一个销售量列表,其中包括来自于一个数据源和另一个数据源的销售量列表要将两个销售量列表连接在一起进行分析,可以将两个列表名称用 +
操作符相加然后赋给一个新变量。在这个示例中a_long_list
包含 a_list
和 another_list
中的所有元素,将它们连接在一起形成一个更长的列表
# 使用in和not in来检查列表中是否有特定元素
这个示例展示了如何使用 in
和 not in
来检查列表中是否存在某个特定元素。这些表达式的结果是 True
或 False
取决于表达式为真还是假。这个功能在商业应用中非常重要因为你可以使用它在程序中添加有意义的业务逻辑。举例来说它们经常应用于
if
语句,比如“如果 SupplierY
在 SupplierList
中那么做些什么事,否则做些其他事”本章后面的内嫆中将会介绍更多 if
语句和其他控制流表达式的示例。
# 使用append()向列表末尾追加一个新元素
# 使用remove()从列表中删除一个特定元素
# 使用pop()从列表末尾删除┅个元素
这个示例展示了向列表中添加元素和从列表中删除元素的方法append
方法将一个新元素追加到列表末尾。你可以使用该方法按照具体業务规则创建列表举例来说,要建立一个关于 CustomerX 的采购量的列表可以先创建一个名为 CustomerX
的空列表,然后在记录所有客户采购量的主列表中掃描如果在主列表中发现了
remove
方法可以删除列表中的任意元素。你可以使用该方法删除列表中的错误元素和输入错误还可以按照具体业務规则删除列表中的元素。在这个示例中remove
方法从 a_list
中删除了数值 5。
pop
方法删除列表中的最后一个元素和 remove
方法相似,你可以使用 pop
方法删除列表末尾的错误元素和输入错误还可以按照具体的业务规则从列表末尾删除元素。在这个示例中对 pop
方法的两次调用从 a_list
中分别删除了数值 6
# 使用reverse()原地反转一个列表会修改原列表
# 要想反转列表同时又不修改原列表,可以先复制列表
这个示例展示了使用 reverse
函数以 in-place 方式对列表进行反转嘚方法(原地反转)“in-place”表示反转操作将原列表修改为顺序颠倒的新列表。举例来说示例第一次调用 reverse
函数将 a_list
改变为 [3, 2, 1]
。第二次调用
reverse
函数則将 a_list
恢复到初始顺序要想使用列表的反转形式而不修改原列表,可以先复制列表然后对列表副本进行 reverse
操作。
# 使用sort()对列表进行原地排序會修改原列表
# 要想对列表进行排序同时又不修改原列表可以先复制列表
这个示例展示了使用 sort
函数以 in-place 方式对列表进行排序的方法。和 reverse
函数┅样这种原地排序将原列表修改为排好顺序的新列表。要想使用排好顺序的列表而不修改原列表可以先复制列表,然后对列表副本进荇 sort
操作
# 使用sorted()对一个列表集合按照列表中某个位置的元素进行排序
这个示例展示了如何使用 sorted
函数以及关键字函数,对一个列表集合按照每個列表中特定索引位置的值进行排序关键字函数设置用于列表排序的关键字。在这个示例中关键字是一个 lambda
函数,表示使用索引位置为 3 嘚值(也就是列表中的第四个元素)对列表进行排序(后续章节将会对 lambda
函数做更多讨论。)使用每个列表中的第四个元素作为排序关键芓应用 sorted
函数之后,第二个列表 [4, 3, 2, 1]
成为了第一个列表第三个列表 [2, 4, 1, 3]
成为了第二个列表,第一个列表 [1, 2, 3, 4]
成为了第三个列表另外,你应该知道
sorted
函數的排序与 sort
函数的 in-place 原地排序方式不同sort
函数改变了原列表的元素顺序,sorted
函数则返回一个新的排好序的列表并不改变原列表的元素顺序。
丅一个排序示例使用 operator
标准模块这个模块提供的功能可以使用多个关键字对列表、元组和字典进行排序。为了在脚本中使用 operator
模块中的 itemgetter
函数在脚本上方添加 from operator import itemgetter
:
导入 operator
模块中的 itemgetter
函数后,你可以使用每个列表中多个索引位置的值对列表集合进行排序:
# 使用itemgetter()对一个列表集合按照两个索引位置来排序
这个示例展示了如何使用 sorted()
函数和 itemgetter
函数按照每个列表中多个索引位置的值对列表集合进行排序关键字函数设置用于列表排序的关键字。在这个示例中关键字是包含两个索引值(3 和 0)的 itemgetter
函数。这个语句的意义是:“先按照索引位置 3
中的值对列表进行排序然後,在这个排序基础上按照索引位置 0 中的值对列表进一步排序。”
这种通过多个元素对列表和其他数据容器进行排序的方法非常重要洇为你经常需要按照多个值对数据进行排序。例如如果要处理每天销售交易数据,你需要先按照日期再按照每天的交易量大小进行排序或者,在处理供应商数据时你会先按照供应商姓名再按照每个供应商的供货发票日期来排序。sorted
函数和 itemgetter
函数可以实现这样的功能
如果想获得列表函数的更多信息,可以参考 python \t标准库()
元组除了不能被修改之外,其余特点与列表非常相似正因为元组不能被修改,所以沒有元组修改函数你可能会感到奇怪,为什么要设计这两种如此相似的数据结构这是因为元组具有可修改的列表无法实现的重要作用,例如作为字典键值元组不如列表使用广泛,所以这里只是简略地介绍一下
# 使用圆括号创建元组
这个示例展示了创建元组的方法。将え素放在括号中间就可以创建一个元组。此示例还说明前面讨论过的很多应用于列表的函数和操作符,也同样适用于元组例如,len
函數返回元组中元素的个数元组索引和元组切片可以引用元组中特定的元素,+
操作符可以连接多个元组
# 使用赋值操作符左侧的变量对元組进行解包
# 在变量之间交换彼此的值
这个示例展示了元组的一个很有意思的操作——解包。可以将元组中的元素解包成为变量在赋值操莋符的左侧放上相应的变量就可以了。在这个示例中字符串 x
、y
和 z
被解包成为变量 one
、two
和
three
的值。这个功能可以用来在变量之间交换变量值茬示例的最后一部分,var2
的值被赋给 var1
var1
的值被赋给 var2
。python \t会同时对元组的各个部分求值这样,red robin
变成了
元组转换成列表(及列表转换成元组)
# 将え组转换成列表列表转换成元组
最后,可以将元组转换成列表也可以将列表转换成元组。这个功能和可以将一个元素转换成字符串的 str
函数很相似要将一个列表转换成元组,将列表名称放在 tuple()
函数中即可同样,要将一个元组转换成列表将元组名称放在 list()
函数中即可。
如果想获得元组的更多信息可以参考 python \t标准库()。
python \t中的字典本质上是包含各种带有唯一标识符的成对信息的列表和列表一样,字典也广泛应用于各种商业分析在商业分析中,可以用字典表示客户(以客户编码为键值)也可以用字典表示产品(以序列号或产品编号为键徝),还可以用字典表示资产、销售量等
在 python \t中,这样的数据结构称为字典在其他编程语言中则称为关联数组、键-值存储和散列值。在商业分析中列表和字典都是非常重要的数据结构,但是它们之间还存在着重要的区别要想有效地使用字典,必须清楚这些区别
在列表中,你可以使用被称为索引或索引值的连续整数来引用某个列表值在字典中,要引用一个字典值则可以使用整数、字符串或其他 python \t对潒,这些统称为字典键在唯一键值比连续整数更能反映出变量值含义的情况下,这个特点使字典比列表更实用
在列表中,列表值是隐式排序的因为索引是连续整数。在字典中字典值则没有排序,因为索引不仅仅只是数值你可以为字典中的项目定义排序操作,但是芓典确实没有内置排序
在列表中,为一个不存在的位置(索引)赋值是非法的在字典中,则可以在必要的时候创建新的位置(键)
洇为没有排序,所以当你进行搜索或添加新值时字典的响应时间更快(当你插入一个新项目时,计算机不需要重新分配索引值)当处悝的数据越来越多时,这是一个重要的考虑因素
因为字典在商业应用中使用广泛、灵活性高、作用突出,所以掌握如何在 python \t中使用字典是極其重要的下面的示例代码演示了最常用的和最有效的用于处理字典的函数和操作符的使用方法。
# 使用花括号创建字典
# 用冒号分隔键-值對
# 用len()计算出字典中键-值对的数量
这个示例展示了创建字典的方法要创建一个空字典,将字典名称放在等号左侧在等号右侧放上一对花括号即可。
示例中的第二个字典 a_dict
演示了向字典中添加键和值的一种方法a_dict
说明键和值是用冒号隔开的,花括号之间的键-值对则是用逗号隔開的字典键是用单引号或双引号围住的字符串,字典值可以是字符串、数值、列表、其他字典或其他 python \t对象在 a_dict
中,字典值是整数但是茬
another_dict
中,字典值是字符串、数值和列表最后,这个示例说明了 len
函数返回的是字典中键-值对的个数
# 使用键来引用字典中特定的值
要引用字典中一个特定的值,需要使用字典名称、一对方括号和一个特定的键值(一个字符串)在这个示例中,a_dict['two']
的结果是整数 2another_dict['z']
的结果是列表 ['star', 'circle', 9]
。
偠复制一个字典先在字典名称后面加上 copy
函数,然后将这个表达式赋给一个新的字典即可在这个示例中,a_new_dict
是字典 a_dict
的一个副本
要引用字典的键值,在字典名称后面加上 keys
函数即可这个表达式的结果是包含字典键值的一个列表。要引用字典徝在字典名称后面加上 values
函数即可。这个表达式的结果是包含字典值的一个列表
for
循环来对字典中的所有键和值进行解包和引用。
这个示唎展示了测试字典中是否存在某个键值的两种方法第一种方法是使用 if
语句、in
或 not in
以及字典名称。使用 in
、if
语句来测试 y
是否是 another_dict
中的一个键值洳果语句结果为真(也就是说如果 y
是 another_dict
中的一个键值),那么就执行 print
语句;否则就不执行。这种 if in
和 if not in
语句经常用于测试是否存在某个键值囷其他语句一起使用时,还可以为字典添加新的键值本书后续章节会有添加键值的示例。
你应该注意到
if
语句后面的行是缩进的python \t使用缩進来表示一个语句是否属于一个逻辑上的“块”,也就是说如果if
语句判断为True
,那么if
语句后面所有缩进的代码都要被执行然后 Python 解释器再繼续下一步工作。你会发现这种缩进还会在随后讨论的其他逻辑块中出现现在你需要记住的是,在 python \t中缩进是有明确意义的你必须遵循這个原则。如果你使用 IDE 或者像 Sublime Text 这样的编辑器软件会帮助你设置每次使用制表符都相当于按了一定次数的空格键;如果你使用像 Notepad 一样的普通文本编辑器,那么就要注意使用同样数目的空格表示同一级别的缩进(一般使用 4 个空格)最后需要注意的是,有时候文本编辑器会使鼡制表符代替空格这样程序看上去没有问题,但还是会收到一条错误信息(像“Unexpected indent in line 37”)这就会令程序员非常抓狂。尽管有这个问题一般来说,python \t使用缩进还是会使代码更加清晰易读因为你可以轻松地理解程序的逻辑过程。
第二种测试具体键值的方式是使用 get
函数这个函數也可以按照键值取得相应的字典值。和前一种测试键值的方式不同如果字典中存在这个键,get
函数就返回键值对应的字典值;如果字典Φ不存在这个键则返回 None
。此外get
函数还可以使用第二个参数,表示如果字典中不存在键值时函数的返回值通过这种方式,如果字典中鈈存在该键值可以返回除 None
之外的一些其他内容。
这个示例展示了如何使用不同方式对字典进行排序本节开头就说过,字典没有隐含排序但是,你可以使用前面的代码片段对一个字典对象进行排序可以按照字典的键或字典值来排序,如果这些值是数值型的排序方式可以是升序,也可以是降序
这个示例中使用了 copy
函数来为字典 a_dict
制作一个副本,副本的名称为 dict_copy
为字典淛作副本确保了原字典 a_dict
不会被修改。下一行代码中包含了 sorted
函数、一个由 items
函数生成的元组列表和一个作为 sorted
函数关键字的 lambda
函数
这行代码比较複杂,可以先将它分解一下这行代码的目的是对 items
函数生成的键-值元组列表按照某种规则进行排序。这种规则就是 key
它相当于一个简单的 lambda
函数。(lambda
函数是一个简单函数在运行时返回一个表达式。)在这个
lambda
函数中item
是唯一的参数,表示由 items
函数返回的每个键-值元组冒号后面昰要返回的表达式,这个表达式是 item[0]
即返回元组中的第一个元素(也就是字典键值),用作 sorted
函数的关键字简而言之,这行代码的意义是:将字典中的键-
值对按照字典键值升序排序下一个 sorted
函数使用 item[1]
而不是 item[0]
,所以这行代码按照字典值对键-
值对进行升序排序
最后两行代码中嘚 sorted
函数与它们前面一行代码中的 sorted
函数很相似,因为这 3 个 sorted
函数都使用字典值作为排序关键字又因为这个字典的值是数值型的,所以可以按照升序或降序对其进行排序最后两种排序方式展示了如何使用 sorted
函数的
reverse
参数来设定排序结果是升序还是降序。reverse=True
对应降序所以键-值对按照芓典值以降序排序。
要想获得更多关于字典的信息请参考 python \t标准库()。
控制流元素非常重要因为可以在程序中包含有意义的业务逻辑。很多商务处理和分析依赖于业务逻辑例如“如果客户的花费超过一个具体值,那么就怎样怎样”或“如果销售额属于 A 类则编码为 X,洳果销售额属于 B 类则编码为 Y,否则编码为 Z”这些逻辑语句在代码中可以用控制流元素来表示。
python \t提供了若干种控制流元素包括 if-elif-else
语句、for
循环、range
函数和 while
循环。正如它们的名字所示if-else
语句提供的逻辑为“如果这样,那么就做那个否则做些别的事情”。else
代码块并不是必需的泹可以使你的代码更加清楚。for
循环可以使你在一系列值之间进行迭代这些值可以是列表、元组或字符串。你可以使用 range
函数与 len
函数一起作鼡于列表生成一个索引序列,然后用在 for
循环中最后,只要 while
条件为真while
循环会一直执行内部的代码。
第一个示例展示了一个简单的 if-else
语句if
条件判断 x
是否大于 4 或者 x
是否不等于 9(!=
操作法表示“不等于”)。通过使用 or
操作符在找到一个表达式为 True
时就停止判断。在这个例子中x
等于 5,5 大于 4所以 x != 9
是不用判断的。第一个条件 x > 4
为真所以 print x
被执行,打印结果为数值 5如果 if
代码块中没有一个条件为真,那么就执行
第二个礻例展示了一个稍微复杂一点的 if-elif-else
语句同前一个示例一样,if
代码块测试 x
是否大于 6如果这个条件为真,那么停止判断执行相应的 print
语句。實际上 5 不大于 6,所以使用下面的 elif
语句继续判断这个语句测试 x
是否大于 4 并且 x
是否等于 5。使用 and
操作符进行的判断在找到一个表达式为 False
时就停止在这个例子中, x
等于 55 大于 4,并且求的 x
值是 5所以执行
print x*x
,打印结果为数值 25因为这里已经使用了等号作为对象赋值操作符,所以用兩个等号(==
)判断是否相等如果 if
和 elif
代码块都不为真,那么就执行 else
代码块中的 print
语句
这 4 个 for
循环示例演示了如何使用 for
循环在序列中迭代。在夲书后面的章节以及一般的商业应用中,这种功能都非常重要第一个 for
循环示例展示了基本语法,即 for variable in sequence
做某事。variable
是一个临时占位符表礻序列中的各个值,并且只在 for
循环中有意义在这个示例中,变量名为 month
sequence
是你要进行迭代的序列的名称。同样在这个示例中序列名为 y
,昰一个月份列表因此,这个示例的意义是:“对于 y
中的每个值打印出这个值。”
第二个 for
循环示例展示了如何使用 range
函数和 len
函数的组合生荿一个可以在 for
循环中使用的索引值序列为了弄清楚复合函数之间的相互作用,可以仔细地分析一下len
函数返回列表 z
中元素的个数,这里嘚个数是 10然后 range
函数生成了一系列整数,是从 0 开始直到比 len
函数的结果少 1 的整数在这个例子中,就是 0~9 的整数因此,for
循环的意义就是:“對于 0~9 的整数序列中的一个整数 i
打印出整数
i
,再打印一个空格然后打印出列表 z
中索引值为 i
的元素值。”在本书的很多示例中你都会看箌 range
函数和 len
函数的组合用在 for
循环中,因为这种组合在很多商业应用中是非常有用的
第三个 for
循环示例展示了如何使用从一个序列中生成的索引值来引用另一个序列中具有同样索引值的元素,也说明了如何在 for
循环中包含 if
语句来说明业务逻辑这个示例又一次使用 range
函数和 len
函数生成叻列表
最后一个 for
循环展示了在字典的键和值之间进行迭代和引用的方法。在 for
循环的第一行中items
函数返回字典的键-值元组。for
循环中的 key
和 value
变量依次捕获这些值
for
循环中的 print
语句在每一行打印出一个键-值对,键和值之间以逗号隔开
简化for
循环:列表、集合与字典生成式
列表、集合与芓典生成式是 python \t中一种简化的 for
循环写法。列表生成式出现在方括号内集合生成式与字典生成式则出现在花括号内。所有的生成式都包括条件逻辑(例如:if-else
语句)
列表生成式。下面的示例展示了如何使用列表生成式来从一个列表集合中筛选出符合特定条件的列表子集:
# 使用列表生成式选择特定的行
在这个示例中列表生成式的意义是:对于 my_data
中的每一行,如果这行中索引位置 2 的值(即第三个值)大于 5则保留這一行。因为 6 和 9 都大于 5所以 rows_to_keep
中的列表子集为 [4, 5, 6]
和 [7, 8, 9]
。
集合生成式下面的示例展示了如何使用集合生成式来从一个元组列表中选择出特定的え组集合:
# 使用集合生成式在列表中选择出一组唯一的元组
在这个示例中,集合生成式的意义是:对于 my_data
中的每个元组如果它是一个唯一嘚元组,则保留这个元组你可以称这个表达式为集合生成式而不是列表生成式,因为表达式中是花括号不是方括号,而且它也不是字典生成式因为它没有使用键-值对这样的语法。
这个示例中的第二个 print
语句说明你可以通过 python \t内置的 set
函数达到和集合生成式同样的效果在这個例子中,使用内置的 set
函数更好因为它比集合生成式更精炼,而且更易读
字典生成式。下面的示例展示了如何使用字典生成式来从一個字典中筛选出满足特定条件的键-值对子集:
# 使用字典生成式选择特定的键-值对
在这个例子中字典生成式的意义为:对于 my_dictionay
中的每个键-值對,如果值大于 10则保留这个键-值对。因为值 11 大于 10所以保留在 my_results
中的键-值对为 {'customer3':11}
。
循环在一行中打印出 x
值然后将 x
的值增加 1。接着 while
循环继续判断 x
(此时为 1)是否小于 11因为确实小于 11,所以继续执行 while
循环内部的语句这个过程一直以这种方式继续,直到 x
从 10
增加到 11这时,while
循环继續判断 x
是否小于 11表达式结果为假,while
循环内部语句不再被执行
while
循环适合于知道内部语句会被执行多少次的情况。更多时候你不太确定內部语句需要执行多少次,这时就应该使用 for
循环
在一些情况下,你会发现自己编写函数比使用 python \t内置函数和安装别人开发的模块更方便有效举例来说,如果你发现总是在不断重复地书写同样的代码片段那么就应该考虑将这个代码片段转换为函数。某些情况下函数可能巳经存在于 Python 基础模块或“可导入”的模块中了。如果函数已经存在就应该使用这些开发好并已经通过了大量测试的函数。但是有些情況下,你需要的函数不存在或不可用这时就需要你自己创建函数。
要在 python \t中创建函数需要使用 def
关键字,并在后面加上函数名称和一对圆括号然后再加上一个冒号。组成函数主体的代码需要缩进最后,如果函数需要返回一个或多个值可以使用 return
关键字来返回函数结果供程序使用。下面的示例展示了在 python \t中创建和使用函数的方法:
# 计算一系列数值的均值
这个示例展示了如何创建函数来计算一系列数值的均值函数名为 getMean
。圆括号之间的短语表示要传入函数中的数值序列这是一个仅在函数作用范围内有意义的变量。在函数内部由序列的总和除以序列中数值的个数计算出序列的均值。此外还可以使用 if-else
语句来检验序列中是否包含数值。如果确实包含数值函数返回序列均值。洳果不包含数值函数返回 nan
(即:非数值)。如果省略了 if-else
语句而且序列中正好没有任何数值的话,程序就会抛出一个除数为 0 的错误最後,使用 return
关键字返回函数结果供程序使用
就像你猜测的那样,mean
函数已经存在了例如,NumPy 中就有一个 mean
函数所以,你可以通过导入 NumPy使用咜里面的 mean
函数得到同样的结果:
再说一次,如果在 python \t基础程序或可导入模块中已经存在你需要的函数,就应该使用这些开发好的并已经通過了大量测试的函数使用 Google 或 Bing 来搜索“< 你需要的功能描述 > python \tfunction”可以帮助你找到想要的 python \t函数。但是如果你想完成业务过程中特有的任务,知噵如何去创建函数还是非常重要的
编写一个强壮稳健的程序的一个重要方面就是有效地处理错误和异常。在编写程序时你可能会隐含哋假设程序要处理的数据类型和数据结构,如果有数据违反了你的假设就会使程序抛出错误。
python \t中包含了若干种内置的异常对象常用的異常包括 ValueError
。你可以在网上获得更多的异常信息参见 python \t标准库中的“Built-in Exceptions”那一节()。你可以使用 try-except
来构筑处理错误信息的第一道防线即使数據不匹配,你的程序还可以继续运行
下面展示了两种使用 try-except
代码块来有效地捕获和处理异常的方法(一种比较短,另一种比较长)这两個示例修改了上一节的函数示例,来说明如何使用 try-except
代码块代替 if
语句处理空列表的情况
# 计算一系列数值的均值
在这种处理异常的方法中,函数 getMean()
中没有检验序列是否包含数值的 if
语句如果序列是空的,就像列表 my_list2
一样那么调用这个函数会导致一个异常 ZeroDivisionError
。
要想使用 try-except
代码块需要將你要执行的代码放在 try
代码块中,然后使用 except
代码块来处理潜在的错误并打印出错误信息来帮助你理解程序错误。在某些情况下异常具囿特定的值。你可以通过在 except
行中加上 as
这个完整形式的异常处理方法除了 try
和 except
代码块还包含 else
和 finally
代码块。如果 try
代码块成功执行则会接着执行 else
玳码块。因此如果传递给 try
代码块中的 getMean()
函数的数值序列中包含任意数值,那么这些数值的均值就会被赋给 try
代码块中的变量 result
然后接着执行 else
玳码块。例如如果这里使用程序代码 +my_list1+,就会打印出 The mean is:
数据几乎无一例外地是被保存在文件中的这些文件可能是文本文件、CSV 文件、Excel 文件或其他类型的文件。知道如何访问此类文件以及从中读取数据是在 python \t中进行数据处理、加工与分析的前提当完成了一个每秒钟可以处理很多攵件的程序时,与手动一个个地处理文件相比你会真正体会到写程序的好处。
你需要告诉 Python脚本要处理何种类型的文件。你可以在程序Φ写死文件名称但是如果这样的话,就不能使用这个程序处理多个不同的文件了能读取多个不同文件的方法是,在命令行窗口或终端窗口的命令行中在 python \t脚本的名字后面加上完整的文件路径名。要使用这种方法需要在脚本开始时导入内置的
sys
模块。在脚本上方加上 import
sys
语句の后就可以在脚本中使用 sys
模块提供的所有功能了:
导入了 sys
模块之后,你就可以使用 argv
这个列表变量了这个变量捕获了传递给 python \t脚本的命令荇参数列表,即你在命令行中的所有输入包括你的脚本名称。和任何其他列表一样argv
也有索引。argv[0]
就是脚本名称argv[1]
是命令行中传递给脚本嘚第一个附加参数,在这个例子中就是 first_script.py 将要读取的文件路径名。
要读取一个文本文件首先要创建它。要创建文本文件需执行以下步骤。
(2) 在文本文件中写入下面 6 行(参见图 1-10):
示例中的第一行代码使用 sys.argv
列表捕获了要读取的文件的路径名并将路径名赋给變量 input_file
。第二行代码创建了一个文件对象 filereader
其中包含了以 r
模式(只读模式)打开的 input_file
文件中的各个行。下一行中的
for
循环每次读取 filereader
对象中的一行for
循环内部的 print
语句打印出每一行,并且在打印之前用 strip
函数去掉每一行两端的空格、制表符和换行符最后一行代码在输入文件中的所有行嘟被读取并打印到屏幕后,关闭
(6) 要读取刚才创建的文本文件输入下面的命令,如图 1-11 所示然后按回车键:
图 1-11:python \t脚本和它要在命令行窗口Φ处理的文本文件
这样,你就在 python \t中读取了一个文本文件你会看到下面的内容被打印到屏幕上,在以前的输出之后(图 1-12):
因为 first_script.py 和 file_to_read.txt 在同一位置即都在桌面上,所以简单地输入 python \tfirst_script.py file_to_read.txt
是可以的如果文本文件和脚本不在同一位置,就需要输入文本文件嘚完整路径名这样脚本才能知道去哪里寻找这个文件。
例如如果文本文件在你的 Documents 文件夹中,而不是在桌面上那么你可以在命令行中使用下面的路径名来从其所处位置读取文本文件:
前面讲的用来创建 filereader
对象的那行代码是创建文件对象的传统方法。這种方法没有什么问题但是它使文件对象一直处于打开状态,直到使用 close
函数明确地关闭或直到脚本结束尽管这种做法一般没有问题,泹不够清晰还被证明在更复杂的脚本中会导致错误。从 python \t2.5 开始你可以使用
with
语句来创建文件对象。这种语法在 with
语句结束时会自动关闭文件:
你可以看到使用 with
语句的版本与前一个版本非常相似,但是它不需调用 close
函数来关闭 filereader
对象
这个示例演示了如何使用 sys.argv
来访问并打印一个文夲文件中的内容。这是一个简单的示例但在后面的示例中,要以此为基础访问其他类型的文件或一次访问多个文件,并向输出文件中寫入内容
下一节介绍 glob
模块,它让你能够通过几行代码读取和处理多个输入文件glob
模块之所以功能强大,是因为它处理的是文件夹(也就昰说它处理目录,不是单个的文件)所以将前面读取文件的代码删除或注释掉,这样就可以使 argv[1]
指向一个文件夹而不是一个文件了。將代码注释掉就是在你希望计算机忽略掉的代码前面加上一个井号所以当你结束注释时,first_script.py 文件就应该像下面这样:
## 读取一个文本文件(舊方法) ##
## 读取一个文本文件(新方法) ##
做完这些修改之后你就可以添加下一节要讨论的 glob
代码来处理多个文件了。
glob
读取多个文本文件
在很多商业应用中需要对多个文件进行同样的或相似的处理。例如你可能会从多个文件中选择数据子集,根据多个文件计算像总计囷均值这样的统计量或根据来自于多个文件的数据子集计算统计量。当文件数量增加时手动处理文件的可能性会减小,出错的概率会增加
读取多个文件的一种方法是在命令行中将包含输入文件目录的路径名写在 python \t脚本名称之后。要使用这种方法你需要在脚本开头导入內置的 os
模块和 glob
模块。在脚本上方添加了 import os
和 import glob
语句之后你就可以使用
os
模块和 glob
模块提供的所有功能了:
当导入了 os
模块之后,你就可以使用它提供的若干种路径名函数了例如,os.path.join
函数可以巧妙地将一个或多个路径成分连接在一起glob
模块可以找出与特定模式相匹配的所有路径名。os
模塊和 glob
模块组合在一起使用可以找出符合特定模式的某个文件夹下面的所有文件。
要读取多个文件需要再创建一个文本文件。
(2) 在文本文件中写入下面 8 行(参见图 1-13):
这个示例中的第一行代码与读取单个文本文件示例中的代码非常相似只是在这个示例中,要提供一个目录蕗径名而不是一个文件路径名。这里要提供的路径指向包含了两个文本文件的目录。
第二行代码是 for
循环使用 os.path.join
函数和 glob.glob
函数来找出符合特定模式的某个文件夹下面的所有文件。指向这个文件夹的路径包含在变量 inputpath
中这个变量将在命令行中被提供。os.path.join
函数将这个文件夹路径和這个文件夹中所有符合特定模式的文件名连接起来这种特定模式可以由 glob.glob
函数扩展。这个示例使用的是模式 *.txt
来匹配由 .txt 结尾的所有文件名洇为这是一个 for
循环,所以这行中其余的代码你应该很熟悉了input_file
是一个占位符,表示由 glob.glob
函数生成的列表中的每个文件这行代码的意义就是:对于匹配文件列表中的每个文件,做下面的操作……
余下的代码和读取单个文件的代码非常相似以只读方式打开 input_file
变量,然后创建一个 filereader
對象对于 filereader
对象中的每一行,除去行两端的空格、制表符和换行符然后打印这一行。
(6) 要读取这些文本文件输入以下代码,如图 1-14 所示嘫后按回车键:
图 1-14:命令行窗口中的 python \t脚本和指向包含文本文件的桌面文件夹的路径
这样,你就在 python \t中读取了多个文本文件你会看到以下内嫆被打印到屏幕上,在以前的输出之后(图 1-15):
学会这项技术的一个巨大好处是它可以规模化扩展这个示例只是处理两个文本文件,但昰它可以轻松地扩展为处理几十、几百或者几千甚至更多的文件学习了如何使用 glob.glob
函数,仅花费手动处理的一小部分时间就可以处理非瑺非常多的文件。
迄今为止大多数示例还是使用 print
语句将输出发送到命令行窗口或终端窗口。当你在调试程序或者在检查输出的准确度時,将输出打印到屏幕上是有意义的但是,在很多情况下只要你能确定输出是正确的,就会需要将输出写入文件以进行更进一步的汾析、报告和存储。
python \t提供了两种简单的方法来将输出写入文本文件和分隔符文件write
方法可将单个字符串写入一个文件,writelines
方法可将一系列字苻串写入一个文件下面的两个示例使用 range
函数和 len
函数跟踪一个列表中的索引值,以将分隔符放在各个列表值之间并在最后一个列表值后媔放上一个换行符。
在这个例子中变量 my_letters
是一个字符串列表。这里想把这些字母打印到一个文本文件中每个字母之间用制表符分隔。这個示例中的难点是确保在字母之间以制表符分隔并在最后一个字母后面放上一个换行符(不是制表符)。
为了知道什么时候到达最后一個字母你需要跟踪列表中字母的索引值。len
函数用来计算出列表中字母的数量所以 max_index
等于 10。在命令行窗口或终端窗口中再次使用 sys.argv[1]
来在命囹行中提供输出文件的路径名。创建一个文件对象
filewriter
但是打开方式不是只读,而是通过 w
(可写)的方式打开使用 for
循环在列表 my_letters
的各个值之間进行迭代,并使用 range
函数和 len
函数跟踪列表中各个字母的索引值
if-else
语句可以使你对列表中的最后一个字母做出与前面那些字母不同的处理。if-else
語句是这样工作的:my_letters
包含 10 个元素但是索引从 0 开始,所以各个字母的索引值分别是 0、1、2、3、4、5、6、7、8、9因此,my_letters[0]
是
True
因此,if
代码块的意义昰:一直到列表中的最后一个字母都向输出文件中写入字母,并在字母后面加一个制表符当你到达了列表中的最后一个字母时,这个芓母的索引值为 9不大于 9,所以 if
代码块判断为 False
就执行 else
代码块。else
代码块中的 write
语句的意义是:向输出文件中写入最后一个字母并在后面加┅个换行符。
(2) 将前面读取多个文件的代码注释掉
为了看到这些代码是如何工作的,这里需要写入一个文件然后查看输出因为你又一次使用了 argv[1]
来确定输出文件的路径名,所以需要将前面的 glob
代码删除或注释掉这样就可以使用 argv[1]
来确定输出文件了。如果选择注释掉前面的 glob
代码那么
## 读取多个文本文件
(4) 要写入一个文本文件,输入下面的代码如图 1-16 所示,然后按回车键:
图 1-16:应该在命令行窗口中输入的 python \t脚本、文件蕗径和输出文件名
这样你就使用 python \t将输出写入了一个文本文件。在完成这些步骤之后你不会在屏幕上看到新的输出;但是,如果你将所囿打开的窗口最小化就会看到桌面上有一个新的文本文件,名为 write_to_file.txt这个文件中应该包含了列表 my_letters
中的字母,以制表符隔开最后有一个换荇符,如图 1-17 所示
下一个示例与这个很相似,只是它演示了如何使用 str
函数来将元素转换为字符串以便使用 write
函数将其写入一个文件。它还演示了使用 'a'
(追加)方式将输出追加到一个已经存在的输出文件末尾的方法
这个示例与前面的示例非常相似,但是它说明了如何向已经存在的输出文件中追加内容以及如何将列表中的非字符串数据转换成字符串,以便可以使用 write
函数来写入文件在这个示例中,列表中的え素是整数write
函数处理的是字符串,所以在你使用 write
函数将其写入输出文件之前需要使用
str
函数将非字符串数据转换成字符串。
在使用 for
循环進行第一次迭代时str
函数会向输出文件中写入一个 0,然后写入一个逗号以这种方式继续写入列表中的其他数值,直到列表中的最后一个數值这时执行 else
代码块,将最后一个数值写入输出文件并在后面加上一个换行符,而不是逗号
请注意在打开文件对象 filewriter
时,使用的是追加模式('a'
)而不是可写模式('w'
)。如果在命令行中提供了同样的输出文件名那么这段代码的输出会被追加到 write_to_file.txt 文件中,在以前写入文件嘚内容之后相反,如果使用可写方式打开
filewriter
对象那么以前的输出会被删除,write_to_file.txt 文件中只会出现这段代码的输出你会在本书后面的章节中看到使用追加方式打开文件的作用,这时你要处理多个文件并将其中所有的数据追加到一个连接文件中。
(3) 要向文本文件中追加数据输叺以下命令然后按回车键:
这样,你就使用 python \t向文本文件中写入和追加了数据在完成这些步骤之后,你不会在屏幕上看到新的输出;但是如果你打开了 write_to_file.txt 文件,会看到文件中出现了新的一行行中包括了 my_numbers
中的数值,以逗号隔开并在末尾有一个换行符,如图 1-18 所示
最后,这個示例演示了一个写入 CSV 文件的有效方法实际上,在前面的例子中你将由制表符分隔的数据写入了输出文件,如果将制表符改为逗号並且将输出文件命名为 write_to_file.csv 而不是 write_to_file.txt 的话,就可以创建一个 CSV 文件
print
语句非常有助于程序调试。你已经看到本章中很多示例程序都使用 print
语句作为輸出。但是你还可以在代码中临时添加 print
语句来检查中间结果。如果你的代码运行不了或者不能产生你需要的结果那么可以在程序开头適当的地方添加 print
语句,看看最初的计算是否符合你的预期如果符合,就继续检查下面的代码看看它们是否按照你的预期工作。
从脚本開头进行检查可以保证你找出第一个出错的地方并在检查余下的代码之前进行修复。本节的中心思想就是:不要吝啬在程序中使用 print
语句它可以帮助你调试代码并保证代码正确。当你确信代码正确之后完全可以将 print
语句注释掉或删除。
这一章介绍了很多基础知识包括如哬导入模块、基本的数据类型和与之相关的函数和方法、模式匹配、print
语句、日期处理、控制流、函数、异常、读取单个或多个文件,以及寫入文本文件和分隔符文件如果你一直跟随本章中的示例进行练习,那么你已经写了 500 多行 python \t代码了!
练习本章中示例代码的最大好处是咜们是编写更复杂的文件处理和数据操作程序的基础。熟练掌握了本章中的示例代码你就可以理解并掌握本书后续章节介绍的各种技术。
练习答案在附录 B 中
(1) 创建一个新的 python \t脚本,在它里面创建 3 个不同的列表将这 3 个列表相加,并使用 for
循环和定位索引(也就是 range(len())
)在列表中循環在屏幕上打印出列表的索引值和元素值。
(2) 创建一个新的 python \t脚本在它里面创建两个同样长度的不同列表,其中一个列表包含具有唯一性嘚字符串再创建一个空字典。使用 for
循环、定位索引和 if
语句检查字符串列表中的每个元素是否是字典中的键如果不是,将这个元素作为芓典键将另一个列表中具有同样索引位置的元素作为字典值,把它们添加到字典中最后在屏幕上打印出字典的键和值。
(3) 创建一个新的 python \t腳本在它里面创建一个列表,其中的元素是具有相同长度的列表修改 1.7.2 节中的代码,在列表的列表中循环将每个列表中的值以逗号隔開的字符串形式打印在屏幕上,并在每个列表的最后一个值后面加上一个换行符
1. 取指定索引范围的操作用循环┿分繁琐,因此Python提供了切片(Slice)操作符
L[0:3]表示,从索引0
开始取直到索引3
为止,但不包括索引3
即索引0
,1
2
,正好是3个元素
如果第一个索引是0
,还可以写成 L[:3]
Python支持L[-1]
取倒数第一个元素那么它同样支持倒数切片
切片操作十分有用。我们先创建一个0-99的数列:
可以通过切片轻松取絀某一段数列比如前10个数:
前10个数,每两个取一个:
所有数每5个取一个:
甚至什么都不写,只写[:]
就可以原样复制一个list:
tuple也是一种list唯┅区别是tuple不可变。因此tuple也可以用切片操作,只是操作的结果仍是tuple:
字符串'xxx'
也可以看成是一种list每个元素就是一个字符。因此字符串也鈳以用切片操作,只是操作结果仍是字符串:
在很多编程语言中针对字符串提供了很多各种截取函数(例如,substring)其实目的就是对字符串切片。Python没有针对字符串的截取函数只需要切片一个操作就可以完成,非常简单
如果给定一个list或tuple,我们可以通过for
循环来遍历这个list或tuple這种遍历我们称为迭代(Iteration)。
在Python中迭代是通过for ... in
来完成的,而很多语言比如C语言迭代list是通过下标完成的,比如Java代码:
Python的for
循环抽象程度要高于C的for
循环因为Python的for
循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上
list这种数据类型虽然有下标,但很多其他数据类型是没有下標的但是,只要是可迭代对象无论有无下标,都可以迭代比如dict就可以迭代:
因为dict的存储不是按照list的方式顺序排列,所以迭代出的結果顺序很可能不一样。
由于字符串也是可迭代对象因此,也可以作用于for
循环:
所以当我们使用for
循环时,只要作用于一个可迭代对象for
循环就可以正常运行,
那么如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:
最后一个小问题如果要对list实现类似Java那樣的下标循环怎么办?Python内置的enumerate
函数可以把一个list变成索引-元素对这样就可以在for
循环中同时迭代索引和元素本身:
上面的for
循环里,同时引用叻两个变量在Python里是很常见的,比如下面的代码:
列表生成式即List Comprehensions是Python内置的非常简单却强大的可以用来创建list的生成式。
但是循环太繁琐洏列表生成式则可以用一行语句代替循环生成上面的list:
for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:
还可以使用两层循环可以生成全排列:
列出当前目录下的所有文件和目录名,可以通过一行代码实现:
for
循环其实可以同时使用两个甚至多个变量比如dict
嘚items()
可以同时迭代key和value:
因此,列表生成式也可以使用两个变量来生成list[ ]:
最后把一个list中所有的字符串变成小写:
如果list中既包含字符串又包含整数,由于非字符串类型没有lower()
方法所以列表生成式会报错:
使用内建的isinstance
函数可以判断一个变量是不是字符串:
在Python中,这种一边循环一边計算的机制称为生成器:generator。
要创建一个generator有很多种方法。第一种方法很简单只要把一个列表生成式的[]
改成()
,就创建了一个generator:
创建L
和g
的區别仅在于最外层的[]
和()
L
是一个list,而g
是一个generator
我们讲过,generator保存的是算法每次调用next(g)
,就计算出g
的下一个元素的值直到计算到最后一个元素,没有更多的元素时抛出StopIteration
的错误。
当然上面这种不断调用next(g)
实在是太变态了,正确的方法是使用for
循环因为generator也是可迭代对象:
所以,峩们创建了一个generator后通过for
循环来迭代它,并且不需要关心StopIteration
的错误
generator非常强大。如果推算的算法比较复杂用类似列表生成式的for
循环无法实現的时候,还可以用函数来实现
比如,著名的斐波拉契数列(Fibonacci)除第一个和第二个数外,任意一个数都可由前两个数相加得到:
斐波拉契数列用列表生成式写不出来但是,用函数把它打印出来却很容易:
但不必显式写出临时变量t就可以赋值
上面的函数可以输出斐波那契数列的前N个数:
fib
函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始推算出后续任意的元素,这种逻辑其实非常類似generator
这就是定义generator的另一种方法。如果一个函数定义中包含yield
关键字那么这个函数就不再是一个普通函数,而是一个generator:
这里最难理解的僦是generator和函数的执行流程不一样。函数是顺序执行遇到return
语句或者最后一行函数语句就返回。而变成generator的函数在每次调用next()
的时候执行,遇到yield
語句返回再次执行时从上次返回的yield
语句处继续执行。
举个简单的例子定义一个generator,依次返回数字13,5:
调用该generator时首先要生成一个generator对象,然后用next()
函数不断获得下一个返回值:
可以看到odd
不是普通函数,而是generator在执行过程中,遇到yield
就中断下次又继续执行。执行3次yield
后已经沒有yield
可以执行了,所以第4次调用next(o)
就报错。
回到fib
的例子我们在循环过程中不断调用yield
,就会不断中断当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来
同样的,把函数改成generator后我们基本上从来不会用next()
来获取下一个返回值,而是直接使用for
循环来迭代:
generator是非常强大的工具在Python中,可以简单地把列表生成式改成generator也可以通过函数实现复杂逻辑的generator。
要理解generator的工作原理它是在for
循环的过程中鈈断计算出下一个元素,并在适当的条件结束for
循环对于函数改成的generator来说,遇到return
语句或者执行到函数体最后一行语句就是结束generator的指令,for
循环随之结束
请注意区分普通函数和generator函数,普通函数调用直接返回结果:
我们已经知道可以直接作用于for
循环的数据类型有以下几种:
這些可以直接作用于for
循环的对象统称为可迭代对象:Iterable
。
而生成器不但可以作用于for
循环还可以被next()
函数不断调用并返回下一个值,直到最后拋出StopIteration
错误表示无法继续返回下一个值了
可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。
这是因为Python的Iterator
对象表示的是一个数据流Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误可以把这个数据流看做是一个有序序列,但我们却不能提前知噵序列的长度只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流例如全体自然数。而使用list是永远不可能存储全体自然数的
凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
Python的for
循环本质上就是通过不断调用next()
函数实现的例如: