第 1 章 Python 基础

第 1 章 Python 基础

很多关于 Python 的图书和在线教程都展示了如何在 Python shell 中运行代码。要以这种形式运行 Python 代码,需要先打开一个命令行窗口(Windows 系统)或终端窗口(macOS 系统),输入“python”,按回车键之后会看见 Python 提示符(就是 >>>)。然后,只需一个一个地输入命令,Python 就会依次执行。

下面是两个典型的示例:

>>> 4 + 5
9

>>> print("I'm excited to learn Python.")
I'm excited to learn Python.

这种运行代码的方法简捷有趣,但是当代码的行数不断增加时,就不太合适了。当你的任务需要多行代码才能完成时,一种更简便的方式是将所有的代码写在一个称为 Python 脚本的文本文件中,然后运行这个脚本。下面就说明创建 Python 脚本的方法。

1.1 创建Python脚本

要创建一个 Python 脚本,需执行下列步骤。

(1) 打开 Spyder IDE 或一个文本编辑器(例如:Windows 系统可以使用 Notepad、Notepad++ 或 Sublime Text;macOS 系统可以使用 TextMate、TextWrangler 或 Sublime Text)。

(2) 将下面两行代码写在文本文件中:

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

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

第二行是一个简单的打印语句。这一行会将双引号之间的文本打印在命令行窗口(Windows)或终端窗口(macOS)上。

(3) 打开 Save As 对话框。

(4) 在 location 栏中切换到桌面,使文件可以保存到桌面上。

(5) 在 format 栏中,选择 All Files,使对话框不自动选择文件类型。

(6) 在 Save As 或 File Name 栏中,输入“first_script.py”。以前,你可能会将这个文本文件保存为 .txt 文件,但是在这个示例中,你应该把它保存为 .py 文件,来创建一个 Python 脚本。

(7) 点击 Save。

这样,你就创建了一个 Python 脚本。图 1-1、图 1-2 和图 1-3 分别展示了使用 Anaconda Spyder、Notepad++(Windows)和 TextWrangler(macOS)创建脚本的界面。

{%}

图 1-1:Anaconda Spyder 中的 Python 脚本 first_script.py

{%}

图 1-2:Notepad++(Windows)中的 Python 脚本

{%}

图 1-3:TextWrangler(macOS)中的 Python 脚本

下一节将介绍如何在命令行窗口或终端窗口中运行 Python 脚本。你会看到运行脚本和创建脚本一样简单。

1.2 运行Python脚本

如果你使用 Anaconda Spyder IDE 来创建脚本,那么点击 IDE 左上角的绿色三角(运行按钮)就可以运行脚本。

点击运行按钮之后,你就会看到输出显示在 IDE 右下窗格里面的 Python 控制台中。屏幕截图显示了绿色运行按钮和红框中的输出(参见图 1-4)。在这个示例中,输出为“Output #1: I'm excited to learn Python.”。

{%}

图 1-4:在 Anaconda Spyder 中运行 Python 脚本 first_script.py

或者,你可以在命令行窗口(Windows)或终端窗口(macOS)中运行脚本,如下所示。

  • Windows 命令行窗口

    (1) 打开一个命令行窗口。

    当窗口打开后,提示符会是一个具体的文件夹,也称为目录(例如:C:\Users\Clinton 或 C:\Users\Clinton\Documents)。

    (2) 切换到桌面(将 Python 脚本保存在这里)。

    要完成这个操作,输入以下命令,然后按回车键:

    cd "C:\Users\[Your Name]\Desktop"
    
    

    使用你的电脑账户名称,通常是你的名字,来替换 [Your Name]。例如,在我的电脑上,应该输入:

    cd "C:\Users\Clinton\Desktop"
    
    

    这时候,提示符应该是这样的:C:\Users[Your Name]\Desktop。这就对了,因为这就是保存 Python 脚本的地方。最后一步就是运行脚本了。

    (3) 运行 Python 脚本。

    要完成这个操作,输入以下命令,然后按回车键:

    python first_script.py
    
    

    你可以看到以下输出在命令行窗口中被打印出来,如图 1-5 所示。

    Output #1: I'm excited to learn Python.
    
    

    {%}

    图 1-5:在命令行窗口(Windows)中运行 Python 脚本

  • 终端窗口(Mac)

    (1) 打开一个终端窗口。

    当窗口打开后,提示符会是一个具体的文件夹,也称为目录(例如:/Users/clinton 或 /Users/clinton/Documents)。

    (2) 切换到桌面,将 Python 脚本保存在这里。

    要完成这个操作,输入以下命令,然后按回车键:

    cd /Users/[Your Name]/Desktop
    
    

    使用你的电脑账户名称,通常是你的名字,来替换 [Your Name]。例如,在我的电脑上,应该输入:

    cd /Users/clinton/Desktop
    
    

    这时候,提示符应该是这样的:/Users/[Your Name]/Desktop。这就对了,因为这就是保存 Python 脚本的地方。下一步是为脚本添加执行权限,然后运行脚本。

    (3) 为 Python 脚本添加执行权限。

    要完成这个操作,输入以下命令,然后按回车键:

    chmod +x first_script.py
    
    

    chmod 是一个 Unix 命令,表示改变访问权限(change access mode)。+x 表示在访问设置中添加执行权限,而不是添加读权限和写权限,这样 Python 就可以执行脚本中的代码了。你必须为创建的每个 Python 脚本运行一次 chmod 命令,以使脚本可以执行。只要你在一个文件上运行了 chmod 命令,以后就可以随意运行这个脚本,不用再执行 chmod 命令了。

    (4) 运行 Python 脚本。

    要完成这个操作,输入以下命令,然后按回车键:

    ./first_script.py
    
    

    你可以看到以下输出在终端窗口中被打印出来,如图 1-6 所示。

    Output #1: I'm excited to learn Python.
    
    

    {%}

    图 1-6:在终端窗口(macOS)中运行 Python 脚本

1.3 与命令行进行交互的几项技巧

下面是与命令行进行交互的几项技巧。

  • 使用向上箭头键得到以前的命令

    命令行窗口和终端窗口的一个美妙功能是,你可以通过按向上箭头键找到以前输入的命令。试着在命令行窗口或终端窗口中按一下向上箭头键,找到你以前输入的命令,在 Windows 系统中是 python first_script.py,在 Mac 系统中是 ./first_script.py

    这个功能非常方便,可以减少每次运行 Python 脚本时必需的输入量,特别是当 Python 脚本的文件名特别长或需要在命令行上提供额外的参数(比如输入文件名或输出文件名)的时候。

  • 用 Ctrl+c 停止脚本

    既然你已经学会了运行脚本,那么是时候学习一下如何中断和停止 Python 脚本了。在相当多的情况下,你应该知道如何停止脚本。例如,你可能会写出死循环代码,这样脚本就永远不会停止运行。另外一种情况是,你编写的代码可能需要很长时间才能执行完毕,如果你在代码中包含了 print 语句,并由此发现脚本不会生成需要的输出,这时就应该提前终止脚本。

    在脚本开始运行之后,如果想随时中断或停止脚本,可以按 Ctrl+c(Windows)或 Control+c(macOS)。这会停止通过命令开始的进程。你不用太在意这项技术的细节,只要知道进程是计算机对一系列命令的处理过程就可以了。你编写了一个脚本程序,计算机将它解释成一个进程,如果这个程序非常复杂,就解释成一系列进程,这些进程或者顺序执行,或者并发执行。

  • 读懂出错信息并找到解决方案

    这部分的主题是如何处理比较麻烦的脚本,也简单说一下遇到以下问题应该如何解决。当你输入了 ./python first_script.py,或者试图运行任何一个 Python 脚本的时候,脚本没有正确运行,命令行窗口或终端窗口显示了出错信息。首先不要慌张,先读懂出错信息。某些情况下,出错信息中明确指出了代码中出现错误的行,你可以将精力集中在这一行上来纠正错误(你的文本编辑器或 IDE 应该设置成显示行号;如果不自动显示行号,请在菜单中找一下或在网上快速搜索一下,弄清楚如何显示行号)。重要的是要知道出错信息也是编程的一部分,学会编码也包括学会如何有效地调试程序错误。

    更重要的是,因为出错信息是通用的,所以通常很容易学会如何调试程序错误。你很可能不是第一个遇到这种错误并在网上搜索解决方案的人。最好的做法是将整个错误信息(至少是信息的主要部分)复制到搜索引擎(例如:Google 或者 Bing)上,然后在搜索结果中仔细研究其他人是如何调试这种错误的。

    熟悉 Python 内置的异常对象也是非常有帮助的,这样你就可以识别出标准的出错信息并知道如何改正错误。你可以在 Python 标准库页面(http://docs.python.org/3/library/exceptions.html)找到 Python 内置的异常,但是在网上通过出错信息搜索其他人的解决方案也是非常有用的。

  • 向 first_script.py 添加更多的代码

    现在,为了更熟练地编写 Python 代码和运行 Python 脚本,试着对 first_script.py 进行编辑,添加更多的代码,然后重新运行脚本。在进行新的练习时,可以把本章提供的代码段添加到脚本中原来代码的下方,保存脚本,然后重新运行。

    举个例子,将下面的两段代码添加到原有的 print 语句下面,然后保存脚本并重新运行(请记住,如果你使用的是命令行窗口或终端窗口,在将这些代码添加到 first_script.py 并重新保存脚本之后,可以按向上箭头键,找到你用来运行脚本的命令,不需要将命令重新输入一遍):

    # 两个数值相加
    x = 4
    y = 5
    z = x + y
    print("Output #2: Four plus five equals {0:d}.".format(z))
     
    # 两个列表相加
    a = [1, 2, 3, 4]
    b = ["first", "second", "third", "fourth"]
    c = a + b
    print("Output #3: {0}, {1}, {2}".format(a, b, c))
    
    

     以 # 开头的行是注释,用来解释代码,描述代码的用途和目的。

    第一个示例展示了将数值赋给变量、变量相加和格式化 print 语句的方法。这里详细说明一下 print 语句中的语法 "{0:d}".format(z)。花括号({})是一个占位符,表示这里将要传入 print 语句一个具体的值,这里就是变量 z 的值。0 指向 format() 方法中的第一个参数,在这里,只包含一个参数 z,所以 0 就指向这个值;相反,如果有多个参数,0 就确定地表示传入第一个参数。

    冒号(:)用来分隔传入的值和它的格式。d 表示这个值应该被格式化为整数,没有小数部分。在下一节中,你将会学习如何设定小数位数来显示浮点数。

    第二个示例展示了创建列表、列表相加和将列表中的值以逗号分隔打印在屏幕上的方法。看一下 print 语句中的语法 "{0}, {1}, {2}".format(a, b, c),它说明了如何在 print 语句中包含多个值。a 被传给 {0}b 被传给 {1}c 被传给 {2}。因为这 3 个值都是列表,不是数值,所以不设置数值格式。本章后面的小节会对这部分内容进行更深入的讨论。

    为什么要在打印时使用 .format

    Python 并不要求每条 print 语句都必须使用 .format,但是 .format 确实功能强大,可以为你节省很多输入。在上面的示例中,注意 print("Output #3: {0}, {1}, {2}".format(a, b, c)) 的最终结果是用逗号分隔的 3 个变量。如果你想在不使用 .format 的情况下得到同样的结果,那么就应该这样写:print("Output #3: ",a,", ",b,", ",c),但这是一段非常容易出现输入错误的代码。后面还会介绍 .format 的其他用法,但是从现在开始,你就应该熟练掌握它的用法,以便在需要的时候加以使用。

    图 1-7 和图 1-8 展示了在 Anaconda Spyder 和 Notepad++ 中添加新代码的界面。

    {%}

    图 1-7:在 Anaconda Spyder 中为 first_script.py 添加新代码

    {%}

    图 1-8:在 Notepad++(Windows)中为 first_script.py 添加新代码

    如果你将前面的代码添加到了 first_script.py 中,那么当你保存并且重新运行脚本之后,会看到屏幕中有如下输出(参见图 1-9):

    Output #1: I'm excited to learn Python.
    Output #2: Four plus five equals 9.
    Output #3: [1, 2, 3, 4], ['first', 'second', 'third', 'fourth'],
    [1, 2, 3, 4, 'first', 'second', 'third', 'fourth']
    
    

    {%}

    图 1-9:在命令行窗口中运行添加了代码之后的 first_script.py

1.4 Python语言基础要素

既然你已经学会了如何创建和运行 Python 脚本,那么以前需要手动实现的业务过程,现在完全可以通过编写 Python 脚本来自动化和规模化地完成。后面几章会详细介绍如何使用 Python 脚本来自动化和规模化地完成任务,但是在进行下一部分内容之前,还需要掌握更多的 Python 语言基础要素。通过掌握更多的基础要素,你会对 Python 有更深入的理解,在后面的章节中就可以综合运用这些知识来完成具体的数据处理任务。首先,本节介绍 Python 中最常用的数据类型,然后讨论使用 if 语句和函数来处理数据的方法。在此之后,从实际出发,介绍如何使用 Python 读写文本文件和 CSV 文件。

1.4.1 数值

Python 有好几种内置数值类型。数值类型非常有用,因为很多商业应用需要对数值进行分析和处理。Python 中最主要的 4 种数值类型是整数、浮点数、长整数和复数。这里只介绍整数和浮点数,因为它们在商业应用中是最常用的。你可以把下面处理整数和浮点数的示例添加到 first_script.py 中,放在现有的代码下面,然后重新运行脚本,在屏幕上检查输出。

  1. 整数

    下面直接看几个带有整数的示例:

    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。

  2. 浮点数

    和整数一样,浮点数(即带小数点的数)对很多商业应用来说也是非常重要的。下面是几个带有浮点数的示例:

    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))
    
    

    Output #7 和 Output #6 非常相似,除了将两个相除的数保留为浮点数,这样算式就是 8.3 除以 2.7,大约等于 3.074。这个示例中 print 语句的语法,"{0:.3f}".format(floating_point_number/floating_point_number),说明了如何设置 print 语句中的小数位数。在这个示例中,.3f 设定了打印的输出值应该有 3 位小数。

    Output #8 表示用 2.5 乘以 4.8,将结果赋给变量 y,然后将结果打印出来,带有一位小数。这两个浮点数相乘的结果是 12,所以打印出的值是 12.0。Output #9 和 Output #10 表示以两种方式计算 8 除以 3,结果都是一个浮点数,大约等于 2.667。

type 函数

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

在 Python 中进行数值处理时,需要知道的非常重要的一点是,你可以使用几种标准库模块和内置函数与模块来进行常见的数学计算。你已经使用了两个内置函数来处理数值,分别是 intfloat。另一个有用的标准模块是 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 个有用的数学函数可以任意使用了。函数 explogsqrt 分别表示 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)))

这 3 种数学函数的计算结果是浮点数,分别约为 20.0855、1.39 和 9.0。

这只是 math 模块中一点微小的功能。Python 中还有很多有用的数学函数和模块,用于商业、科学、统计和其他应用,本书还会对此进行更多的讨论。关于数学模块和其他标准模块以及内置函数的更多信 - 息,可以参考 Python 标准库(https://docs.python.org/3/library/index.html)。

1.4.2 字符串

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

字符串可以包含在单引号、双引号、3 个单引号或 3 个双引号之间。下面是字符串的几个示例:

print("Output #14: {0:s}".format('I\'m enjoying learning Python.'))

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."))

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 #14 和本章开头的示例非常像。它展示了一个包含在单引号之间的简单字符串。这个 print 语句的结果是 "I'm enjoying learning Python."。请记住,如果用双引号来包含这个字符串的话,就不需要在 "I'm" 的单引号前面使用反斜杠了。

Output #15 展示了如何使用反斜杠将一个非常长的字符串分段显示在多个行中,以使它易于理解和编辑。尽管这个字符串分布在脚本的多个行中,它还是一个字符串,并作为一个完整的字符串被打印出来。在这种将长字符串分成多行的方法中,要注意的是反斜杠必须是每行的最后一个字符。如果你意外地按了一下空格键,反斜杠后面就会出现一个看不见的空格,脚本就会抛出一个语法错误,不能正常运行。因此,使用 3 个单引号或 3 个双引号来创建多行字符串更稳妥一些。

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))
print("Output #19: {0:s} {1:s}{2:s}".format("She is", "very "*4, "beautiful."))
m = len(sentence)
print("Output #20: {0:d}".format(m))

Output #18 展示了如何使用 + 操作符来将两个字符串相加。这个 print 语句的输出结果是 This is a short string+ 操作符将两个字符串按照原样相加,所以如果你想在结果字符串中留出空格的话,就必须在原字符串中加上空格(例如:在 Output #18 中,要在字母“a”后面加空格)或者在原字符串之间加上空格(例如:在 Output #19 中,要在“very”后面加上空格)。

Output #19 展示了如何使用 * 操作符将字符串重复一定的次数。在这个示例中,结果字符串包含了字符串“very”(就是 very 后面跟着一个空格)的 4 个副本。

Output #20 展示了如何使用内置函数 len 来确定字符串中字符的数量。函数 len 将空格与标点符号也计入字符串长度。所以,在 Output #20 中,字符串 This is a short string. 的长度是 23 个字符。

处理字符串的一个常用标准库模块是 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))
    print("Output #22: FIRST PIECE:{0} SECOND PIECE:{1} THIRD PIECE:{2}"\
    .format(string1_list2[0], string1_list2[1], string1_list2[2]))
    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 #21 中,括号中没有附加参数,所以 split 函数使用空格字符(默认值)对字符串进行拆分。因为这个字符串中有 5 个空格,所以字符串被拆分成具有 6 个子字符串的列表。新生成的列表为 ['My', 'deliverable', 'is', 'due', 'in', 'May']

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

    在 Output #23 和 Output #24 中,括号中的附加参数是个逗号。在这种情况下,split 函数在出现逗号的位置拆分字符串。结果列表为 ['Your', 'deliverable', 'is', 'due', 'in', '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

    下面两组示例展示了如何使用 striplstriprstrip 函数从字符串两端删除不想要的字符。这 3 个函数都可以在括号中使用一个附加参数来设定要从字符串两端删除的字符(或字符串)。

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

    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))
    
    

    string3 的左侧含有几个空格。此外,右侧包含制表符(\t)、几个空格和换行符(\n)。如果以前你没有见过 \t\n,那么现在知道了这是计算机中表示制表符和换行符的方法。

    在 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))
    string5_replace = string5.replace(" ", ",")
    print("Output #33 (with commas): {0:s}".format(string5_replace))
    
    

    Output #32 展示了如何使用 replace 函数将字符串中的空格替换为 !@!。结果字符串为 Let's!@!replace!@!the!@!spaces !@!in!@!this!@!sentence!@!with!@!other!@!characters.

    Output #33 展示了如何使用逗号替换字符串中的空格。结果字符串为 Let's,replace,the, spaces,in,this,sentence,with,other,characters.

  5. loweruppercapitalize

    最后 3 个示例展示了如何使用 loweruppercapitalize 函数。lowerupper 函数分别用来将字符串中的字母转换为小写和大写。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()))
    string5 = "here's WHAT Happens WHEN you use Capitalize."
    print("Output #36: {0:s}".format(string5.capitalize()))
    string5_list = string5.split()
    print("Output #37 (on each word):")
    for word in string5_list:
        print("{0:s}".format(word.capitalize()))
    
    

    Output #34 和 Output #35 是对 lowerupper 函数最直接的应用。在字符串上应用了这些函数之后,string6 中的所有字母都是小写,string7 中的所有字母都是大写。

    Output #36 和 Output #37 演示了 capitalize 函数。Output #36 说明了 capitalize 函数对字符串中的第一个字母应用 upper 函数,对其余的字母应用 lower 函数。Output #37 将 capitalize 函数放在一个 for 循环中。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)来获取更多信息。

1.4.3 正则表达式与模式匹配

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

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

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

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

re 模块中还包括很多有用的函数,用于创建和搜索特定的模式(本节要介绍的函数包括 re.compilere.searchre.subre.ignorecasere.I)。来看一下示例代码:

# 计算字符串中模式出现的次数
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.compilere.I 函数以及用 r 表示的原始字符串,创建一个名为 pattern 的正则表达式。re.compile 函数将文本形式的模式编译成为编译后的正则表达式。正则表达式不是必须编译的,但是编译是个好习惯,因为这样可以显著地提高程序运行速度。re.I 函数确保模式是不区分大小写的,能同时在字符串中匹配“The”和“the”。原始字符串标志 r 可以确保 Python 不处理字符串中的转义字符,比如 \\t\n。这样在进行模式匹配时,字符串中的转义字符和正则表达式中的元字符就不会有意外的冲突。在上面的例子中,字符串中没有转义字符,所以 r 不是必需的,但是在正则表达式中使用原始字符串标志是一个好习惯。接下来的一行代码创建了一个变量 count 来保存字符串中模式出现的次数,初始值设为 0

再下一行是个 for 循环语句,在列表变量 string_list 的各个元素之间进行迭代。它取出的第一个元素是“The”这个单词,取出的第二个元素是“quick”这个单词,以此类推,直到取出列表中所有的单词。接下来的一行使用 re.search 函数将列表中的每个单词与正则表达式进行比较。如果这个单词与正则表达式相匹配,函数就返回 True,否则就返回 NoneFalse。所以 if 语句的意义就是:如果单词与正则表达式匹配,那么 count 的值就加 1。

最后,print 语句打印出正则表达式在字符串中找到模式“The”(不区分大小写)的次数,在本例中,找到了两次。

 太可怕了

正则表达式在进行搜索时的确功能强大,但是非常难以读懂(它曾被称为“只用来写的语言”),所以当你第一次没有看懂的时候,不用太在意,连专家都不一定能看懂!

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

再看另一个示例:

# 在字符串中每次找到模式时将其打印出来
string = "The quick brown fox jumps over the lazy dog."
string_list = string.split()
pattern = re.compile(r"(?P<match_word>The)", re.I)</match_word>
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 组中的值,并把这些值打印在屏幕上。”

下面是最后一个示例:

# 使用字母“a”替换字符串中的单词“the”
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 的著作《学习正则表达式》1

1此书已由人民邮电出版社出版。——编者注

1.4.4 日期

日期在大多数商业应用中都是必不可少的。你需要知道一个事件在何时发生,距离这件事发生还有多少时间,或者几个事件之间的时间间隔。因为日期是很多应用的核心,也因为日期是一种非常不寻常的数据,在处理时经常要乘以 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 模块之后,就有各式各样的日期时间对象和函数供你随意使用了。常用的对象和函数包括 todayyearmonthdaytimedeltastrftimestrptime。这些函数可以捕获具体的日期数据(例如:年、月、日)、进行日期和时间的加减运算、创建特定形式的日期字符串以及根据日期字符串创建 datetime 对象。下面是使用这些 datetime 对象和函数的几个示例。第一组示例演示了 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 语句中的值应该格式化为字符串,尽管它是个数值型数据。最后,你可以使用 yearmonthday 来捕获具体的日期元素。

下一个示例演示了如何使用 timedelta 函数来对 date 对象进行时间的加减操作:

# 使用timedelta计算一个新日期
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=10hours=-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 秒。正如你所见,对负值的规范化乍看上去很令人吃惊,特别是在进行取整和舍入时。

第三个示例展示了如何从一个 date 对象中减去另一个。相减的结果是个 datetime 对象,将所得的差以天、小时、分钟和秒来显示。例如,在这个示例中结果是“1 day, 0:00:00”:

# 计算出两个日期之间的天数
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] 这种语法,因为它是列表索引,可以用来从列表中取出特定的元素。

第四组示例展示了如何使用 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')))

在我写这一章的时候,当天的 4 种打印形式如下:

01/28/2016
Jan 28, 2016
2016-01-28
January 28, 2016

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

# 根据一个表示日期的字符串
# 创建一个带有特殊格式的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'))))

第五组示例展示了如何使用 strptime 函数根据具有特定形式的日期字符串来创建 datetime 对象。在这个示例中,date1date2date3date4 是字符串变量,以不同的形式表示今天。前两个 print 语句展示了将前两个字符串变量 date1date2 转换成 datetime 对象的结果。要使它们正确工作,strptime 函数中使用的形式需要和传入函数的字符串变量的形式相匹配。这两个 print 语句的输出结果是个 datetime 对象,2014-01-28 00:00:00

有些时候,你可能只对 datetime 对象中的日期部分感兴趣。在这种情况下,你可以使用嵌套的函数(在最后两个 print 语句中,是 datestrptime)将日期字符串变量转换为 datetime 对象,然后仅返回 datetime 对象的日期部分。这些 print 语句的结果是 2014-01-28。当然,你不需要立刻打印出这个值。你可以将这个日期赋给一个新变量,然后使用这个变量进行计算,洞悉随着时间产生的业务数据。

1.4.5 列表

很多商业分析中都使用列表。你会维护各种客户列表、产品列表、资产列表、销售量列表,等等。但是 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)))
    
    

    这个示例展示了如何创建两个简单列表,a_listanother_list。将元素放在方括号之间就可以创建列表。a_list 包含数值 1、2 和 3。another_list 包含一个字符串 printer、一个数值 5 以及一个包含了两个字符串和一个数值的列表。

    这个示例还展示了如何使用 4 种列表函数:lenminmaxcountlen 返回列表中元素的个数。minmax 分别返回列表中的最小值和最大值。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]))
    
    

    这个示例说明了如何使用索引值来引用列表中的特定元素。列表索引值从 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:]
    
    

    这个示例展示了如何使用列表切片引用列表元素的一个子集。在列表名称后面的方括号中放入由冒号隔开的两个索引,就可以创建一个列表切片。列表切片引用的是列表中从第一个索引值到第二个索引值的前一个元素。举例来说,第一个 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))
    
    

    这个示例展示了如何复制一个列表。如果你需要对列表进行某种操作,比如添加或删除元素,或对列表进行排序,但你还希望原始列表保持不变,这时这个功能就非常重要了。要复制一个列表,在列表名称后面的方括号中放入一个冒号,然后将其赋给一个新的变量即可。在这个示例中,a_new_lista_list 的一个完美复制,所以你可以对 a_new_list 添加或删除元素,也可以对 a_new_list 进行排序,而不会影响 a_list

  5. 列表连接

    # 使用+将两个或更多个列表连接起来
    a_longer_list = a_list + another_list
    print("Output #78: {}".format(a_longer_list))
    
    

    这个示例展示了如何将两个或更多个列表连接在一起。当你必须分别引用多个具有相似信息的列表,但希望将它们组合起来进行分析的时候,这个功能就非常重要了。举例来说,由于数据存储方式的原因,你可能需要生成一个销售量列表,其中包括来自于一个数据源和另一个数据源的销售量列表。要将两个销售量列表连接在一起进行分析,可以将两个列表名称用 + 操作符相加,然后赋给一个新变量。在这个示例中,a_long_list 包含 a_listanother_list 中的所有元素,将它们连接在一起形成一个更长的列表。

  6. 使用innot 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))
    
    

    这个示例展示了如何使用 innot in 来检查列表中是否存在某个特定元素。这些表达式的结果是 TrueFalse,取决于表达式为真还是假。这个功能在商业应用中非常重要,因为你可以使用它在程序中添加有意义的业务逻辑。举例来说,它们经常应用于 if 语句,比如“如果 SupplierYSupplierList 中,那么做些什么事,否则做些其他事。”本章后面的内容中将会介绍更多 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))
    
    

    这个示例展示了向列表中添加元素和从列表中删除元素的方法。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))
    
    

    这个示例展示了使用 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))
    
    

    这个示例展示了使用 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))
    
    

    这个示例展示了如何使用 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

    #!/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))
    
    

    这个示例展示了如何使用 sorted() 函数和 itemgetter 函数按照每个列表中多个索引位置的值对列表集合进行排序。关键字函数设置用于列表排序的关键字。在这个示例中,关键字是包含两个索引值(3 和 0)的 itemgetter 函数。这个语句的意义是:“先按照索引位置 3 中的值对列表进行排序,然后,在这个排序基础上,按照索引位置 0 中的值对列表进一步排序。”

    这种通过多个元素对列表和其他数据容器进行排序的方法非常重要,因为你经常需要按照多个值对数据进行排序。例如,如果要处理每天销售交易数据,你需要先按照日期再按照每天的交易量大小进行排序。或者,在处理供应商数据时,你会先按照供应商姓名再按照每个供应商的供货发票日期来排序。sorted 函数和 itemgetter 函数可以实现这样的功能。

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

1.4.6 元组

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

  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))
    
    

    这个示例展示了创建元组的方法。将元素放在括号中间,就可以创建一个元组。此示例还说明,前面讨论过的很多应用于列表的函数和操作符,也同样适用于元组。例如,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))
    
    

    这个示例展示了元组的一个很有意思的操作——解包。可以将元组中的元素解包成为变量,在赋值操作符的左侧放上相应的变量就可以了。在这个示例中,字符串 xyz 被解包成为变量 onetwothree 的值。这个功能可以用来在变量之间交换变量值。在示例的最后一部分,var2 的值被赋给 var1var1 的值被赋给 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)))
    
    

    最后,可以将元组转换成列表,也可以将列表转换成元组。这个功能和可以将一个元素转换成字符串的 str 函数很相似。要将一个列表转换成元组,将列表名称放在 tuple() 函数中即可。同样,要将一个元组转换成列表,将元组名称放在 list() 函数中即可。

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

1.4.7 字典

Python 中的字典本质上是包含各种带有唯一标识符的成对信息的列表。和列表一样,字典也广泛应用于各种商业分析。在商业分析中,可以用字典表示客户(以客户编码为键值),也可以用字典表示产品(以序列号或产品编号为键值),还可以用字典表示资产、销售量等。

在 Python 中,这样的数据结构称为字典,在其他编程语言中则称为关联数组键-值存储散列值。在商业分析中,列表和字典都是非常重要的数据结构,但是它们之间还存在着重要的区别,要想有效地使用字典,必须清楚这些区别。

  • 在列表中,你可以使用被称为索引索引值的连续整数来引用某个列表值。在字典中,要引用一个字典值,则可以使用整数、字符串或其他 Python 对象,这些统称为字典键。在唯一键值比连续整数更能反映出变量值含义的情况下,这个特点使字典比列表更实用。

  • 在列表中,列表值是隐式排序的,因为索引是连续整数。在字典中,字典值则没有排序,因为索引不仅仅只是数值。你可以为字典中的项目定义排序操作,但是字典确实没有内置排序。

  • 在列表中,为一个不存在的位置(索引)赋值是非法的。在字典中,则可以在必要的时候创建新的位置(键)。

  • 因为没有排序,所以当你进行搜索或添加新值时,字典的响应时间更快(当你插入一个新项目时,计算机不需要重新分配索引值)。当处理的数据越来越多时,这是一个重要的考虑因素。

因为字典在商业应用中使用广泛、灵活性高、作用突出,所以掌握如何在 Python 中使用字典是极其重要的。下面的示例代码演示了最常用的和最有效的用于处理字典的函数和操作符的使用方法。

  1. 创建字典

    # 使用花括号创建字典
    # 用冒号分隔键-值对
    # 用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)))
    
    

    这个示例展示了创建字典的方法。要创建一个空字典,将字典名称放在等号左侧,在等号右侧放上一对花括号即可。

    示例中的第二个字典 a_dict 演示了向字典中添加键和值的一种方法。a_dict 说明键和值是用冒号隔开的,花括号之间的键-值对则是用逗号隔开的。字典键是用单引号或双引号围住的字符串,字典值可以是字符串、数值、列表、其他字典或其他 Python 对象。在 a_dict 中,字典值是整数,但是在 another_dict 中,字典值是字符串、数值和列表。最后,这个示例说明了 len 函数返回的是字典中键-值对的个数。

  2. 引用字典中的值

    # 使用键来引用字典中特定的值
    print("Output #106: {}".format(a_dict['two']))
    print("Output #107: {}".format(another_dict['z']
    
    

    要引用字典中一个特定的值,需要使用字典名称、一对方括号和一个特定的键值(一个字符串)。在这个示例中,a_dict['two'] 的结果是整数 2,another_dict['z'] 的结果是列表 ['star', 'circle', 9]

  3. 复制

    # 使用copy()复制一个字典
    a_new_dict = a_dict.copy()
    print("Output #108: {}".format(a_new_dict))
    
    

    要复制一个字典,先在字典名称后面加上 copy 函数,然后将这个表达式赋给一个新的字典即可。在这个示例中,a_new_dict 是字典 a_dict 的一个副本。

  4. 键、值和项目

    # 使用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()))
    
    

    要引用字典的键值,在字典名称后面加上 keys 函数即可。这个表达式的结果是包含字典键值的一个列表。要引用字典值,在字典名称后面加上 values 函数即可。这个表达式的结果是包含字典值的一个列表。

    要想同时引用字典的键和值,在字典名称后面加上 items 函数即可。结果是一个列表,其中包含的是键-值对形式的元组。例如,a_dict.items() 的结果是 [('three', 3), ('two', 2), ('one', 1)]。在 1.4.8 节中会介绍如何使用 for 循环来对字典中的所有键和值进行解包和引用。

  5. 使用innot inget

    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')))
    
    

    这个示例展示了测试字典中是否存在某个键值的两种方法。第一种方法是使用 if 语句、innot in 以及字典名称。使用 inif 语句来测试 y 是否是 another_dict 中的一个键值。如果语句结果为真(也就是说如果 yanother_dict 中的一个键值),那么就执行 print 语句;否则,就不执行。这种 if inif not in 语句经常用于测试是否存在某个键值,和其他语句一起使用时,还可以为字典添加新的键值。本书后续章节会有添加键值的示例。

    缩进的作用

    你应该注意到 if 语句后面的行是缩进的。Python 使用缩进来表示一个语句是否属于一个逻辑上的“块”,也就是说,如果 if 语句判断为 True,那么 if 语句后面所有缩进的代码都要被执行,然后 Python 解释器再继续下一步工作。你会发现这种缩进还会在随后讨论的其他逻辑块中出现,现在你需要记住的是,在 Python 中缩进是有明确意义的,你必须遵循这个原则。如果你使用 IDE 或者像 Sublime Text 这样的编辑器,软件会帮助你设置每次使用制表符都相当于按了一定次数的空格键;如果你使用像 Notepad 一样的普通文本编辑器,那么就要注意使用同样数目的空格表示同一级别的缩进(一般使用 4 个空格)。

    最后需要注意的是,有时候文本编辑器会使用制表符代替空格,这样程序看上去没有问题,但还是会收到一条错误信息(像“Unexpected indent in line 37”),这就会令程序员非常抓狂。尽管有这个问题,一般来说,Python 使用缩进还是会使代码更加清晰易读,因为你可以轻松地理解程序的逻辑过程。

    第二种测试具体键值的方式是使用 get 函数,这个函数也可以按照键值取得相应的字典值。和前一种测试键值的方式不同,如果字典中存在这个键,get 函数就返回键值对应的字典值;如果字典中不存在这个键,则返回 None。此外,get 函数还可以使用第二个参数,表示如果字典中不存在键值时函数的返回值。通过这种方式,如果字典中不存在该键值,可以返回除 None 之外的一些其他内容。

  6. 排序

    # 使用sorted()对字典进行排序
    # 要想对字典排序的同时不修改原字典
    # 先复制字典
    print("Output #119: {}".format(a_dict))
    dict_copy = a_dict.copy()
    ordered_dict1 = sorted(dict_copy.items(), key=lambda item: item[
    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))
    
    

    这个示例展示了如何使用不同方式对字典进行排序。本节开头就说过,字典没有隐含排序,但是,你可以使用前面的代码片段对一个字典对象进行排序。可以按照字典的键或字典值来排序,如果这些值是数值型的,排序方式可以是升序,也可以是降序。

    这个示例中使用了 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)。

1.4.8 控制流

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

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

  1. 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")
    
    

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

  2. 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")
    
    

    第二个示例展示了一个稍微复杂一点的 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。因为这里已经使用了等号作为对象赋值操作符,所以用两个等号(==)判断是否相等。如果 ifelif 代码块都不为真,那么就执行 else 代码块中的 print 语句。

  3. for循环

    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))
    
    

    这 4 个 for 循环示例演示了如何使用 for 循环在序列中迭代。在本书后面的章节,以及一般的商业应用中,这种功能都非常重要。第一个 for 循环示例展示了基本语法,即 for variable in sequence,做某事。variable 是一个临时占位符,表示序列中的各个值,并且只在 for 循环中有意义。在这个示例中,变量名为 monthsequence 是你要进行迭代的序列的名称。同样在这个示例中,序列名为 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 循环中的 keyvalue 变量依次捕获这些值。 for 循环中的 print 语句在每一行打印出一个键-值对,键和值之间以逗号隔开。

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

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

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

    # 使用列表生成式选择特定的行
    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))
    
    

    在这个示例中,列表生成式的意义是:对于 my_data 中的每一行,如果这行中索引位置 2 的值(即第三个值)大于 5,则保留这一行。因为 6 和 9 都大于 5,所以 rows_to_keep 中的列表子集为 [4, 5, 6][7, 8, 9]

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

    # 使用集合生成式在列表中选择出一组唯一的元组
    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))
    
    

    在这个示例中,集合生成式的意义是:对于 my_data 中的每个元组,如果它是一个唯一的元组,则保留这个元组。你可以称这个表达式为集合生成式而不是列表生成式,因为表达式中是花括号,不是方括号,而且它也不是字典生成式,因为它没有使用键-值对这样的语法。

    这个示例中的第二个 print 语句说明你可以通过 Python 内置的 set 函数达到和集合生成式同样的效果。在这个例子中,使用内置的 set 函数更好,因为它比集合生成式更精炼,而且更易读。

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

    # 使用字典生成式选择特定的键-值对
    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))
    
    

    在这个例子中,字典生成式的意义为:对于 my_dictionay 中的每个键-值对,如果值大于 10,则保留这个键-值对。因为值 11 大于 10,所以保留在 my_results 中的键-值对为 {'customer3':11}

  5. while循环

    print("Output #134:")
    x = 0
    while x < 11:
        print("{!s}".format(x))
        x += 1
    
    

    这个示例展示了如何使用 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 循环。

  6. 函数

    在一些情况下,你会发现自己编写函数比使用 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))
    
    

    这个示例展示了如何创建函数来计算一系列数值的均值。函数名为 getMean。圆括号之间的短语表示要传入函数中的数值序列,这是一个仅在函数作用范围内有意义的变量。在函数内部,由序列的总和除以序列中数值的个数计算出序列的均值。此外,还可以使用 if-else 语句来检验序列中是否包含数值。如果确实包含数值,函数返回序列均值。如果不包含数值,函数返回 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 函数。但是,如果你想完成业务过程中特有的任务,知道如何去创建函数还是非常重要的。

  7. 异常

    编写一个强壮稳健的程序的一个重要方面就是有效地处理错误和异常。在编写程序时,你可能会隐含地假设程序要处理的数据类型和数据结构,如果有数据违反了你的假设,就会使程序抛出错误。

    Python 中包含了若干种内置的异常对象。常用的异常包括 IOErrorIndexErrorKeyErrorNameErrorSyntaxErrorTypeErrorUnicodeErrorValueError。你可以在网上获得更多的异常信息,参见 Python 标准库中的“Built-in Exceptions”那一节(http://docs.python.org/3/library/exceptions.html)。你可以使用 try-except 来构筑处理错误信息的第一道防线,即使数据不匹配,你的程序还可以继续运行。

    下面展示了两种使用 try-except 代码块来有效地捕获和处理异常的方法(一种比较短,另一种比较长)。这两个示例修改了上一节的函数示例,来说明如何使用 try-except 代码块代替 if 语句处理空列表的情况。

  8. 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))
    
    

    在这种处理异常的方法中,函数 getMean() 中没有检验序列是否包含数值的 if 语句。如果序列是空的,就像列表 my_list2 一样,那么调用这个函数会导致一个异常 ZeroDivisionError

    要想使用 try-except 代码块,需要将你要执行的代码放在 try 代码块中,然后,使用 except 代码块来处理潜在的错误并打印出错误信息来帮助你理解程序错误。在某些情况下,异常具有特定的值。你可以通过在 except 行中加上 as 短语来引用异常值,然后打印出你为异常指定的变量。因为 my_list2 中不包含任何数值,所以执行 except 代码块,打印出 nanError: float division by zero

  9. try-except-else-finally

    # 完整形式
    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"
    
    

    这个完整形式的异常处理方法除了 tryexcept 代码块,还包含 elsefinally 代码块。如果 try 代码块成功执行,则会接着执行 else 代码块。因此,如果传递给 try 代码块中的 getMean() 函数的数值序列中包含任意数值,那么这些数值的均值就会被赋给 try 代码块中的变量 result,然后接着执行 else 代码块。例如,如果这里使用程序代码 +my_list1+,就会打印出 The mean is: 5.0。因为 my_list2 不包含任何数值,所以执行 except 代码块,打印出 nanError: float division by zero。然后,总是执行 finally 模块,打印出 The finally block is executed every time

1.5 读取文本文件

数据几乎无一例外地是被保存在文件中的。这些文件可能是文本文件、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.5.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.

{%}

图 1-10:Notepad++ 中的文本文件 file_to_read.txt(Windows)

(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

{%}

图 1-11:Python 脚本和它要在命令行窗口中处理的文本文件

这样,你就在 Python 中读取了一个文本文件。你会看到下面的内容被打印到屏幕上,在以前的输出之后(图 1-12):

I'm
already
much
better
at
Python.

{%}

图 1-12:first_script.py 的输出,在命令行窗口中处理文本文件

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

因为 first_script.py 和 file_to_read.txt 在同一位置,即都在桌面上,所以简单地输入 python first_script.py file_to_read.txt 是可以的。如果文本文件和脚本不在同一位置,就需要输入文本文件的完整路径名,这样脚本才能知道去哪里寻找这个文件。

例如,如果文本文件在你的 Documents 文件夹中,而不是在桌面上,那么你可以在命令行中使用下面的路径名来从其所处位置读取文本文件:

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

1.5.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 来访问并打印一个文本文件中的内容。这是一个简单的示例,但在后面的示例中,要以此为基础访问其他类型的文件,或一次访问多个文件,并向输出文件中写入内容。

下一节介绍 glob 模块,它让你能够通过几行代码读取和处理多个输入文件。glob 模块之所以功能强大,是因为它处理的是文件夹(也就是说,它处理目录,不是单个的文件),所以将前面读取文件的代码删除或注释掉,这样就可以使 argv[1] 指向一个文件夹,而不是一个文件了。将代码注释掉就是在你希望计算机忽略掉的代码前面加上一个井号,所以当你结束注释时,first_script.py 文件就应该像下面这样:

## 读取一个文本文件(旧方法) ##
#input_file = sys.argv[1]
#print("Output #143:")
#filereader = open(input_file, 'r', newline='')
#for row in filereader:
# print("{}".format(row.strip()))
#filereader.close()
## 读取一个文本文件(新方法) ##
#input_file = sys.argv[1]
#print("Output #144:")
#with open(input_file, 'r', newline='') as filereader:
# for row in filereader:
# print("{}".format(row.strip()))

做完这些修改之后,你就可以添加下一节要讨论的 glob 代码来处理多个文件了。

1.6 使用glob读取多个文本文件

在很多商业应用中,需要对多个文件进行同样的或相似的处理。例如,你可能会从多个文件中选择数据子集,根据多个文件计算像总计和均值这样的统计量,或根据来自于多个文件的数据子集计算统计量。当文件数量增加时,手动处理文件的可能性会减小,出错的概率会增加。

读取多个文件的一种方法是在命令行中将包含输入文件目录的路径名写在 Python 脚本名称之后。要使用这种方法,你需要在脚本开头导入内置的 os 模块和 glob 模块。在脚本上方添加了 import osimport 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 行(参见图 1-13):

This
text
comes
from
a
different
text
file.

{%}

图 1-13:Notepad++ 中的文本文件 another_file_to_read.txt

(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"

{%}

图 1-14:命令行窗口中的 Python 脚本和指向包含文本文件的桌面文件夹的路径

这样,你就在 Python 中读取了多个文本文件。你会看到以下内容被打印到屏幕上,在以前的输出之后(图 1-15):

This
text
comes
from
a
different
text
file.
I'm
already
much
better
at
Python.

{%}

图 1-15:first_script.py 的输出,在命令行窗口中处理多个文本文件

学会这项技术的一个巨大好处是它可以规模化扩展。这个示例只是处理两个文本文件,但是它可以轻松地扩展为处理几十、几百或者几千甚至更多的文件。学习了如何使用 glob.glob 函数,仅花费手动处理的一小部分时间,就可以处理非常非常多的文件。

1.7 写入文本文件

迄今为止,大多数示例还是使用 print 语句将输出发送到命令行窗口或终端窗口。当你在调试程序,或者在检查输出的准确度时,将输出打印到屏幕上是有意义的。但是,在很多情况下,只要你能确定输出是正确的,就会需要将输出写入文件,以进行更进一步的分析、报告和存储。

Python 提供了两种简单的方法来将输出写入文本文件和分隔符文件。write 方法可将单个字符串写入一个文件,writelines 方法可将一系列字符串写入一个文件。下面的两个示例使用 range 函数和 len 函数跟踪一个列表中的索引值,以将分隔符放在各个列表值之间,并在最后一个列表值后面放上一个换行符。

1.7.1 向first_script.py添加代码

(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 语句可以使你对列表中的最后一个字母做出与前面那些字母不同的处理。if-else 语句是这样工作的:my_letters 包含 10 个元素,但是索引从 0 开始,所以各个字母的索引值分别是 0、1、2、3、4、5、6、7、8、9。因此,my_letters[0]amy_letters[9]jif 代码块判断索引值 x 是否小于 9,max_index - 1 或者是 10 - 1 = 9。直到列表中的最后一个字母,这个条件才为 True。因此,if 代码块的意义是:一直到列表中的最后一个字母,都向输出文件中写入字母,并在字母后面加一个制表符。当你到达了列表中的最后一个字母时,这个字母的索引值为 9,不大于 9,所以 if 代码块判断为 False,就执行 else 代码块。else 代码块中的 write 语句的意义是:向输出文件中写入最后一个字母,并在后面加一个换行符。

(2) 将前面读取多个文件的代码注释掉。

为了看到这些代码是如何工作的,这里需要写入一个文件然后查看输出。因为你又一次使用了 argv[1] 来确定输出文件的路径名,所以需要将前面的 glob 代码删除或注释掉,这样就可以使用 argv[1] 来确定输出文件了。如果选择注释掉前面的 glob 代码,那么 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()))

(3) 重新保存 first_script.py。

(4) 要写入一个文本文件,输入下面的代码,如图 1-16 所示,然后按回车键:

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

{%}

图 1-16:应该在命令行窗口中输入的 Python 脚本、文件路径和输出文件名

(5) 打开输出文件 write_to_file.txt。

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

{%}

图 1-17:输出文件 write_to_file.txt,由 first_script.py 在桌面上创建

下一个示例与这个很相似,只是它演示了如何使用 str 函数来将元素转换为字符串,以便使用 write 函数将其写入一个文件。它还演示了使用 'a'(追加)方式将输出追加到一个已经存在的输出文件末尾的方法。

1.7.2 写入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 中的数值,以逗号隔开,并在末尾有一个换行符,如图 1-18 所示。

{%}

图 1-18:桌面上的输出文件 write_to_file.txt,由 first_script.py 追加了信息

最后,这个示例演示了一个写入 CSV 文件的有效方法。实际上,在前面的例子中,你将由制表符分隔的数据写入了输出文件,如果将制表符改为逗号,并且将输出文件命名为 write_to_file.csv 而不是 write_to_file.txt 的话,就可以创建一个 CSV 文件。

1.8 print语句

print 语句非常有助于程序调试。你已经看到,本章中很多示例程序都使用 print 语句作为输出。但是,你还可以在代码中临时添加 print 语句来检查中间结果。如果你的代码运行不了或者不能产生你需要的结果,那么可以在程序开头适当的地方添加 print 语句,看看最初的计算是否符合你的预期。如果符合,就继续检查下面的代码,看看它们是否按照你的预期工作。

从脚本开头进行检查,可以保证你找出第一个出错的地方并在检查余下的代码之前进行修复。本节的中心思想就是:不要吝啬在程序中使用 print 语句,它可以帮助你调试代码并保证代码正确。当你确信代码正确之后,完全可以将 print 语句注释掉或删除。

这一章介绍了很多基础知识,包括如何导入模块、基本的数据类型和与之相关的函数和方法、模式匹配、print 语句、日期处理、控制流、函数、异常、读取单个或多个文件,以及写入文本文件和分隔符文件。如果你一直跟随本章中的示例进行练习,那么你已经写了 500 多行 Python 代码了!

练习本章中示例代码的最大好处是,它们是编写更复杂的文件处理和数据操作程序的基础。熟练掌握了本章中的示例代码,你就可以理解并掌握本书后续章节介绍的各种技术。

1.9 本章练习

练习答案在附录 B 中。

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

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

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

目录