一、Python基础

Python的语法比较简单,采用缩进方式,写出来的代码就像下面的样子:

# print absolute value of an integer:
a = 100
if a >= 0:
    print(a)
else:
    print(-a)

#开头的语句是注释,注释是给人看的,可以是任意内容,解释器会忽略掉注释。其他每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块。
缩进有利有弊。好处是强迫你写出格式化的代码,但没有规定缩进是几个空格还是Tab。按照约定俗成的惯例,应该始终坚持使用4个空格的缩进。

1、shebang行

打开文本编译器,将下面两行代码写在文本文件中:

#!/usr/bin/env python3
print("Output #1: I'm excited to learn Python.")

第一行比较特殊,称为shebang行,在Python脚本中,你应该一直将它作为第一行。以#开头的行为单行注释,安装了Windows系统的计算机不读取也不执行这行代码。但是,安装了Unix系统的计算机使用这一行来找到执行文件中代码的Python版本。因为Windows系统忽略这一行,像macOS这样的基于Unix的系统使用这一行,所以加入这一行可以使脚本在不同操作系统的计算机之间具有可移植性。

2、赋值变量+变量相加+格式化print语句

代码:

# 两个数值相加
x=4
y=5
z=x+y
print("Output #2: Four plus five equals: {0:d}".format(z))

解释:
此示例展示了将数值赋给变量、变量相加和格式化print语句的方法。
这里详细讲一下print语句中的语法"{0,d}".format(z)。花括号({})是一个占位符,表示这里将要传入print语句一个具体的值,这里就是变量z的值。0指向format()方法中的第一参数,在这里,只包含一个参数z,所以0就指向这个值;相反,如果有多个参数,0就确定的表示传入第一参数。
冒号(:)用来分隔传入的值和它的格式。d表示这个值应该被格式化为整数,没有小数部分。

3、创建列表+列表相加+打印列表

代码:

# 两个列表相加
a=[1,2,3,4]
b=["first","second","third","fourth"]
c=a+b
print("Output #3: {0},{1},{2}".format(a,b,c))

解释:
此示例展示了创建列表、列表相加和将列表中的值以逗号分隔打印在屏幕上的方法。
看一下print语句中的语法"{0},{1},{2}".format(a,b,c),它说明了如何在print语句中包含多个值。a被传给{0},b被传给{1},c被传给{2}。因为这3个值都是列表,不是数值,所以不设置数值格式。

4、为什么要在打印时使用format

Python并不要求每条print语句都必须使用.format,但是,.format确实功能强大,可以为你节省很多输入。在上面的示例中,注意

print("Output #3: {0},{1},{2}".format(a,b,c))

最终结果是用逗号分隔3个变量。如果你想在不使用.format的情况下得到同样的结果,那么就应该这样写:

print("Output #3:",a,",",b,",",c)

注意:
两种输入方法的差异

Output #3: [1, 2, 3, 4],['first', 'second', 'third', 'fourth'],[1, 2, 3, 4, 'first', 'second', 'third', 'fourth']

Output #3: [1, 2, 3, 4] , ['first', 'second', 'third', 'fourth'] , [1, 2, 3, 4, 'first', 'second', 'third', 'fourth']

print()函数也可以接受多个字符串,用逗号“,”隔开,就可以连成一串输出,rint()会依次打印每个字符串,遇到逗号“,”会输出一个空格,所以第二种打印方法里,abc三个列表输出的结果之间有一个空格间隔。

5、format函数

print语句中的语法"{0,d}".format(z)
1)花括号({})是一个占位符,表示这里将要传入print语句一个具体的值,这里就是变量z的值。
2)0指向format()方法中的第一参数,在这里,只包含一个参数z,所以0就指向这个值;相反,如果有多个参数,0就确定的表示传入第一参数。
3)冒号(:)用来分隔传入的值和它的格式。

1)整数:d

d {0:d}表示这个值应该被格式化为整数,没有小数部分。

2)浮点数:.*f

*.f 表示设置print语句中的小数位数,{0:.3f}设定了打印的输出值应该有3位小数。

3)字符串:s

s {0:s}表示这个值应该被格式化为字符串

4)时间转字符串:!s

!s {0!s} 中的!s 表示传入到print 语句中的值应该格式化为字符串,尽管它是个数值型数据。

6、缩进的作用

another_dict = {'x':'printer', 'y':5, 'z':['star', 'circle', 9]}
if 'y' in another_dict:
    print("Output #114: y is a key in another_dict: {}."\
.format(another_dict.keys()))

你应该注意到if 语句后面的行是缩进的。Python 使用缩进来表示一个语句是否属于一个逻辑上的“块”,也就是说,如果if 语句判断为True,那么if 语句后面所有缩进的代码都要被执行,然后Python 解释器再继续下一步工作。你会发现这种缩进还会在随后讨论的其他逻辑块中出现,现在你需要记住的是,在Python 中缩进是有明确意义的,你必须遵循这个原则。如果你使用IDE 或者像Sublime Text 这样的编辑器,软件会帮助你设置每次使用制表符都相当于按了一定次数的空格键;如果你使用像Notepad 一样的普通文本编辑器,那么就要注意使用同样数目的空格表示同一级别的缩进(一般使用4 个空格)。
最后需要注意的是,有时候文本编辑器会使用制表符代替空格,这样程序看上去没有问题,但还是会收到一条错误信息(像“Unexpected indent in line 37”),这就会令程序员非常抓狂。尽管有这个问题,一般来说,Python 使用缩进还是会使代码更加清晰易读,因为你可以轻松地理解程序的逻辑过程。

二、数据类型

1、整数

Python可以处理任意大小的整数,当然包括负整数,在程序中的表示方法和数学上的写法一模一样,例如:1,100,-8080,0,等等。

示例

x=9
print("Output 4#: {0}".format(x))
print("Output 5#: {0}".format(3**4))
print("Output 6#: {0}".format(int(8.3)/int(2.7)))

解释
Output 4#展示了如何将一个整数(数字9)赋给变量x,然后将x打印出来。
Output 5#说明了如何得到3的4次方(等于81)并将结果打印出来。
Output 6#演示了将数值转换成整数并进行除法运算的方法,数值通过内置的int函数转换成整数,所以算式变成了8除以2,结果为4.0。

备注:
除法 (/) 始终返回float。要进行楼层除法并获得整数结果 (丢弃任何小数结果),可以使用 // 运算符; 要计算余数,可以使用 %:

>>> 4/2
2.0
>>> 4//2
2
>>> 7%3
1
>>> 7/3
2.3333333333333335

(余数,数学用语。在整数的除法中,只有能整除与不能整除两种情况。当不能整除时,就产生余数,取余数运算 a mod b = c(b不为0)表示整数a除以整数b所得余数为c。例如:7÷3 = 2…1,更专业的符号也可以写作 7÷3=2 又 1/3,或者 7 mod 3=1。)

2、浮点数

浮点数也就是小数(即带小数点的数),浮点数可以用数学写法,如1.23,3.14,-9.01,等等。
整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的(除法难道也是精确的?是的!),而浮点数运算则可能会有四舍五入的误差。

示例

print("Output 7#: {0:.3f}".format(8.3/2.7))

y=2.5*4.8
print("Output #8: {0:.1f}".format(y))

r=8/float(3)
print("Output #9: {0:.2f}".format(r))

print("Output #10=: {0:.4f}".format(8.0/3))

解释
这个示例中print语句的语法," {0:.3f}".format(floating_point_number/floating_point_number),说明了如何设置print语句中的小数位数,.3f设定了打印的输出值应该有3位小数。

3、字符串

字符串是Python 中的另一种基本数据类型。它通常是指人类可以阅读的文本,你可以这么理解。但更广泛地说,它是一个字符序列,并且字符只有在组成这个序列时才有意义。
很多商业应用中都有字符串类型的数据,比如供应商和客户的名字及地址、评价和反馈数据、事件日志和文档记录。一些对象看上去是整数,但实际上是字符串,比如邮政编码。邮政编码01111(马萨诸塞州斯普林菲尔德)和整数1111 是不一样的,你不能对邮政编码做加减乘除,所以最好在代码中将邮政编码作为字符串来处理。
字符串可以包含在单引号、双引号、3 个单引号或3 个双引号之间。

示例

print("Output #14: {0:s}".format('I\'m enjoying learning Python.'))
#解释:Output #14 和本章开头的示例非常像。它展示了一个包含在单引号之间的简单字符串。这个print 语句的结果是"I'm enjoying learning Python."。请记住,如果用双引号来包含
这个字符串的话,就不需要在"I'm" 的单引号前面使用反斜杠了。

print("Output #15: {0:s}".format("This is a long string. Without the backslash\
it would run off of the page on the right in the text editor and be very\
difficult to read and edit. By using the backslash you can split the long\
string into smaller strings on separate lines so that the whole string is easy\
to view in the text editor."))
#解释:Output #15 展示了如何使用反斜杠将一个非常长的字符串分段显示在多个行中,以使它易于理解和编辑。尽管这个字符串分布在脚本的多个行中,它还是一个字符串,并作为一个完整的字符串被打印出来。在这种将长字符串分成多行的方法中,要注意的是反斜杠必须是每行的最后一个字符。如果你意外地按了一下空格键,反斜杠后面就会出现一个看不见的空格,脚本就会抛出一个语法错误,不能正常运行。因此,使用3 个单引号或3 个双引号来创建多行字符串更稳妥一些。

print("Output #16: {0:s}".format('''You can use triple single quotes
for multi-line comment strings.'''))
print("Output #17: {0:s}".format("""You can also use triple double quotes
for multi-line comment strings."""))
#解释:Output #16 和Output #17 展示了如何使用3 个单引号和3 个双引号来创建多行字符串。这两个示例的输出如下:
Output #16: You can use triple single quotes
for multi-line comment strings.
Output #17: You can also use triple double quotes
for multi-line comment strings.

#总结:当你使用3 个单引号或3 个双引号时,不需要在前面一行的末尾加上反斜杠。还有,请注意一下Output #15 和Output #16 以及Output #17 在打印到屏幕之后的区别。Output #15 使用在行尾添加反斜杠的方式将字符串分成多行,使每行代码更短并且更易于理解,但还是作为一行长文本打印到屏幕上。与之相反,Output #16 和Output #17 使用3 个单引号和3个双引号创建多行字符串,打印到屏幕上后也是分行的。

和数值类型一样,也有很多标准模块、内置函数和操作符可以用来管理字符串。常用的操作符和函数包括+、* 和len。下面就是几个使用这些操作符处理字符串的示例:

string1 = "This is a "
string2 = "short string."
sentence = string1 + string2
print("Output #18: {0:s}".format(sentence))
#解释:Output #18 展示了如何使用+ 操作符来将两个字符串相加。+ 操作符将两个字符串按照原样相加,所以如果你想在结果字符串中留出空格的话,就必须在原字符串中加上空格。

print("Output #19: {0:s} {1:s}{2:s}".format("She is", "very "*4, "beautiful."))
#解释:Output #19 展示了如何使用* 操作符将字符串重复一定的次数。在这个示例中,结果字符串包含了字符串“very”(就是very 后面跟着一个空格)的4 个副本。

m = len(sentence)
print("Output #20: {0:d}".format(m))
#解释:Output #20 展示了如何使用内置函数len 来确定字符串中字符的数量。函数len 将空格与标点符号也计入字符串长度。所以,在Output #20 中,字符串This is a short string.
的长度是23 个字符。

#输出结果:
Output #18: This is a short string.
Output #19: She is very very very very beautiful.
Output #20: 23

这字符串是以单引号或双引号"括起来的任意文本,比如’abc’“xyz"等等。请注意,’'”“本身只是一种表示方式,不是字符串的一部分,因此,字符串’abc’只有abc这3个字符。如果本身也是一个字符,那就可以用”"括起来,比如"I’m OK"包含的字符是Im,空格,OK这6个字符。
如果字符串内部既包含又包含"怎么办?可以用转义字符\来标识,比如:

'I\'m \"OK\"!'

表示的字符串内容是:

I'm "OK"!

转义字符\可以转义很多字符,比如\n表示换行,\t表示制表符,字符\本身也要转义,所以\表示的字符就是\,可以在Python的交互式命令行用print()打印字符串看看:

>>> print('I\'m ok.')
I'm ok.
>>> print('I\'m learning\nPython.')
I'm learning
Python.
>>> print('\\\n\\')
\
\

如果字符串里面有很多字符都需要转义,就需要加很多\,为了简化,Python还允许用r’‘表示’'内部的字符串默认不转义,可以自己试试:

>>> print('\\\t\\')
\       \
>>> print(r'\\\t\\')
\\\t\\

如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用’’’…’’'的格式表示多行内容,可以自己试试:

>>> print('''line1
... line2
... line3''')
line1
line2
line3

上面是在交互式命令行内输入,注意在输入多行内容时,提示符由>>>变为…,提示你可以接着上一行输入,注意…是提示符,不是代码的一部分:
在这里插入图片描述
当输入完结束符```和括号)后,执行该语句并打印结果。如果写成程序并存为.py文件,就是:

print('''line1
line2
line3''')

多行字符串’’’…’’'还可以在前面加上r使用,

print(r'''hello,\n
world''')

4、string函数

处理字符串的一个常用标准库模块是string。在string 模块中,你可以使用多个函数来有效管理字符串。

1)split函数

使用split 函数来将一个字符串拆分成一个子字符串列表,列表中的子字符串正好可以构成原字符串。(列表是Python 中的另一种内置数据类型)

split 函数可以在括号中使用两个附加参数。第一个附加参数表示使用哪个字符进行拆分。第二个附加参数表示进行拆分的次数(例如:进行两次拆分,可以得到3 个子字符串)

string1 = "My deliverable is due in May"
string1_list1 = string1.split()
string1_list2 = string1.split(" ",2)
print("Output #21: {0}".format(string1_list1))
#解释:在Output #21 中,括号中没有附加参数,所以split 函数使用空格字符(默认值)对字符串进行拆分。因为这个字符串中有5 个空格,所以字符串被拆分成具有6 个子字符串的列表。新生成的列表为['My', 'deliverable', 'is', 'due', 'in', 'May']。

print("Output #22: FIRST PIECE:{0} SECOND PIECE:{1} THIRD PIECE:{2}"\
.format(string1_list2[0], string1_list2[1], string1_list2[2]))
#解释:Output #22 明确地在split 函数中包含了两个附加参数。第一个附加参数是" ",说明想用空格来拆分字符串。第二个附加参数是2,说明只想使用前两个空格进行拆分。因为设定了拆分两次,所以会生成一个带有3 个元素的列表。第二个附加参数会在你解析数据的时候派上用场。举例来说,你可能会解析一个日志文件,文件中包含时间戳、错误代码和由空格分隔的错误信息。在这种情况下,你应该使用前两个空格进行拆分,解析出时间戳和错误代码,但是不使用剩下的空格进行拆分,以便完整无缺地保留错误信息。

string2 = "Your,deliverable,is,due,in,June"
string2_list = string2.split(',')
print("Output #23: {0}".format(string2_list))
print("Output #24: {0} {1} {2}".format(string2_list[1], string2_list[5],\
string2_list[-1]))
#解释:在Output #23 和Output #24 中,括号中的附加参数是个逗号。在这种情况下,split 函数在出现逗号的位置拆分字符串。结果列表为['Your', 'deliverable', 'is', 'due', 'in','June']。

#输出结果:
Output #21: ['My', 'deliverable', 'is', 'due', 'in', 'May']
Output #22: FIRST PIECE:My SECOND PIECE:deliverable THIRD PIECE:is due in May
Output #23: ['Your', 'deliverable', 'is', 'due', 'in', 'June']
Output #24: deliverable June June

2)join函数

使用join 函数将列表中的子字符串组合成一个字符串。join 函数将一个参数放在join 前面,表示使用这个字符(或字符串)在子字符串之间进行组合:

print("Output #25: {0}".format(','.join(string2_list)))
#解释:在这个示例中,附加参数为一个逗号,位于圆括号中。所以join 函数将子字符串组合成一个字符串,子字符串之间为逗号。因为列表中有6 个子字符串,所以子字符串被组合成一个字符串,子字符串之间有5 个逗号。新生成的字符串是Your,deliverable,is,due,in,June。

3)strip、lstrip、rstrip函数

使用strip、lstrip 和rstrip 函数从字符串两端删除不想要的字符。这3 个函数都可以在括号中使用一个附加参数来设定要从字符串两端删除的字符(或字符串)。

第一组示例展示了如何使用lstrip、rstrip 和strip 函数分别从字符串的左侧、右侧和两侧删除空格、制表符和换行符:

string3 = " Remove unwanted characters from this string.\t\t \n"
print("Output #26: string3: {0:s}".format(string3))
string3_lstrip = string3.lstrip()
print("Output #27: lstrip: {0:s}".format(string3_lstrip))
string3_rstrip = string3.rstrip()
print("Output #28: rstrip: {0:s}".format(string3_rstrip))
string3_strip = string3.strip()
print("Output #29: strip: {0:s}".format(string3_strip))
#解释:使用lstrip、rstrip 和strip 函数分别从字符串的左侧、右侧和两侧删除空格、制表符和换行符
#string3 的左侧含有几个空格。在Output #26 中,你会看到句子前面有空白字符;在句子下面有一个空行,因为有换行符;在句子后面你看不到制表符和空格,但它们确实在那儿。Output #27、Output #28 和Output #29 分别展示了从字符串左侧、右侧和两侧删除空格、制表符和换行符的方法。{0:s} 中的s 表示传入print 语句的值应该格式化为一个字符串。

第二组示例展示了从字符串两端删除其他字符的方法,将这些字符作为strip 函数的附加参数即可。

string4 = "$$Here's another string that has unwanted characters.__---++"
print("Output #30: {0:s}".format(string4))
string4 = "$$The unwanted characters have been removed.__---++"
string4_strip = string4.strip('$_-+')
print("Output #31: {0:s}".format(string4_strip))
#解释:在这组示例中,美元符号($)、下划线(_)、短划线(-)和加号(+)需要从字符串两端删除。通过将这些字符作为附加参数,可以通知程序从字符串两端删除它们。在Output#31 中,结果字符串为The unwanted characters have been removed.。

4)replace函数

使用replace 函数将字符串中的一个或一组字符替换为另一个或另一组字符。
这个函数在括号中使用两个附加参数,第一个参数是要在字符串中查找替换
的字符或一组字符,第二个参数是要用来替换掉第一个参数的字符或一组字符。

string5 = "Let's replace the spaces in this sentence with other characters."
string5_replace = string5.replace(" ", "!@!")
print("Output #32 (with !@!): {0:s}".format(string5_replace))
#解释:Output #32 展示了如何使用replace 函数将字符串中的空格替换为!@!。结果字符串为Let's!@!replace!@!the!@!spaces !@!in!@!this!@!sentence!@!with!@!other!@!characters.。

string5_replace = string5.replace(" ", ",")
print("Output #33 (with commas): {0:s}".format(string5_replace))
#解释:Output #33 展示了如何使用逗号替换字符串中的空格。结果字符串为Let's,replace,the,
spaces,in,this,sentence,with,other,characters.。

#输出结果:
#Output #32 (with !@!): Let's!@!replace!@!the!@!spaces!@!in!@!this!@!sentence!@!with!@!other!@!characters.
#Output #33 (with commas): Let's,replace,the,spaces,in,this,sentence,with,other,characters.

5)lower、upper、capitalize函数

lower 和upper 函数分别用来将字符串中的字母转换为小写和大写。
capitalize 函数对字符串中的第一个字母应用upper 函数,对其余的字母应用lower 函数。

string6 = "Here's WHAT Happens WHEN You Use lower."
print("Output #34: {0:s}".format(string6.lower()))
string7 = "Here's what Happens when You Use UPPER."
print("Output #35: {0:s}".format(string7.upper()))
#解释:Output #34 和Output #35 是对lower 和upper 函数最直接的应用。在字符串上应用了这些函数之后,string6 中的所有字母都是小写,string7 中的所有字母都是大写。

string5 = "here's WHAT Happens WHEN you use Capitalize."
print("Output #36: {0:s}".format(string5.capitalize()))
#解释:Output #36 说明了capitalize 函数对字符串中的第一个字母应用upper 函数,对其余的字母应用lower 函数

string5_list = string5.split()
print("Output #37 (on each word):")
for word in string5_list:
 print("{0:s}".format(word.capitalize()))
#解释:Output #37 将capitalize 函数放在一个for 循环中代码for word in string5_list 的意义是这样的:“对于string5_list 这个列表中的每个元素,需要做点事情。”下面的代码print word.capitalize() 说明了对于列表中的每个元素要做的事情。这两行代码放在一起的意义就是:“对于string5_list 这个列表中的每个元素,先应用capitalize 函数,然后再打印出来。”代码执行的结果就是列表中的每个单词首字母大写,其余字母小写

Python 中还有更多管理字符串的模块和函数。和内置函数math 一样,你可以参考Python标准库(https://docs.python.org/3/library/index.html)来获取更多信息。

5、type函数

解释
Python 提供一个名为type 的函数,你可以对所有对象调用这个函数,来获得关于Python 如何处理这个对象的更多信息。如果你对一个数值变量调用这个函数,它会告诉你这个数值是整数还是浮点数,还会告诉你这个数值是否能当作字符串进行处理。函数的语法非常简单:type(varible) 会返回Python 中的数据类型。此外,因为Python 是一种“面向对象”的语言,所以你可以对Python 中所有命名对象调用type函数,不仅是变量,还有函数、语句等。如果你的代码出现了意外的错误,调用type函数可以帮助你进行错误诊断。

举例

>>>type(1)
<class 'int'>

>>>type(1.0)
<class 'float'>

>>>type('hello world')
<class 'str'>

6、标准模块math

在Python 中进行数值处理时,需要知道的非常重要的一点是,你可以使用几种标准库模块和内置函数与模块来进行常见的数学计算。你已经使用了两个内置函数来处理数值,分别是int 和float。另一个有用的标准模块是math。

Python 标准模块随着Python 基本程序一起安装在你的计算机中,但是当你新建一个脚本时,计算机只加载一些非常基本的操作(这就是Python 启动非常快的一个原因)。要想使用math 模块中的一些函数,只需在脚本开头shebang 行的下方添加from math import[function name]。例如,可以将下面的代码行添加到first_script.py 中,在shebang 行下面:

#!/usr/bin/env python3
from math import exp, log, sqrt

如果在first_script.py 中添加了这一行,你就有3 个有用的数学函数可以任意使用了。函数exp、log 和sqrt 分别表示e 的乘方、自然对数和平方根。下面是使用math 模块中的函数进行计算的几个示例:

print("Output #11: {0:.4f}".format(exp(3)))
print("Output #12: {0:.2f}".format(log(4)))
print("Output #13: {0:.1f}".format(sqrt(81)))

运算结果:

Output #11: 20.0855
Output #12: 1.39
Output #13: 9.0

这只是math 模块中一点微小的功能。Python 中还有很多有用的数学函数和模块,用于商业、科学、统计和其他应用。

三、正则表达式、模式匹配、日期

很多商业分析都依赖模式匹配,也称为正则表达式(regular expression)。举例来说,你可能需要分析一下来自某个州(比如马里兰州)的所有订单。在这种情况下,你需要识别的模式就是Maryland 这个单词。同样,你还可能需要分析一下来自某个供应商(比如StaplesRUs)的商品质量,那么你要识别的模式就是StaplesRUs。

1、re 模块

Python 包含了re 模块,它提供了在文本中搜索特定模式(也就是正则表达式)的强大功能。要在脚本中使用re 模块提供的功能,需要在脚本上方加入import re 这行代码,放在上一个import 语句之后。现在first_script.py 的上方应该是这样的:

#!/usr/bin/env python3
from math import exp, log, sqrt
import re

通过导入re 模块,你可以使用一大波函数和元字符来创建和搜索任意复杂的模式。元字符(metacharacter)是正则表达式中具有特殊意义的字符。每个元字符都有特殊意义,它们使正则表达式能够匹配特定的字符串。常用的元字符包括|、()、[]、.、*、+、?、^、$ 和(?P)。如果你在正则表达式中见到这些字符,要知道程序不是要搜索这些字符本身,而是要搜索它们描述的东西。你可以在Python 标准库中的“Regular ExpressionOperations”一节中获得关于元字符的更多信息(https://docs.python.org/3/library/re.html)。

re 模块中还包括很多有用的函数,用于创建和搜索特定的模式(本节要介绍的函数包括re.compile、re.search、re.sub、re.ignorecase 和re.I)。

1)示例:计算字符串中模式出现的次数

# 计算字符串中模式出现的次数
string = "The quick brown fox jumps over the lazy dog."
string_list = string.split()
pattern = re.compile(r"The", re.I)
count = 0
for word in string_list:
    if pattern.search(word):
        count += 1
print("Output #38: {0:d}".format(count))
解释:
第一行将字符串变量string 赋值为The quick brown fox jumps over the lazy dog.。下
一行将字符串拆分列表,列表中的每个元素都是一个单词。
再下一行使用re.compile 和re.I 函数以及用r 表示的原始字符串,创建一个名为pattern
的正则表达式。re.compile 函数将文本形式的模式编译成为编译后的正则表达式。正则
表达式不是必须编译的,但是编译是个好习惯,因为这样可以显著地提高程序运行速度。
re.I 函数确保模式是不区分大小写的,能同时在字符串中匹配“The”和“the”。原始字
符串标志r 可以确保Python 不处理字符串中的转义字符,比如\、\t 或\n。这样在进行
模式匹配时,字符串中的转义字符和正则表达式中的元字符就不会有意外的冲突。在上面
的例子中,字符串中没有转义字符,所以r 不是必需的,但是在正则表达式中使用原始字
符串标志是一个好习惯。接下来的一行代码创建了一个变量count 来保存字符串中模式出
现的次数,初始值设为0。
再下一行是个for 循环语句,在列表变量string_list 的各个元素之间进行迭代。它取出的
第一个元素是“The”这个单词,取出的第二个元素是“quick”这个单词,以此类推,直
到取出列表中所有的单词。接下来的一行使用re.search 函数将列表中的每个单词与正则表
达式进行比较。如果这个单词与正则表达式相匹配,函数就返回True,否则就返回None 或
False。所以if 语句的意义就是:如果单词与正则表达式匹配,那么count 的值就加1。
最后,print 语句打印出正则表达式在字符串中找到模式“The”(不区分大小写)的次数,
在本例中,找到了两次。

当你逐渐熟悉了正则表达式后,使用它们得到你想要的结果就简单了。要想愉快地学习正则表达式,你可以参考一下Google 研发总监Peter Norvig 的工作,他创建了一个正则表达式,这个表达式可以匹配美国总统的名字,并且可以筛掉失败的总统竞选人,网址为https://www.oreilly.com/learning/regexgolf-with-peter-norvig。

2)示例:在字符串中每次找到模式时将其打印出来

# 在字符串中每次找到模式时将其打印出来
string = "The quick brown fox jumps over the lazy dog."
string_list = string.split()
pattern = re.compile(r"(?P<match_word>The)", re.I)
print("Output #39:")
for word in string_list:
    if pattern.search(word):
        print("{:s}".format(pattern.search(word).group('match_word')))
解释:
第二个示例与第一个示例的区别在于,这个示例是想在屏幕上打印出每个匹配的字符串,
而不是匹配的次数。要想得到匹配的字符串,将它们打印到屏幕上或存储在文件中,需要
使用(?P<name>) 元字符和group 函数。这个示例中的多数代码和前一个示例中讨论过的代
码是一样的,所以这里重点解释新的部分。
第一个新代码片段是(?P<name>),这是一个出现在re.compile 函数中的元字符。这个元字
符使匹配的字符串可以在后面的程序中通过组名符号<name> 来引用。在这个示例中,这个
组被称为<match_word>。
最后一个新代码片段出现在if 语句中。这个代码片段的意义是:“如果结果为True(也就
是说,如果单词与模式匹配),那么就在search 函数返回的数据结构中找出match_word 组
中的值,并把这些值打印在屏幕上。

3)示例:使用字母“a”替换字符串中的单词“the”(re.compile)

string = "The quick brown fox jumps over the lazy dog."
string_to_find = r"The"
pattern = re.compile(string_to_find, re.I)
print("Output #40: {:s}".format(pattern.sub("a", string)))
#解释:展示了如何使用re.sub 函数来在文本中用一种模式替换另一种模式。
第一个新代码片段将正则表达式赋给变量pattern, 于是这个变量可以被传入到
re.compile 函数中。解释一下,在调用re.compile 函数之前,将正则表达式赋给变量不是
必需的;但是,如果你的正则表达式特别长而且复杂的话,将它赋给一个变量然后将变量
传给re.compile 函数这种做法可以使你的代码更利于理解。
最后一个新代码片段在最后一行。这个代码片段使用re.sub 函数以不区分大小写的方式在
变量string 中寻找模式,然后将发现的每个模式替换成字母a。这次替换的最终结果是a
quick brown fox jumps over a lazy dog.。

更多关于正则表达式函数的信息可以在Python 标准库(https://docs.python.org/3/library/index.html)中找到,也可以参考Michael Fitzgerald 的著作《学习正则表达式》。

2、日期

日期在大多数商业应用中都是必不可少的。你需要知道一个事件在何时发生,距离这件事发生还有多少时间,或者几个事件之间的时间间隔。因为日期是很多应用的核心,也因为日期是一种非常不寻常的数据,在处理时经常要乘以60 或24,也有“差不多30 分钟”和“几乎是365 天和一个季度”这样的说法,所以在Python 中对日期有特殊的处理方式。

Python 中包含了datetime 模块,它提供了非常强大的功能来处理日期和时间。要想在脚本中使用datetime 模块提供的功能,需要在脚本上方加入from datetime import date,time, datetime, timedelta,放在之前的import 语句下面。现在first_script.py 的上方应该是这样的:

#!/usr/bin/env python3
from math import exp, log, sqrt
import re
from datetime import date, time, datetime, timedelta

导入datetime 模块之后,就有各式各样的日期时间对象和函数供你随意使用了。常用的对象和函数包括today、year、month、day、timedelta、strftime 和strptime。这些函数可以捕获具体的日期数据(例如:年、月、日)、进行日期和时间的加减运算、创建特定形式的日期字符串以及根据日期字符串创建datetime 对象。下面是使用这些datetime 对象和函数的几个示例。

1)示例:打印出今天的日期形式(date/datetime)

# 打印出今天的日期形式,以及年、月、日
today = date.today()
print("Output #41: today: {0!s}".format(today))
print("Output #42: {0!s}".format(today.year))
print("Output #43: {0!s}".format(today.month))
print("Output #44: {0!s}".format(today.day))
current_datetime = datetime.today()
print("Output #45: {0!s}".format(current_datetime))

通过使用date.today(),你可以创建一个date 对象,其中包含了年、月、日,但不包含时间元素,比如时、分、秒。相反,通过datetime.today() 创建的对象则包含时间元素。
{0!s} 中的!s 表示传入到print 语句中的值应该格式化为字符串,尽管它是个数值型数据。最后,你可以使用year、month 和day 来捕获具体的日期元素。

2)示例:使用timedelta计算一个新日期

today = date.today()
one_day = timedelta(days=-1)
yesterday = today + one_day
print("Output #46: yesterday: {0!s}".format(yesterday))
eight_hours = timedelta(hours=-8)
print("Output #47: {0!s} {1!s}".format(eight_hours.days, eight_hours.seconds))
解释:
在这个示例中,使用timedelta 函数从今天减去了1 天。当然,还可以在括号中使用
days=10、hours=-8 或者weeks=2 来创建变量,分别表示未来10 天、以前8 个小时或者未
来2 个星期。
在使用timedelta 时需要注意的一点是,它将括号中的时间差以天、秒和毫秒的形式存
储,然后将数值规范化后得到一个唯一的值。这说明分钟、小时和星期会被分别转换成60
秒、3600 秒和7 天,然后规范化,就是生成天、秒和毫秒“列”(类似于小学数学中的个
位、十位 等等)。举例来说,hours=-8 的输出是(-1 days, 57,600 seconds),不是更简单
的(-28,800 seconds)。是这样计算的:86 400 秒(3600 秒每小时*24 小时每天)-28 800
秒(3600 秒每小时*8 小时)= 57 600 秒。正如你所见,对负值的规范化乍看上去很令人
吃惊,特别是在进行取整和舍入时。

3)示例:从一个date 对象中减去另一个

# 计算出两个日期之间的天数
date_diff = today - yesterday
print("Output #48: {0!s}".format(date_diff))
print("Output #49: {0!s}".format(str(date_diff).split()[0]))
解释:
在某些情况下,你可能只需要结果中的数值部分。举例来说,在这个示例中你只需要数值
1。从结果中得到这个数值的一种方法是使用前面已经讨论过的字符串函数。str 函数可以
将结果转换成字符串;split 函数可以使用空白字符将字符串拆分,并使每个子字符串成
为列表的一个元素;[0] 表示“取出列表中的第一个元素”,在本例中就是数值1。在1.4.5
节中还会看到[0] 这种语法,因为它是列表索引,可以用来从列表中取出特定的元素。

4)示例:使用strftime 函数根据一个date 对象来创建具有特定格式的字符

# 根据一个日期对象创建具有特定格式的字符串
print("Output #50: {:s}".format(today.strftime('%m/%d/%Y')))
print("Output #51: {:s}".format(today.strftime('%b %d, %Y')))
print("Output #52: {:s}".format(today.strftime('%Y-%m-%d')))
print("Output #53: {:s}".format(today.strftime('%B %d, %Y')))
输出结果:
Output #50: 10/20/2021
Output #51: Oct 20, 2021
Output #52: 2021-10-20
Output #53: October 20, 2021

这4 个示例说明了如何使用格式符来创建不同格式的日期字符串,格式符包括%Y、%B、%b、%m 和%d。你可以在Python 标准库(https://docs.python.org/3/library/datetime.html)的“datetime—Basic date and time types”一节中找到datetime 模块使用的其他格式符。

5)示例:使用strptime 函数根据具有特定形式的日期字符串来创建datetime对象。

today = date.today()
# 根据一个表示日期的字符串
# 创建一个带有特殊格式的datetime对象
date1 = today.strftime('%m/%d/%Y')
date2 = today.strftime('%b %d, %Y')
date3 = today.strftime('%Y-%m-%d')
date4 = today.strftime('%B %d, %Y')
# 基于4个具有不同日期格式的字符串
# 创建2个datetime对象和2个date对象
print("Output #54: {!s}".format(datetime.strptime(date1, '%m/%d/%Y')))
print("Output #55: {!s}".format(datetime.strptime(date2, '%b %d, %Y')))
# 仅显示日期部分
print("Output #56: {!s}".format(datetime.date(datetime.strptime\
(date3, '%Y-%m-%d'))))
print("Output #57: {!s}".format(datetime.date(datetime.strptime\
(date4, '%B %d, %Y'))))
输出结果:
Output #54: 2021-10-20 00:00:00
Output #55: 2021-10-20 00:00:00
Output #56: 2021-10-20
Output #57: 2021-10-20
解释:
第五组示例展示了如何使用strptime 函数根据具有特定形式的日期字符串来创建datetime
对象。在这个示例中,date1、date2、date3 和date4 是字符串变量,以不同的形式表示今
天。前两个print 语句展示了将前两个字符串变量date1 和date2 转换成datetime 对象的
结果。要使它们正确工作,strptime 函数中使用的形式需要和传入函数的字符串变量的形
式相匹配。这两个print 语句的输出结果是个datetime 对象,2021-10-20 00:00:00。
有些时候,你可能只对datetime 对象中的日期部分感兴趣。在这种情况下,你可以使用
嵌套的函数(在最后两个print 语句中,是date 和strptime)将日期字符串变量转换为
datetime 对象,然后仅返回datetime 对象的日期部分。这些print 语句的结果是2014-01-
28。当然,你不需要立刻打印出这个值。你可以将这个日期赋给一个新变量,然后使用这
个变量进行计算,洞悉随着时间产生的业务数据。

四、列表

很多商业分析中都使用列表。你会维护各种客户列表、产品列表、资产列表、销售量列表,等等。但是Python 中的列表(对象的可排序集合)更加灵活!上面那些列表中包含的都是相似的对象(例如:包含客户姓名的字符串或代表销售量的浮点数),但是Python 中的列表可不止这么简单。它可以包含数值、字符串、其他列表、元组和字典(本章稍后介绍)的任意组合。因为列表在商业应用中使用广泛、灵活性高、作用突出,所以掌握如何在Python 中操作列表是极其重要的。
如你所愿,Python 提供了很多有用的函数和操作符来管理列表。以下演示了最常用的和最有效的列表函数和操作符的使用方法。

1、创建列表

# 使用方括号创建一个列表
# 用len()计算列表中元素的数量
# 用max()和min()找出最大值和最小值
# 用count()计算出列表中某个值出现的次数
a_list = [1, 2, 3]
print("Output #58: {}".format(a_list))
print("Output #59: a_list has {} elements.".format(len(a_list)))
print("Output #60: the maximum value in a_list is {}.".format(max(a_list)))
print("Output #61: the minimum value in a_list is {}.".format(min(a_list)))
another_list = ['printer', 5, ['star', 'circle', 9]]
print("Output #62: {}".format(another_list))
print("Output #63: another_list also has {} elements.".format\
(len(another_list)))
print("Output #64: 5 is in another_list {} time.".format(another_list.count(5)))
输出结果:
Output #58: [1, 2, 3]
Output #59: a_list has 3 elements.
Output #60: the maximum value in a_list is 3.
Output #61: the minimum value in a_list is 1.
Output #62: ['printer', 5, ['star', 'circle', 9]]
Output #63: another_list also has 3 elements.
Output #64: 5 is in another_list 1 time.
解释:
这个示例展示了如何创建两个简单列表,a_list 和another_list。将元素放在方括号之间
就可以创建列表。a_list 包含数值1、2 和3。another_list 包含一个字符串printer、一
个数值5 以及一个包含了两个字符串和一个数值的列表。
这个示例还展示了如何使用4 种列表函数:len、min、max 和count。len 返回列表中元素
的个数。min 和max 分别返回列表中的最小值和最大值。count 返回列表中某个元素出现的次数。

2、索引值

# 使用索引值访问列表中的特定元素
# [0]是第1个元素,[-1]是最后一个元素
print("Output #65: {}".format(a_list[0]))
print("Output #66: {}".format(a_list[1]))
print("Output #67: {}".format(a_list[2]))
print("Output #68: {}".format(a_list[-1]))
print("Output #69: {}".format(a_list[-2]))
print("Output #70: {}".format(a_list[-3]))
print("Output #71: {}".format(another_list[2]))
print("Output #72: {}".format(another_list[-1]))
输出结果:
Output #65: 1
Output #66: 2
Output #67: 3
Output #68: 3
Output #69: 2
Output #70: 1
Output #71: ['star', 'circle', 9]
Output #72: ['star', 'circle', 9]
解释:
这个示例说明了如何使用索引值来引用列表中的特定元素。列表索引值从0 开始,所以你
可以通过在列表名称后面的方括号中放入0 来引用列表中的第一个元素。示例中的第一个
print 语句print a_list[0] 打印出a_list 中的第一个元素,即数值1。a_list[1] 引用的是
列表中的第二个元素,a_list[2] 引用的是列表中的第三个元素,以此类推直到列表结束。
这个示例还说明了你可以使用负索引值从列表尾部引用列表元素。列表尾部的索引值从
-1 开始,所以你可以通过在列表名称后面的方括号中放入-1 来引用列表中的最后一个元
素。第四个print 语句print a_list[-1] 打印出a_list 中的最后一个元素,即数值3。a_
list[-2] 打印出列表中倒数第二个元素,a_list[-3] 打印出列表中倒数第三个元素,以此
类推直到列表开头。

3、列表切片

# 使用列表切片访问列表元素的一个子集
# 从开头开始切片,可以省略第1个索引值
# 一直切片到末尾,可以省略第2个索引值
print("Output #73: {}".format(a_list[0:2]))
print("Output #74: {}".format(another_list[:2]))
print("Output #75: {}".format(a_list[1:3]))
print("Output #76: {}".format(another_list[1:]))
输出结果:
Output #73: [1, 2]
Output #74: ['printer', 5]
Output #75: [2, 3]
Output #76: [5, ['star', 'circle', 9]]
解释:
这个示例展示了如何使用列表切片引用列表元素的一个子集。在列表名称后面的方括号中
放入由冒号隔开的两个索引,就可以创建一个列表切片。列表切片引用的是列表中从第一
个索引值到第二个索引值的前一个元素。举例来说,第一个print 语句print a_list[0:2]
的意义就是:“打印出a_list 中索引值为0 和1 的元素”。这个print 语句打印出[1, 2],
因为这是列表中的前两个元素。
这个示例还说明,如果从列表开头开始切片,就可以省略第一个索引值,如果一直切片到列表末尾,就可以省略第二个索引值。举例来说,最后一个print 语句print another_list[1:]
的意义就是:“从列表中第二个元素开始,打印出another_list 中其余所有的元素。”这个
print 语句打印出[5, ['star', 'circle', 9]],因为这是列表中最后两个元素。

4、列表复制

# 使用[:]复制一个列表
a_new_list = a_list[:]
print("Output #77: {}".format(a_new_list))
输出结果
Output #77: [1, 2, 3]
解释:
这个示例展示了如何复制一个列表。如果你需要对列表进行某种操作,比如添加或删除元
素,或对列表进行排序,但你还希望原始列表保持不变,这时这个功能就非常重要了。要
复制一个列表,在列表名称后面的方括号中放入一个冒号,然后将其赋给一个新的变量即
可。在这个示例中,a_new_list 是a_list 的一个完美复制,所以你可以对a_new_list 添
加或删除元素,也可以对a_new_list 进行排序,而不会影响a_list。

5、列表连接

# 使用+将两个或更多个列表连接起来
a_longer_list = a_list + another_list
print("Output #78: {}".format(a_longer_list))
输出结果:
Output #78: [1, 2, 3, 'printer', 5, ['star', 'circle', 9]]
解释:
这个示例展示了如何将两个或更多个列表连接在一起。当你必须分别引用多个具有相似信
息的列表,但希望将它们组合起来进行分析的时候,这个功能就非常重要了。举例来说,
由于数据存储方式的原因,你可能需要生成一个销售量列表,其中包括来自于一个数据源
和另一个数据源的销售量列表。要将两个销售量列表连接在一起进行分析,可以将两个列
表名称用+ 操作符相加,然后赋给一个新变量。在这个示例中,a_long_list 包含a_list
和another_list 中的所有元素,将它们连接在一起形成一个更长的列表。

6、使用in和not in

# 使用in和not in来检查列表中是否有特定元素
a = 2 in a_list
print("Output #79: {}".format(a))
if 2 in a_list:
    print("Output #80: 2 is in {}.".format(a_list))
b = 6 not in a_list
print("Output #81: {}".format(b))
if 6 not in a_list:
    print("Output #82: 6 is not in {}.".format(a_list))
输出结果:
Output #79: True
Output #80: 2 is in [1, 2, 3].
Output #81: True
Output #82: 6 is not in [1, 2, 3].
解释:
这个示例展示了如何使用in 和not in 来检查列表中是否存在某个特定元素。这些表达式
的结果是True 或False,取决于表达式为真还是假。这个功能在商业应用中非常重要,因
为你可以使用它在程序中添加有意义的业务逻辑。举例来说,它们经常应用于if 语句,
比如“如果SupplierY 在SupplierList 中,那么做些什么事,否则做些其他事。”本章后
面的内容中将会介绍更多if 语句和其他控制流表达式的示例。

7、追加、删除和弹出元素

# 使用append()向列表末尾追加一个新元素
# 使用remove()从列表中删除一个特定元素
# 使用pop()从列表末尾删除一个元素
a_list.append(4)
a_list.append(5)
a_list.append(6)
print("Output #83: {}".format(a_list))
a_list.remove(5)
print("Output #84: {}".format(a_list))
a_list.pop()
a_list.pop()
print("Output #85: {}".format(a_list))
输出结果:
Output #83: [1, 2, 3, 4, 5, 6]
Output #84: [1, 2, 3, 4, 6]
Output #85: [1, 2, 3]
解释:
这个示例展示了向列表中添加元素和从列表中删除元素的方法。append 方法将一个新元素
追加到列表末尾。你可以使用该方法按照具体业务规则创建列表。举例来说,要建立一个
关于CustomerX 的采购量的列表,可以先创建一个名为CustomerX 的空列表,然后在记录
所有客户采购量的主列表中扫描,如果在主列表中发现了CustomerX,就可以将这个采购
量数据追加到列表CustomerX 中。
remove 方法可以删除列表中的任意元素。你可以使用该方法删除列表中的错误元素和输入
错误,还可以按照具体业务规则删除列表中的元素。在这个示例中,remove 方法从a_list
中删除了数值5。
pop 方法删除列表中的最后一个元素。和remove 方法相似,你可以使用pop 方法删除列表
末尾的错误元素和输入错误,还可以按照具体的业务规则从列表末尾删除元素。在这个示
例中,对pop 方法的两次调用从a_list 中分别删除了数值6 和数值4。

8、列表反转

# 使用reverse()原地反转一个列表会修改原列表
# 要想反转列表同时又不修改原列表,可以先复制列表
a_list.reverse()
print("Output #86: {}".format(a_list))
a_list.reverse()
print("Output #87: {}".format(a_list))
输出结果:
Output #86: [3, 2, 1]
Output #87: [1, 2, 3]
解释:
这个示例展示了使用reverse 函数以in-place 方式对列表进行反转的方法(原地反转)。
“in-place”表示反转操作将原列表修改为顺序颠倒的新列表。举例来说,示例第一次调用
reverse 函数将a_list 改变为[3, 2, 1]。第二次调用reverse 函数则将a_list 恢复到初
始顺序。要想使用列表的反转形式而不修改原列表,可以先复制列表,然后对列表副本进
行reverse 操作。

9、列表排序

# 使用sort()对列表进行原地排序会修改原列表
# 要想对列表进行排序同时又不修改原列表,可以先复制列表
unordered_list = [3, 5, 1, 7, 2, 8, 4, 9, 0, 6]
print("Output #88: {}".format(unordered_list))
list_copy = unordered_list[:]
list_copy.sort()
print("Output #89: {}".format(list_copy))
print("Output #90: {}".format(unordered_list))
输出结果:
Output #88: [3, 5, 1, 7, 2, 8, 4, 9, 0, 6]
Output #89: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Output #90: [3, 5, 1, 7, 2, 8, 4, 9, 0, 6]
解释:
这个示例展示了使用sort 函数以in-place 方式对列表进行排序的方法。和reverse 函数一
样,这种原地排序将原列表修改为排好顺序的新列表。要想使用排好顺序的列表而不修改
原列表,可以先复制列表,然后对列表副本进行sort 操作。

10、sorted列表集合排序函数

# 使用sorted()对一个列表集合按照列表中某个位置的元素进行排序
my_lists = [[1,2,3,4], [4,3,2,1], [2,4,1,3]]
my_lists_sorted_by_index_3 = sorted(my_lists, key=lambda index_value:\
index_value[3])
print("Output #91: {}".format(my_lists_sorted_by_index_3))
输出结果:
Output #91: [[4, 3, 2, 1], [2, 4, 1, 3], [1, 2, 3, 4]]
解释:
这个示例展示了如何使用sorted 函数以及关键字函数,对一个列表集合按照每个列表中特
定索引位置的值进行排序。关键字函数设置用于列表排序的关键字。在这个示例中,关键
字是一个lambda 函数,表示使用索引位置为3 的值(也就是列表中的第四个元素)对列表
进行排序。(后续章节将会对lambda 函数做更多讨论。)使用每个列表中的第四个元素作为排序关键字,应用sorted 函数之后,第二个列表[4, 3, 2, 1] 成为了第一个列表,第三
个列表[2, 4, 1, 3] 成为了第二个列表,第一个列表[1, 2, 3, 4] 成为了第三个列表。另
外,你应该知道sorted 函数的排序与sort 函数的in-place 原地排序方式不同,sort 函数
改变了原列表的元素顺序,sorted 函数则返回一个新的排好序的列表,并不改变原列表的
元素顺序。

11、operator 标准模块:以使用多个关键字对列表、元组和字典进行排序

例使用operator 标准模块,这个模块提供的功能可以使用多个关键字对列
表、元组和字典进行排序。为了在脚本中使用operator 模块中的itemgetter 函数,在脚本上方添加from operator import itemgetter:

#!/usr/bin/env python3
from math import exp, log, sqrt
import re
from datetime import date, time, datetime, timedelta
from operator import itemgetter

导入operator 模块中的itemgetter 函数后,你可以使用每个列表中多个索引位置的值对列表集合进行排序:

# 使用itemgetter()对一个列表集合按照两个索引位置来排序
my_lists = [[123,2,2,444], [22,6,6,444], [354,4,4,678], [236,5,5,678], \
[578,1,1,290], [461,1,1,290]]
my_lists_sorted_by_index_3_and_0 = sorted(my_lists, key=itemgetter(3,0))
print("Output #92: {}".format(my_lists_sorted_by_index_3_and_0))
输出结果:
Output #92: [[461, 1, 1, 290], [578, 1, 1, 290], [22, 6, 6, 444], [123, 2, 2, 444], [236, 5, 5, 678], [354, 4, 4, 678]]
解释:
这个示例展示了如何使用sorted() 函数和itemgetter 函数按照每个列表中多个索引位置
的值对列表集合进行排序。关键字函数设置用于列表排序的关键字。在这个示例中,关键
字是包含两个索引值(3 和0)的itemgetter 函数。这个语句的意义是:“先按照索引位置
3 中的值对列表进行排序,然后,在这个排序基础上,按照索引位置0 中的值对列表进一
步排序。

这种通过多个元素对列表和其他数据容器进行排序的方法非常重要,因为你经常需要按照多个值对数据进行排序。例如,如果要处理每天销售交易数据,你需要先按照日期再按照每天的交易量大小进行排序。或者,在处理供应商数据时,你会先按照供应商姓名再按照每个供应商的供货发票日期来排序。sorted 函数和itemgetter 函数可以实现这样的功能。如果想获得列表函数的更多信息,可以参考Python 标准库(https://docs.python.org/3/
library/index.html)。

五、元祖

元组除了不能被修改之外,其余特点与列表非常相似。正因为元组不能被修改,所以没有元组修改函数。你可能会感到奇怪,为什么要设计这两种如此相似的数据结构。这是因为元组具有可修改的列表无法实现的重要作用,例如作为字典键值。元组不如列表使用广泛,所以这里只是简略地介绍一下。

1、创建元组

# 使用圆括号创建元组
my_tuple = ('x', 'y', 'z')
print("Output #93: {}".format(my_tuple))
print("Output #94: my_tuple has {} elements".format(len(my_tuple)))
print("Output #95: {}".format(my_tuple[1]))
longer_tuple = my_tuple + my_tuple
print("Output #96: {}".format(longer_tuple))
输出结果:
Output #93: ('x', 'y', 'z')
Output #94: my_tuple has 3 elements
Output #95: y
Output #96: ('x', 'y', 'z', 'x', 'y', 'z')
解释:
这个示例展示了创建元组的方法。将元素放在括号中间,就可以创建一个元组。此示例还
说明,前面讨论过的很多应用于列表的函数和操作符,也同样适用于元组。例如,len 函
数返回元组中元素的个数,元组索引和元组切片可以引用元组中特定的元素,+ 操作符可
以连接多个元组。

2、元组解包

# 使用赋值操作符左侧的变量对元组进行解包
one, two, three = my_tuple
print("Output #97: {0} {1} {2}".format(one, two, three))
var1 = 'red'
var2 = 'robin'
print("Output #98: {} {}".format(var1, var2))
# 在变量之间交换彼此的值
var1, var2 = var2, var1
print("Output #99: {} {}".format(var1, var2))
输出结果:
Output #97: x y z
Output #98: red robin
Output #99: robin red
解释:
这个示例展示了元组的一个很有意思的操作——解包。可以将元组中的元素解包成为变
量,在赋值操作符的左侧放上相应的变量就可以了。在这个示例中,字符串x、y 和z 被
解包成为变量one、two 和three 的值。这个功能可以用来在变量之间交换变量值。在示例
的最后一部分,var2 的值被赋给var1,var1 的值被赋给var2。Python 会同时对元组的各
个部分求值。这样,red robin 变成了robin red。

3、元组转换成列表(及列表转换成元组)

# 将元组转换成列表,列表转换成元组
my_list = [1, 2, 3]
my_tuple = ('x', 'y', 'z')
print("Output #100: {}".format(tuple(my_list)))
print("Output #101: {}".format(list(my_tuple)))
输出结果:
Output #100: (1, 2, 3)
Output #101: ['x', 'y', 'z']
解释:
最后,可以将元组转换成列表,也可以将列表转换成元组。这个功能和可以将一个元素转
换成字符串的str 函数很相似。要将一个列表转换成元组,将列表名称放在tuple() 函数
中即可。同样,要将一个元组转换成列表,将元组名称放在list() 函数中即可。

如果想获得元组的更多信息,可以参考Python 标准库(https://docs.python.org/3/library/
index.html)。

六、字典

Python 中的字典本质上是包含各种带有唯一标识符的成对信息的列表。和列表一样,字典也广泛应用于各种商业分析。在商业分析中,可以用字典表示客户(以客户编码为键值),也可以用字典表示产品(以序列号或产品编号为键值),还可以用字典表示资产、销售量等。
在Python 中,这样的数据结构称为字典,在其他编程语言中则称为关联数组、键- 值存储和散列值。在商业分析中,列表和字典都是非常重要的数据结构,但是它们之间还存在着重要的区别,要想有效地使用字典,必须清楚这些区别。

1、字典和列表的区别

1)在列表中,你可以使用被称为索引或索引值的连续整数来引用某个列表值。在字典中,要引用一个字典值,则可以使用整数、字符串或其他Python 对象,这些统称为字典键。在唯一键值比连续整数更能反映出变量值含义的情况下,这个特点使字典比列表更实用。
2)在列表中,列表值是隐式排序的,因为索引是连续整数。在字典中,字典值则没有排序,因为索引不仅仅只是数值。你可以为字典中的项目定义排序操作,但是字典确实没有内置排序。
3)在列表中,为一个不存在的位置(索引)赋值是非法的。在字典中,则可以在必要的时候创建新的位置(键)。
4)因为没有排序,所以当你进行搜索或添加新值时,字典的响应时间更快(当你插入一个新项目时,计算机不需要重新分配索引值)。当处理的数据越来越多时,这是一个重要的考虑因素。

2、创建字典

# 使用花括号创建字典
# 用冒号分隔键-值对
# 用len()计算出字典中键-值对的数量
empty_dict = { }
a_dict = {'one':1, 'two':2, 'three':3}
print("Output #102: {}".format(a_dict))
print("Output #103: a_dict has {!s} elements".format(len(a_dict)))
another_dict = {'x':'printer', 'y':5, 'z':['star', 'circle', 9]}
print("Output #104: {}".format(another_dict))
print("Output #105: another_dict also has {!s} elements"\
.format(len(another_dict)))
输出结果:
Output #102: {'one': 1, 'two': 2, 'three': 3}
Output #103: a_dict has 3 elements
Output #104: {'x': 'printer', 'y': 5, 'z': ['star', 'circle', 9]}
Output #105: another_dict also has 3 elements
解释:这个示例展示了创建字典的方法。要创建一个空字典,将字典名称放在等号左侧,在等号右侧放上一对花括号即可。
示例中的第二个字典a_dict 演示了向字典中添加键和值的一种方法。a_dict 说明键和值是用冒号隔开的,花括号之间的键-值对则是用逗号隔开的。字典键是用单引号或双引号
围住的字符串,字典值可以是字符串、数值、列表、其他字典或其他Python 对象。在a_
dict 中,字典值是整数,但是在another_dict 中,字典值是字符串、数值和列表。最后,
这个示例说明了len 函数返回的是字典中键-值对的个数。

3、引用字典中的值

# 使用键来引用字典中特定的值
print("Output #106: {}".format(a_dict['two']))
print("Output #107: {}".format(another_dict['z']))
输出结果:
Output #106: 2
Output #107: ['star', 'circle', 9]
解释:
要引用字典中一个特定的值,需要使用字典名称、一对方括号和一个特定的键值(一个字
符串)。在这个示例中,a_dict['two'] 的结果是整数2,another_dict['z'] 的结果是列表
['star', 'circle', 9]。

4、复制

# 使用copy()复制一个字典
a_new_dict = a_dict.copy()
print("Output #108: {}".format(a_new_dict))
输出结果:
Output #108: {'one': 1, 'two': 2, 'three': 3}
解释:
要复制一个字典,先在字典名称后面加上copy 函数,然后将这个表达式赋给一个新的字典
即可。在这个示例中,a_new_dict 是字典a_dict 的一个副本。

5、键、值和项目

# 使用keys()、values()和items()
# 分别引用字典中的键、值和键-值对
print("Output #109: {}".format(a_dict.keys()))
a_dict_keys = a_dict.keys()
print("Output #110: {}".format(a_dict_keys))
print("Output #111: {}".format(a_dict.values()))
print("Output #112: {}".format(a_dict.items()))
输出结果:
Output #109: dict_keys(['one', 'two', 'three'])
Output #110: dict_keys(['one', 'two', 'three'])
Output #111: dict_values([1, 2, 3])
Output #112: dict_items([('one', 1), ('two', 2), ('three', 3)])
解释:
要引用字典的键值,在字典名称后面加上keys 函数即可。这个表达式的结果是包含字典键
值的一个列表。要引用字典值,在字典名称后面加上values 函数即可。这个表达式的结果
是包含字典值的一个列表。
要想同时引用字典的键和值,在字典名称后面加上items 函数即可。结果是一个列表,其
中包含的是键-值对形式的元组。例如,a_dict.items() 的结果是[('three', 3), ('two',
2), ('one', 1)]。

6、使用in、not in和get

if 'y' in another_dict:
    print("Output #114: y is a key in another_dict: {}."\
.format(another_dict.keys()))
if 'c' not in another_dict:
    print("Output #115: c is not a key in another_dict: {}."\
.format(another_dict.keys()))
print("Output #116: {!s}".format(a_dict.get('three')))
print("Output #117: {!s}".format(a_dict.get('four')))
print("Output #118: {!s}".format(a_dict.get('four', 'Not in dict')))
输出结果:
Output #114: y is a key in another_dict: dict_keys(['x', 'y', 'z']).
Output #115: c is not a key in another_dict: dict_keys(['x', 'y', 'z']).
Output #116: 3
Output #117: None
Output #118: Not in dict
解释:
这个示例展示了测试字典中是否存在某个键值的两种方法。第一种方法是使用if 语句、in
或not in 以及字典名称。使用in、if 语句来测试y 是否是another_dict 中的一个键值。
如果语句结果为真(也就是说如果y 是another_dict 中的一个键值),那么就执行print 语
句;否则,就不执行。这种if in 和if not in 语句经常用于测试是否存在某个键值,和其
他语句一起使用时,还可以为字典添加新的键值。
第二种测试具体键值的方式是使用get 函数,这个函数也可以按照键值取得相应的字典
值。和前一种测试键值的方式不同,如果字典中存在这个键,get 函数就返回键值对应的
字典值;如果字典中不存在这个键,则返回None。此外,get 函数还可以使用第二个参数,表示如果字典中不存在键值时函数的返回值。通过这种方式,如果字典中不存在该键值,可以返回除None 之外的一些其他内容。

你应该注意到if 语句后面的行是缩进的。Python 使用缩进来表示一个语句是否属于一个逻辑上的“块”,也就是说,如果if 语句判断为True,那么if 语句后面所有缩进的代码都要被执行,然后Python 解释器再继续下一步工作。你会发现这种缩进还会在随后讨论的其他逻辑块中出现,现在你需要记住的是,在Python 中缩进是有明确意义的,你必须遵循这个原则。如果你使用IDE 或者像Sublime Text 这样的编辑器,软件会帮助你设置每次使用制表符都相当于按了一定次数的空格键;如果你使用像Notepad 一样的普通文本编辑器,那么就要注意使用同样数目的空格表示同一级别的缩进(一般使用4 个空格)。
最后需要注意的是,有时候文本编辑器会使用制表符代替空格,这样程序看上去没有问题,但还是会收到一条错误信息(像“Unexpected indent in line 37”),这就会令程序员非常抓狂。尽管有这个问题,一般来说,Python 使用缩进还是会使代码更加清晰易读,因为你可以轻松地理解程序的逻辑过程。

7、排序

# 使用sorted()对字典进行排序
# 要想对字典排序的同时不修改原字典
# 先复制字典
print("Output #119: {}".format(a_dict))
dict_copy = a_dict.copy()
ordered_dict1 = sorted(dict_copy.items(), key=lambda item: item[0])
print("Output #120 (order by keys): {}".format(ordered_dict1))
ordered_dict2 = sorted(dict_copy.items(), key=lambda item: item[1])
print("Output #121 (order by values): {}".format(ordered_dict2))
ordered_dict3 = sorted(dict_copy.items(), key=lambda x: x[1], reverse=True)
print("Output #122 (order by values, descending): {}".format(ordered_dict3))
ordered_dict4 = sorted(dict_copy.items(), key=lambda x: x[1], reverse=False)
print("Output #123 (order by values, ascending): {}".format(ordered_dict4))
输出结果:
Output #119: {'one': 1, 'two': 2, 'three': 3}
Output #120 (order by keys): [('one', 1), ('three', 3), ('two', 2)]
Output #121 (order by values): [('one', 1), ('two', 2), ('three', 3)]
Output #122 (order by values, descending): [('three', 3), ('two', 2), ('one', 1)]
Output #123 (order by values, ascending): [('one', 1), ('two', 2), ('three', 3)]
解释:
这个示例展示了如何使用不同方式对字典进行排序。本节开头就说过,字典没有隐含排
序,但是,你可以使用前面的代码片段对一个字典对象进行排序。可以按照字典的键或字
典值来排序,如果这些值是数值型的,排序方式可以是升序,也可以是降序。
这个示例中使用了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 标准库(https://docs.python.org/3/library/
index.html)。

是不是看不懂解释?
哈哈哈,我重新自己了解了一下。
字典可以使用items取:键-值 一对儿都取出来,诸如上方的’one’:1
然后keys可以取键,诸如上方的one
而values则取得是值,诸如上方的1

#比如我们分别打印一下字典的items、keys、values
a_dict = {'one':1, 'two':2, 'three':3}
print(a_dict.items())
print(a_dict.keys())
print(a_dict.values())
#打印结果是:
dict_items([('one', 1), ('two', 2), ('three', 3)])
dict_keys(['one', 'two', 'three'])
dict_values([1, 2, 3])

所以刚才120个示例,是以item[0]进行升序排列,即取到的键-值对中的键,而键排列时是分别以键中字母的字母排序进行对比的,one中的o,比two和three中的t靠前,所以排在第一位,而two中的w,比three中的h靠后,所以排在three后面,则结果即为

Output #120 (order by keys): [('one', 1), ('three', 3), ('two', 2)]

你看懂了咩?~

七、控制流

控制流元素非常重要,因为可以在程序中包含有意义的业务逻辑。很多商务处理和分析依赖于业务逻辑,例如“如果客户的花费超过一个具体值,那么就怎样怎样”或“如果销售额属于A 类,则编码为X,如果销售额属于B 类,则编码为Y,否则编码为Z。”这些逻辑语句在代码中可以用控制流元素来表示。

Python 提供了若干种控制流元素,包括if-elif-else 语句、for 循环、range 函数和while循环。正如它们的名字所示,if-else 语句提供的逻辑为“如果这样,那么就做那个,否则做些别的事情”。else 代码块并不是必需的,但可以使你的代码更加清楚。for 循环可以使你在一系列值之间进行迭代,这些值可以是列表、元组或字符串。你可以使用range函数与len 函数一起作用于列表,生成一个索引序列,然后用在for 循环中。最后,只要while 条件为真,while 循环会一直执行内部的代码。

1、常见的判断字符

!= 操作法表示“不等于”
两个等号(==)判断是否相等

2、if-else

# if-else语句
x = 5
if x > 4 or x != 9:
    print("Output #124: {}".format(x))
else:
    print("Output #124: x is not greater than 4")
输出结果:
Output #124: 5
解释:
第一个示例展示了一个简单的if-else 语句。if 条件判断x 是否大于4 或者x 是否不等于
9(!= 操作法表示“不等于”)。通过使用or 操作符,在找到一个表达式为True 时就停止
判断。在这个例子中,x 等于5,5 大于4,所以x != 9 是不用判断的。第一个条件x > 4
为真,所以print x 被执行,打印结果为数值5。如果if 代码块中没有一个条件为真,那
么就执行else 代码块中的print 语句。

3、if-elif-else

# if-elif-else语句
if x > 6:
    print("Output #125: x is greater than six")
elif x > 4 and x == 5:
    print("Output #125: {}".format(x*x))
else:
    print("Output #125: x is not greater than 4")
输出结果:
Output #125: 25
解释:
第二个示例展示了一个稍微复杂一点的if-elif-else 语句。同前一个示例一样,if 代码块
测试x 是否大于6。如果这个条件为真,那么停止判断,执行相应的print 语句。实际上,
5 不大于6,所以使用下面的elif 语句继续判断。这个语句测试x 是否大于4 并且x 是否
等于5。使用and 操作符进行的判断在找到一个表达式为False 时就停止。在这个例子中,
x 等于5,5 大于4,并且求的x 值是5,所以执行print x*x,打印结果为数值25。因为
这里已经使用了等号作为对象赋值操作符,所以用两个等号(==)判断是否相等。如果if
和elif 代码块都不为真,那么就执行else 代码块中的print 语句。

4、for循环

another_dict = {'x':'printer', 'y':5, 'z':['star', 'circle', 9]}
y = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', \
'Nov', 'Dec']
z = ['Annie', 'Betty', 'Claire', 'Daphne', 'Ellie', 'Franchesca', 'Greta', \
'Holly', 'Isabel', 'Jenny']
print("Output #126:")
for month in y:
    print("{!s}".format(month))
print("Output #127: (index value: name in list)")
for i in range(len(z)):
    print("{0!s}: {1:s}".format(i, z[i]))
print("Output #128: (access elements in y with z's index values)")
for j in range(len(z)):
    if y[j].startswith('J'):
        print("{!s}".format(y[j]))
print("Output #129:")
for key, value in another_dict.items():
    print("{0:s}, {1}".format(key, value))
输出结果:
Output #126:
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
Output #127: (index value: name in list)
0: Annie
1: Betty
2: Claire
3: Daphne
4: Ellie
5: Franchesca
6: Greta
7: Holly
8: Isabel
9: Jenny
Output #128: (access elements in y with z's index values)
Jan
Jun
Jul
Output #129:
x, printer
y, 5
z, ['star', 'circle', 9]
解释:
这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 函数生成了列表z 的索引值。然后,if 语句测试列表y 中具有这些
索引值的元素(y[0]='Jan', y[1]='Feb', …, y[9]='Oct')是否以大写字母J 开头。
最后一个for 循环展示了在字典的键和值之间进行迭代和引用的方法。在for 循环的第一
行中,items 函数返回字典的键-值元组。for 循环中的key 和value 变量依次捕获这些值。
for 循环中的print 语句在每一行打印出一个键-值对,键和值之间以逗号隔开。

5、简化for循环:列表、集合与字典生成式

列表、集合与字典生成式是Python 中一种简化的for 循环写法。列表生成式出现在方括号内,集合生成式与字典生成式则出现在花括号内。所有的生成式都包括条件逻辑(例如:if-else 语句)。

1)列表生成式。

下面的示例展示了如何使用列表生成式来从一个列表集合中筛选出符合特定条件的列表子集:

# 使用列表生成式选择特定的行
my_data = [[1,2,3], [4,5,6], [7,8,9]]
rows_to_keep = [row for row in my_data if row[2] > 5]
print("Output #130 (list comprehension): {}".format(rows_to_keep))
输出结果:
Output #130 (list comprehension): [[4, 5, 6], [7, 8, 9]]
解释:
在这个示例中,列表生成式的意义是:对于my_data 中的每一行,如果这行中索引位置2
的值(即第三个值)大于5,则保留这一行。因为6 和9 都大于5,所以rows_to_keep 中
的列表子集为[4, 5, 6] 和[7, 8, 9]。

2)集合生成式。

下面的示例展示了如何使用集合生成式来从一个元组列表中选择出特定的元组集合:

# 使用集合生成式在列表中选择出一组唯一的元组
my_data = [(1,2,3), (4,5,6), (7,8,9), (7,8,9)]
set_of_tuples1 = {x for x in my_data}
print("Output #131 (set comprehension): {}".format(set_of_tuples1))
set_of_tuples2 = set(my_data)
print("Output #132 (set function): {}".format(set_of_tuples2))
输出结果:
Output #131 (set comprehension): {(7, 8, 9), (1, 2, 3), (4, 5, 6)}
Output #132 (set function): {(7, 8, 9), (1, 2, 3), (4, 5, 6)}
解释:
在这个示例中,集合生成式的意义是:对于my_data 中的每个元组,如果它是一个唯一
的元组,则保留这个元组。你可以称这个表达式为集合生成式而不是列表生成式,因为
表达式中是花括号,不是方括号,而且它也不是字典生成式,因为它没有使用键-值对
这样的语法。
这个示例中的第二个print 语句说明你可以通过Python 内置的set 函数达到和集合生成式
同样的效果。在这个例子中,使用内置的set 函数更好,因为它比集合生成式更精炼,而
且更易读。

3)字典生成式。

下面的示例展示了如何使用字典生成式来从一个字典中筛选出满足特定条件
的键-值对子集:

# 使用字典生成式选择特定的键-值对
my_dictionary = {'customer1': 7, 'customer2': 9, 'customer3': 11}
my_results = {key : value for key, value in my_dictionary.items() if \
value > 10}
print("Output #133 (dictionary comprehension): {}".format(my_results))
输出结果:
Output #133 (dictionary comprehension): {'customer3': 11}
解释:
在这个例子中,字典生成式的意义为:对于my_dictionay 中的每个键-值对,如果值大
于10,则保留这个键-值对。因为值11 大于10,所以保留在my_results 中的键-值对为
{'customer3':11}。

6、while循环

#while循环
print("Output #134:")
x = 0
while x < 11:
    print("{!s}".format(x))
    x += 1
输出结果:
Output #134:
0
1
2
3
4
5
6
7
8
9
10
解释:
这个示例展示了如何使用while 循环来打印0~10 的整数。x = 0 将变量x 初始化为0。然
后while 循环判断x 是否小于11。因为x 小于11,所以while 循环在一行中打印出x 值,
然后将x 的值增加1。接着while 循环继续判断x(此时为1)是否小于11。因为确实小于
11,所以继续执行while 循环内部的语句。这个过程一直以这种方式继续,直到x 从10 增
加到11。这时,while 循环继续判断x 是否小于11,表达式结果为假,while 循环内部语
句不再被执行。

while 循环适合于知道内部语句会被执行多少次的情况。更多时候,你不太确定内部语句需要执行多少次,这时就应该使用for 循环。

7、函数

在一些情况下,你会发现自己编写函数比使用Python 内置函数和安装别人开发的模块更方便有效。举例来说,如果你发现总是在不断重复地书写同样的代码片段,那么就应该考虑将这个代码片段转换为函数。某些情况下,函数可能已经存在于Python 基础模块或“可导入”的模块中了。如果函数已经存在,就应该使用这些开发好并已经通过了大量测试的函数。但是,有些情况下,你需要的函数不存在或不可用,这时就需要你自己创建函数。

要在Python 中创建函数,需要使用def 关键字,并在后面加上函数名称和一对圆括号,然后再加上一个冒号。组成函数主体的代码需要缩进。最后,如果函数需要返回一个或多个值,可以使用return 关键字来返回函数结果供程序使用。下面的示例展示了在Python 中创建和使用函数的方法:

# 计算一系列数值的均值
def getMean(numericValues):
    return sum(numericValues)/len(numericValues) if len(numericValues) > 0\
    else float('nan')

my_list = [2, 2, 4, 4, 6, 6, 8, 8]
print("Output #135 (mean): {!s}".format(getMean(my_list)))
输出结果:
Output #135 (mean): 5.0
解释:
这个示例展示了如何创建函数来计算一系列数值的均值。函数名为getMean。圆括号之间
的短语表示要传入函数中的数值序列,这是一个仅在函数作用范围内有意义的变量。在函
数内部,由序列的总和除以序列中数值的个数计算出序列的均值。此外,还可以使用ifelse
语句来检验序列中是否包含数值。如果确实包含数值,函数返回序列均值。如果不包
含数值,函数返回nan(即:非数值)。如果省略了if-else 语句,而且序列中正好没有任
何数值的话,程序就会抛出一个除数为0 的错误。最后,使用return 关键字返回函数结果
供程序使用。
在这个示例中,my_list 包含了8 个数值。my_list 被传入getMean() 函数中。8 个数值的
总和为40,40 除以8 等于5。所以,print 语句打印出整数5。

就像你猜测的那样,mean 函数已经存在了。例如,NumPy 中就有一个mean 函数。所以,你可以通过导入NumPy,使用它里面的mean 函数得到同样的结果:

import numpy as np
print np.mean(my_list)

再说一次,如果在Python 基础程序或可导入模块中,已经存在你需要的函数,就应该使用这些开发好的并已经通过了大量测试的函数。使用Google 或Bing 来搜索“< 你需要的功能描述> Python function”可以帮助你找到想要的Python 函数。但是,如果你想完成业务过程中特有的任务,知道如何去创建函数还是非常重要的。

8、异常

编写一个强壮稳健的程序的一个重要方面就是有效地处理错误和异常。在编写程序时,你可能会隐含地假设程序要处理的数据类型和数据结构,如果有数据违反了你的假设,就会使程序抛出错误。
Python 中包含了若干种内置的异常对象。常用的异常包括IOError、IndexError、KeyError、NameError、SyntaxError、TypeError、nicodeError 和ValueError。你可以在网上获得更多的异常信息,参见Python 标准库中的“Built-in Exceptions”那一节(http://docs.python.org/3/library/exceptions.html)。你可以使用try-except 来构筑处理错误信息的第一道防线,即使数据不匹配,你的程序还可以继续运行。
下面展示了两种使用try-except 代码块来有效地捕获和处理异常的方法(一种比较短,另一种比较长)。这两个示例修改了上一节的函数示例,来说明如何使用try-except 代码块代替if 语句处理空列表的情况。

1)try-except

# 计算一系列数值的均值
def getMean(numericValues):
    return sum(numericValues)/len(numericValues)
my_list2 = [ ]
try:
    print("Output #138: {}".format(getMean(my_list2)))
except ZeroDivisionError as detail:
    print("Output #138 (Error): {}".format(float('nan')))
    print("Output #138 (Error): {}".format(detail))
输出结果:
Output #138 (Error): nan
Output #138 (Error): division by zero
解释:
在这种处理异常的方法中, 函数getMean() 中没有检验序列是否包含数值的if 语
句。如果序列是空的,就像列表my_list2 一样,那么调用这个函数会导致一个异常
ZeroDivisionError。
要想使用try-except 代码块,需要将你要执行的代码放在try 代码块中,然后,使用
except 代码块来处理潜在的错误并打印出错误信息来帮助你理解程序错误。在某些情况
下,异常具有特定的值。你可以通过在except 行中加上as 短语来引用异常值,然后打印
出你为异常指定的变量。因为my_list2 中不包含任何数值,所以执行except 代码块,打
印出nan 和Error: float division by zero。

2)try-except-else-finally

# 完整形式
def getMean(numericValues):
    return sum(numericValues)/len(numericValues)
my_list2 = [ ]
try:
    result = getMean(my_list2)
except ZeroDivisionError as detail:
    print ("Output #142 (Error): " + str(float('nan')))
    print ("Output #142 (Error):", detail)
else:
    print ("Output #142 (The mean is):", result)
finally:
    print ("Output #142 (Finally): The finally block is executed every time")
输出结果:
Output #142 (Error): nan
Output #142 (Error): division by zero
Output #142 (Finally): The finally block is executed every time
解释:
这个完整形式的异常处理方法除了try 和except 代码块,还包含else 和finally 代码块。
如果try 代码块成功执行,则会接着执行else 代码块。因此,如果传递给try 代码块中的
getMean() 函数的数值序列中包含任意数值,那么这些数值的均值就会被赋给try 代码块
中的变量result,然后接着执行else 代码块。例如,如果这里使用程序代码+my_list1+,
就会打印出The mean is: 5.0。因为my_list2 不包含任何数值,所以执行except 代码块,
打印出nan 和Error: float division by zero。然后,总是执行finally 模块,打印出
The finally block is executed every time。

八、文本文件的读取和写入

数据几乎无一例外地是被保存在文件中的。这些文件可能是文本文件、CSV 文件、Excel文件或其他类型的文件。知道如何访问此类文件以及从中读取数据是在Python 中进行数据处理、加工与分析的前提。当完成了一个每秒钟可以处理很多文件的程序时,与手动一个个地处理文件相比,你会真正体会到写程序的好处。
你需要告诉Python,脚本要处理何种类型的文件。你可以在程序中写死文件名称,但是如果这样的话,就不能使用这个程序处理多个不同的文件了。能读取多个不同文件的方法是,在命令行窗口或终端窗口的命令行中,在Python 脚本的名字后面加上完整的文件路径名。要使用这种方法,需要在脚本开始时导入内置的sys 模块。在脚本上方加上import sys 语句之后,就可以在脚本中使用sys 模块提供的所有功能了:

#!/usr/bin/env python3
from math import exp, log, sqrt
import re
from datetime import date, time, datetime, timedelta
from operator import itemgetter
import sys

导入了sys 模块之后,你就可以使用argv 这个列表变量了。这个变量捕获了传递给Python脚本的命令行参数列表,即你在命令行中的所有输入,包括你的脚本名称。和任何其他列表一样,argv 也有索引。argv[0] 就是脚本名称,argv[1] 是命令行中传递给脚本的第一个附加参数,在这个例子中,就是first_script.py 将要读取的文件路径名。

1、创建文本文件

要读取一个文本文件,首先要创建它。要创建文本文件,需执行以下步骤。
(1) 打开Spyder IDE 或一个文本编辑器(例如:Windows 系统下的Notepad、Notepad++、Sublime Text;macOS 系统下的TextMate、TextWrangler、Sublime Text)。
(2) 在文本文件中写入下面6 行(参见图1-10):
I’m
already
much
better
at
Python.
(3) 将文件保存在桌面上,文件名为file_to_read.txt。
(4) 将下面几行代码添加到first_script.py 的下方:

# 读取文件
# 读取单个文本文件
input_file = sys.argv[1]
print ("Output #143: ")
filereader = open(input_file, 'r')
for row in filereader:
    print (row.strip())
filereader.close()

示例中的第一行代码使用sys.argv 列表捕获了要读取的文件的路径名,并将路径名赋给变量input_file。第二行代码创建了一个文件对象filereader,其中包含了以r 模式(只读模式)打开的input_file 文件中的各个行。下一行中的for 循环每次读取filereader 对象中的一行。for 循环内部的print 语句打印出每一行,并且在打印之前用strip 函数去掉每一行两端的空格、制表符和换行符。最后一行代码在输入文件中的所有行都被读取并打印到屏幕后,关闭filereader 对象。

(5) 重新保存first _script.py。
(6) 要读取刚才创建的文本文件,输入下面的命令,如图1-11 所示,然后按回车键:

python first_script.py file_to_read.txt

在这里插入图片描述
这样,你就在Python 中读取了一个文本文件。你会看到下面的内容被打印到屏幕上:
在这里插入图片描述

2、脚本和输入文件在同一位置

因为first_script.py 和file_to_read.txt 在同一位置,即都在桌面上,所以简单地输入pythonfirst_script.py file_to_read.txt 是可以的。如果文本文件和脚本不在同一位置,就需要输入文本文件的完整路径名,这样脚本才能知道去哪里寻找这个文件。例如,如果文本文件在你的Documents 文件夹中,而不是在桌面上,那么你可以在命令行中使用下面的路径名来从其所处位置读取文本文件:

python first_script.py "C:\Users\[Your Name]\Documents\file_to_read.txt"

3、读取文件的新型语法

前面讲的用来创建filereader 对象的那行代码是创建文件对象的传统方法。这种方法没有什么问题,但是它使文件对象一直处于打开状态,直到使用close 函数明确地关闭或直到脚本结束。尽管这种做法一般没有问题,但不够清晰,还被证明在更复杂的脚本中会导致错误。从Python 2.5 开始,你可以使用with 语句来创建文件对象。这种语法在with 语句结束时会自动关闭文件:

input_file = sys.argv[1]
print("Output #144:")
with open(input_file, 'r', newline='') as filereader:
   for row in filereader:
    print("{}".format(row.strip()))

你可以看到,使用with 语句的版本与前一个版本非常相似,但是它不需调用close 函数来关闭filereader 对象。
这个示例演示了如何使用sys.argv 来访问并打印一个文本文件中的内容。这是一个简单的示例,但在后面的示例中,要以此为基础访问其他类型的文件,或一次访问多个文件,并向输出文件中写入内容。

4、使用glob读取多个文本文件

在很多商业应用中,需要对多个文件进行同样的或相似的处理。例如,你可能会从多个文件中选择数据子集,根据多个文件计算像总计和均值这样的统计量,或根据来自于多个文件的数据子集计算统计量。当文件数量增加时,手动处理文件的可能性会减小,出错的概率会增加。
读取多个文件的一种方法是在命令行中将包含输入文件目录的路径名写在Python 脚本名称之后。要使用这种方法,你需要在脚本开头导入内置的os 模块和glob 模块。在脚本上方添加了import os 和import glob 语句之后,你就可以使用os 模块和glob 模块提供的所有功能了:

#!/usr/bin/env python3
from math import exp, log, sqrt
import re
from datetime import date, time, datetime, timedelta
from operator import itemgetter
import sys
import glob
import os

当导入了os 模块之后,你就可以使用它提供的若干种路径名函数了。例如,os.path.join函数可以巧妙地将一个或多个路径成分连接在一起。glob 模块可以找出与特定模式相匹配的所有路径名。os 模块和glob 模块组合在一起使用,可以找出符合特定模式的某个文件夹下面的所有文件。
要读取多个文件,需要再创建一个文本文件。

创建另一个文本文件
(1) 打开Spyder IDE 或一个文本编辑器(例如:Windows 系统下的Notepad、Notepad++、Sublime Text;macOS 系统下的TextMate、TextWrangler、Sublime Text)。
(2) 在文本文件中写入下面8 行:

This
text
comes
from
a
different
text
file.

(3) 将文件保存在桌面上,文件名为another_file_to_read.txt。
(4) 将下面几行代码添加到first_script.py 的下方:

# 读取多个文本文件
print("Output #145:")
inputPath = sys.argv[1]
for input_file in glob.glob(os.path.join(inputPath,'*.txt')):
    with open(input_file, 'r', newline='') as filereader:
        for row in filereader:
            print("{}".format(row.strip()))
解释:
这个示例中的第一行代码与读取单个文本文件示例中的代码非常相似,只是在这个示例中,要提供一个目录路径名,而不是一个文件路径名。这里,要提供的路径指向包含了两个文本文件的目录。
第二行代码是for 循环,使用os.path.join 函数和glob.glob 函数来找出符合特定模式的
某个文件夹下面的所有文件。指向这个文件夹的路径包含在变量inputpath 中,这个变量
将在命令行中被提供。os.path.join 函数将这个文件夹路径和这个文件夹中所有符合特
定模式的文件名连接起来,这种特定模式可以由glob.glob 函数扩展。这个示例使用的是
模式*.txt 来匹配由.txt 结尾的所有文件名。因为这是一个for 循环,所以这行中其余的
代码你应该很熟悉了。input_file 是一个占位符,表示由glob.glob 函数生成的列表中的
每个文件。这行代码的意义就是:对于匹配文件列表中的每个文件,做下面的操作……
余下的代码和读取单个文件的代码非常相似。以只读方式打开input_file 变量,然后
创建一个filereader 对象。对于filereader 对象中的每一行,除去行两端的空格、制
表符和换行符,然后打印这一行。

(5) 重新保存first _script.py。
(6) 要读取这些文本文件,输入以下代码,如图1-14 所示,然后按回车键:

python first_script.py "C:\Users\[Your Name]\Desktop"

在这里插入图片描述
这样,你就在Python 中读取了多个文本文件。你会看到以下内容被打印到屏幕上:
在这里插入图片描述
学会这项技术的一个巨大好处是它可以规模化扩展。这个示例只是处理两个文本文件,但是它可以轻松地扩展为处理几十、几百或者几千甚至更多的文件。学习了如何使用glob.glob函数,仅花费手动处理的一小部分时间,就可以处理非常非常多的文件。

5、写入文本文件

迄今为止,大多数示例还是使用print 语句将输出发送到命令行窗口或终端窗口。当你在调试程序,或者在检查输出的准确度时,将输出打印到屏幕上是有意义的。但是,在很多情况下,只要你能确定输出是正确的,就会需要将输出写入文件,以进行更进一步的分析、报告和存储。
Python 提供了两种简单的方法来将输出写入文本文件和分隔符文件。write 方法可将单个字符串写入一个文件,writelines 方法可将一系列字符串写入一个文件。下面的两个示例使用range 函数和len 函数跟踪一个列表中的索引值,以将分隔符放在各个列表值之间,并在最后一个列表值后面放上一个换行符。

(1) 将下面各行代码添加到first_script.py 的底部:

# 写入文件
# 写入一个文本文件
my_letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
max_index = len(my_letters)
output_file = sys.argv[1]
filewriter = open(output_file, 'w')
for index_value in range(len(my_letters)):
    if index_value < (max_index-1):
        filewriter.write(my_letters[index_value]+'\t')
    else:
        filewriter.write(my_letters[index_value]+'\n')
filewriter.close()
print ("Output #146: Output written to file")
解释:
在这个例子中,变量my_letters 是一个字符串列表。这里想把这些字母打印到一个文
本文件中,每个字母之间用制表符分隔。这个示例中的难点是确保在字母之间以制表符
分隔,并在最后一个字母后面放上一个换行符(不是制表符)。
为了知道什么时候到达最后一个字母,你需要跟踪列表中字母的索引值。len 函数用来计
算出列表中字母的数量,所以max_index 等于10。在命令行窗口或终端窗口中,再次使
用sys.argv[1] 来在命令行中提供输出文件的路径名。创建一个文件对象filewriter,但
是打开方式不是只读,而是通过w(可写)的方式打开。使用for 循环在列表my_letters
的各个值之间进行迭代,并使用range 函数和len 函数跟踪列表中各个字母的索引值。
if-else 语句可以使你对列表中的最后一个字母做出与前面那些字母不同的处理。ifelse
语句是这样工作的:my_letters 包含10 个元素,但是索引从0 开始,所以各个字
母的索引值分别是0、1、2、3、4、5、6、7、8、9。因此,my_letters[0] 是a,my_
letters[9] 是j。if 代码块判断索引值x 是否小于9,max_index – 1 或者是10 – 1 = 9。
直到列表中的最后一个字母,这个条件才为True。因此,if 代码块的意义是:一直到
列表中的最后一个字母,都向输出文件中写入字母,并在字母后面加一个制表符。当你
到达了列表中的最后一个字母时,这个字母的索引值为9,不大于9,所以if 代码块判
断为False,就执行else 代码块。else 代码块中的write 语句的意义是:向输出文件中
写入最后一个字母,并在后面加一个换行符。

(2) 将前面读取多个文件的代码注释掉。
为了看到这些代码是如何工作的,这里需要写入一个文件然后查看输出。因为你又一次使用了argv[1] 来确定输出文件的路径名,所以需要将前面的glob 代码删除或注释掉,这样就可以使用argv[1] 来确定输出文件了。
(3) 重新保存first_script.py。
(4) 要写入一个文本文件,输入下面的代码,然后按回车键:

python first_script.py "C:\Users\[Your Name]\Desktop\write_to_file.txt"

在这里插入图片描述
(5) 打开输出文件write_to_file.txt。
这样,你就使用Python 将输出写入了一个文本文件。在完成这些步骤之后,你不会在屏幕上看到新的输出;但是,如果你将所有打开的窗口最小化,就会看到桌面上有一个新的文本文件,名为write_to_file.txt。这个文件中应该包含了列表my_letters 中的字母,以制表符隔开,最后有一个换行符。
在这里插入图片描述

6、写入CSV文件 - 追加文本内容

(1) 将下列各行代码添加到first_script.py 的底部:

# 写入CSV文件
my_numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
max_index = len(my_numbers)
output_file = sys.argv[1]
filewriter = open(output_file, 'a')
for index_value in range(len(my_numbers)):
    if index_value < (max_index-1):
        filewriter.write(str(my_numbers[index_value])+',')
    else:
        filewriter.write(str(my_numbers[index_value])+'\n')
filewriter.close()
print ("Output #147: Output appended to file")
解释:
这个示例与前面的示例非常相似,但是它说明了如何向已经存在的输出文件中追加内
容,以及如何将列表中的非字符串数据转换成字符串,以便可以使用write 函数来写入
文件。在这个示例中,列表中的元素是整数。write 函数处理的是字符串,所以在你使
用write 函数将其写入输出文件之前,需要使用str 函数将非字符串数据转换成字符串。
在使用for 循环进行第一次迭代时,str 函数会向输出文件中写入一个0,然后写入一个
逗号。以这种方式继续写入列表中的其他数值,直到列表中的最后一个数值,这时执行
else 代码块,将最后一个数值写入输出文件,并在后面加上一个换行符,而不是逗号。
请注意在打开文件对象filewriter 时,使用的是追加模式('a'),而不是可写模式
('w')。如果在命令行中提供了同样的输出文件名,那么这段代码的输出会被追加到
write_to_file.txt 文件中,在以前写入文件的内容之后。相反,如果使用可写方式打开
filewriter 对象,那么以前的输出会被删除,write_to_file.txt 文件中只会出现这段代码
的输出。你会在本书后面的章节中看到使用追加方式打开文件的作用,这时你要处理多
个文件,并将其中所有的数据追加到一个连接文件中。

(2) 重新保存first_script.py。
(3) 要向文本文件中追加数据,输入以下命令然后按回车键:

python first_script.py "C:\Users\[Your Name]\Desktop\write_to_file.txt"

(4) 打开输出文件write_to_file.txt。
这样,你就使用Python 向文本文件中写入和追加了数据。在完成这些步骤之后,你不会在屏幕上看到新的输出;但是,如果你打开了write_to_file.txt 文件,会看到文件中出现了新的一行,行中包括了my_numbers 中的数值,以逗号隔开,并在末尾有一个换行符,如图:
在这里插入图片描述
最后,这个示例演示了一个写入CSV 文件的有效方法。实际上,在前面的例子中,你将由制表符分隔的数据写入了输出文件,如果将制表符改为逗号,并且将输出文件命名为write_to_file.csv 而不是write_to_file.txt 的话,就可以创建一个CSV 文件。

九、print语句

print 语句非常有助于程序调试。你已经看到,本章中很多示例程序都使用print 语句作
为输出。但是,你还可以在代码中临时添加print 语句来检查中间结果。如果你的代码运
行不了或者不能产生你需要的结果,那么可以在程序开头适当的地方添加print 语句,看看最初的计算是否符合你的预期。如果符合,就继续检查下面的代码,看看它们是否按照你的预期工作。
从脚本开头进行检查,可以保证你找出第一个出错的地方并在检查余下的代码之前进行修复。本节的中心思想就是:不要吝啬在程序中使用print 语句,它可以帮助你调试代码并保证代码正确。当你确信代码正确之后,完全可以将print 语句注释掉或删除。
这一章介绍了很多基础知识,包括如何导入模块、基本的数据类型和与之相关的函数和方法、模式匹配、print 语句、日期处理、控制流、函数、异常、读取单个或多个文件,以及写入文本文件和分隔符文件。如果你一直跟随本章中的示例进行练习,那么你已经写了500 多行Python 代码了!

十、本章练习

(1) 创建一个新的Python 脚本,在它里面创建3 个不同的列表,将这3 个列表相加,并使用for 循环和定位索引(也就是range(len()))在列表中循环,在屏幕上打印出列表的索引值和元素值。

a_list=[1,2,3]
b_list=['a','b','c']
c_list=['hello','world']
num_list=a_list+b_list+c_list
for i in range(len(num_list)):
    print("{}: {}".format(i, num_list[i]))
输出结果:
0: 1
1: 2
2: 3
3: a
4: b
5: c
6: hello
7: world

(2) 创建一个新的Python 脚本,在它里面创建两个同样长度的不同列表,其中一个列表包含具有唯一性的字符串。再创建一个空字典。使用for 循环、定位索引和if 语句检查字符串列表中的每个元素是否是字典中的键。如果不是,将这个元素作为字典键,将另一个列表中具有同样索引位置的元素 作为字典值,把它们添加到字典中。最后在屏幕上打印出字典的键和值。

a_dictionary={}
b_list=['a','b','c']
c_list=['1,2,3','a','Letters in words']
for i in range(len(b_list)):
    if b_list[i] not in a_dictionary:
        a_dictionary[b_list[i]]=c_list[i]
for key,value in a_dictionary.items():
    print("{}:{}".format(key,value))
输出结果:
a:1,2,3
b:a
c:Letters in words

(3) 创建一个新的Python 脚本,在它里面创建一个列表,其中的元素是具有相同长度的列表。修改1.7.2 节中的代码,在列表的列表中循环,将每个列表中的值以逗号隔开的字符串形式打印在屏幕上,并在每个列表的最后一个值后面加上一个换行符。


注:内容引自《Python数据分析基础》—Clinton W.Brownley著,陈光欣译


Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐