Python 入门指南:从新手到大师(一)
一、即时黑魔法:基础知识
是时候开始黑了。在这一章中,你将学习如何通过说一种电脑能理解的语言来控制你的电脑:Python。这里没有什么特别难的,所以如果你知道你的计算机如何工作的基本原理,你应该能够跟随例子并自己尝试。我将介绍一些基础知识,从极其简单的开始,但是因为 Python 是一种如此强大的语言,所以您很快就能做相当高级的事情。
首先,您需要安装 Python,或者验证您已经安装了 Python。如果你运行的是 macOS 或者 Linux/UNIX,打开一个终端(Mac 上的终端应用),输入python,然后回车。您应该会收到一条欢迎消息,以下面的提示结束:
>>>
如果这样做了,您可以立即开始输入 Python 命令。但是,请注意,您可能有旧版本的 Python。如果第一行以Python 2而不是Python 3开头,您可能还是想安装一个更新的版本,因为 Python 3 引入了几个突破性的变化。
安装过程的细节当然会因您的操作系统和首选安装机制而异,但最直接的方法是访问 www.python.org ,在那里您应该可以找到下载页面的链接。这一切都是不言自明的——只需通过链接找到您的平台的最新版本,无论是 Windows、macOS、Linux/UNIX 还是其他平台。对于 Windows 和 Mac,您将下载一个安装程序,您可以运行它来实际安装 Python。对于 Linux/UNIX,您需要按照附带的说明自己编译源代码。如果你正在使用一个包管理器,比如 Homebrew 或者 APT,你可以用它来简化这个过程。
一旦你安装了 Python,试着启动交互式解释器。如果您使用命令行,您可以简单地使用python命令,或者如果您已经安装了旧版本,也可以使用python3。如果您更喜欢使用图形界面,可以启动 Python 安装附带的空闲应用。
交互式解释器
当您启动 Python 时,您会得到类似如下的提示:
Python 3.5.0 (default, Dec 5 2015, 15:03:35)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.1.76)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
解释器的确切外观及其错误消息将取决于您使用的版本。这可能看起来不太有趣,但是相信我,它很有趣。这是你通往黑魔法世界的大门,是你控制电脑的第一步。用更实际的术语来说,它是一个交互式 Python 解释器。为了看看它是否有效,请尝试以下方法:
>>> print("Hello, world!")
当您按下 Enter 键时,会出现以下输出:
Hello, world!
>>>
如果你熟悉其他计算机语言,你可能习惯于用分号结束每一行。在 Python 中不需要这样做。一条线就是一条线,或多或少。如果你愿意,你可以添加一个分号,但它不会有任何效果(除非在同一行上有更多的代码),而且它不是一个常见的事情。
这里发生了什么?那个东西就是提示。你可以在这个空间写点东西,比如print "Hello, world!"。如果您按 Enter 键,Python 解释器会打印出字符串“Hello,world!”你会在下面看到一个新的提示。
如果你写的是完全不同的东西呢?尝试一下:
>>> The Spanish Inquisition
SyntaxError: invalid syntax
>>>
显然,翻译不明白这一点。 2 (如果运行的是 IDLE 以外的解释器,比如 Linux 的命令行版本,错误信息会略有不同。)解释器还指出了错误:它将通过给西班牙语一个红色背景来强调这个单词(或者,在命令行版本中,通过使用一个脱字符号,^)。
如果你喜欢,多和翻译玩玩。为了获得一些指导,尝试在提示符下输入命令help()并按 Enter 键。您可以按 F1 键获取有关空闲的帮助。否则,让我们继续努力。毕竟,当你不知道告诉它什么的时候,解释器就没什么意思了。
有什么事吗?。。怎么了?
在我们开始认真编程之前,我会试着给你一个什么是计算机编程的概念。简单地说,它告诉计算机做什么。电脑可以做很多事情,但是它们不太擅长独立思考。他们真的需要被灌输细节。你需要用计算机能理解的语言输入一个算法。算法只是一个程序或配方的花哨词汇——对如何做某事的详细描述。请考虑以下几点:
SPAM with SPAM, SPAM, Eggs, and SPAM: First, take some SPAM.
Then add some SPAM, SPAM, and eggs.
If a particularly spicy SPAM is desired, add some SPAM.
Cook until done -- Check every 10 minutes.
这不是最奇特的食谱,但它的结构很有启发性。它由一系列要按顺序执行的指令组成。有些指示可以直接完成(“吃一些垃圾邮件”),而有些则需要一些深思熟虑(“如果想要一份特别辣的垃圾邮件”),而有些则必须重复几次(“每 10 分钟检查一次。”)
配方和算法由配料(对象、事物)和指令(语句)组成。在这个例子中,垃圾邮件和鸡蛋是配料,而指令包括添加垃圾邮件,烹饪给定的时间长度,等等。让我们从一些相当简单的 Python 成分开始,看看可以用它们做些什么。
数字和表达式
交互式 Python 解释器可以用作强大的计算器。尝试以下方法:
>>> 2 + 2
这应该会给你答案 4。这并不难。好吧,那这个呢:
>>> 53672 + 235253
288925
还是没印象?诚然,这是相当标准的东西。(我假设你已经用了足够多的计算器,知道1 + 2 * 3和(1 + 2) * 3之间的区别。)所有常用的算术运算符都按预期工作。除法产生十进制数,称为浮点数(或浮点数)。
>>> 1 / 2
0.5
>>> 1 / 1
1.0
如果你宁愿舍弃小数部分而做整数除法,你可以用双斜线。
>>> 1 // 2
0
>>> 1 // 1
1
>>> 5.0 // 2.4
2.0
在 Python 的旧版本中,整数的普通除法曾经像这个双斜线一样工作。如果您正在使用 Python 2.x,您可以通过将以下语句添加到程序的开头(编写完整的程序将在后面描述)或在交互式解释器中简单地执行它来获得适当的划分:
>>> from __future__ import division
Note
如果不完全清楚,指令中的future两边用两个下划线包围:_ _future_ _。
另一种方法是,如果您从命令行运行旧的 Python,提供命令行开关-Qnew。在本章后面的“回到 __ 未来 _ _”一节中有关于__future__的更详细的解释。
现在你已经看到了基本的算术运算符(加、减、乘、除),但是我没有提到整数除法的近亲。
>>> 1 % 2
1
这是余数(模数)运算符。x % y给出x除以y的余数。换句话说,就是用整数除法时剩下的部分。也就是说,x % y和x - ((x // y) * y)是一样的。
>>> 10 // 3
3
>>> 10 % 3
1
>>> 9 // 3
3
>>> 9 % 3
0
>>> 2.75 % 0.5
0.25
这里的10 // 3是3,因为结果是向下舍入的。但是 3 × 3 是 9,所以你得到 1 的余数。当你用 9 除以 3 时,结果正好是 3,没有四舍五入。因此,余数为 0。如果你想像本章前面的食谱那样“每 10 分钟”检查一次,这可能是有用的。你可以简单地检查minute % 10是否为 0。(有关如何操作的描述,请参阅本章后面的边栏“预览:if 语句”。)正如您在最后一个例子中看到的,余数操作符同样适用于浮点数。它甚至适用于负数,这可能会有点混乱。
>>> 10 % 3
1
>>> 10 % -3
-2
>>> -10 % 3
2
>>> -10 % -3
-1
看着这些例子,它的工作原理可能不是很明显。如果看看整数除法的伴生运算,大概就更容易理解了。
>>> 10 // 3
3
>>> 10 // -3
-4
>>> -10 // 3
-4
>>> -10 // -3
3
考虑到除法是如何工作的,就不难理解余数是多少了。关于整数除法,需要理解的重要一点是,它是向下舍入的,对于负数来说是远离零的。这意味着-10 // 3被向下舍入到-4,而不是向上舍入到-3。
我们要看的最后一个运算符是幂运算符。
>>> 2 ** 3
8
>>> -3 ** 2
-9
>>> (-3) ** 2
9
请注意,求幂运算符比求反运算符(一元减号)绑定得更紧,因此-3**2实际上与-(3**2)相同。如果你想计算(-3)**2,你必须明确地说出来。
十六进制八进制和二进制
为了结束这一节,我应该提到十六进制、八进制和二进制数是这样写的:
>>> 0xAF
175
>>> 010
8
>>> 0b1011010010
722
这两个数字的第一个数字都是零。(如果你不知道这是怎么回事,你可能还不需要这个。把它归档以备后用。)
变量
另一个你可能很熟悉的概念是变量。如果代数只是遥远的记忆,不要担心:Python 中的变量很容易理解。变量是代表(或引用)某个值的名称。例如,您可能希望名称x代表3。为此,只需执行以下命令:
>>> x = 3
这就是所谓的任务。我们将值3赋给变量x。换句话说,我们将变量x绑定到值(或对象)3。给变量赋值后,可以在表达式中使用该变量。
>>> x * 2
6
与其他一些语言不同,在将变量绑定到某个东西之前,不能使用它。没有“默认值”
Note
简单来说,Python 中的名称或标识符由字母、数字和下划线字符(_)组成。它们不能以数字开头,所以Plan9是有效的变量名,而9Plan不是。 3
声明
到目前为止,我们一直(几乎)专门研究表情,也就是食谱的成分。但是陈述——指令呢?
事实上,我作弊了。我已经介绍了两种类型的语句:print语句和赋值语句。语句和表达式有什么区别?你可以这样想:一个表达式是一些东西,而一个语句做一些事情。例如,2 * 2是4,而print(2 * 2)打印的是4。这两者的行为非常相似,所以它们之间的区别可能不是那么明显。
>>> 2 * 2
4
>>> print(2 * 2)
4
只要在交互式解释器中执行,就没有区别,但那只是因为解释器总是打印出所有表达式的值(使用与repr相同的表示——见本章后面的“字符串表示,str 和 repr”一节)。一般来说,Python 不是这样的。在本章的后面,你将会看到如何让程序在没有这个交互提示的情况下运行;简单地在你的程序中放一个像2 * 2这样的表达式不会有任何有趣的效果。 4 然而把print(2 * 2)放在那里,仍然会打印出4。
Note
实际上,print是一个函数(这一章后面会详细介绍),所以我所说的print语句只是一个函数调用。在 Python 2.x 中,print有自己的语句类型,并且不在参数周围使用括号。
在处理赋值的时候,语句和表达式的区别更加明显。因为它们不是表达式,所以它们没有可以由交互式解释器打印出来的值。
>>> x = 3
>>>
您只是立即得到一个新的提示。然而,有些事情已经改变了。我们现在有了一个新的变量x,它现在被绑定到值3。从某种程度上来说,这是一种定义性的陈述:它们改变事物。例如,赋值改变变量,而print语句改变你的屏幕外观。
赋值可能是任何编程语言中最重要的语句类型,尽管现在可能很难理解它们的重要性。变量可能看起来只是临时的“存储”(就像烹饪食谱中的锅碗瓢盆),但变量的真正力量在于,你不需要知道它们持有什么值才能操纵它们。 5
例如,你知道x * y等于x和y的乘积,即使你可能不知道x和y是什么。所以,你可能会编写以不同方式使用变量的程序,而不知道程序运行时它们最终会保存(或引用)的值。
从用户那里获得输入
您已经看到,您可以在不知道变量的值的情况下编写带有变量的程序。当然,解释者最终必须知道这些值。所以我们怎么可能不知道呢?翻译只知道我们告诉它的东西,对吗?不一定。
你可能写了一个程序,别人可能会用。您无法预测用户将为程序提供什么样的价值。我们来看看有用的函数input。(一会儿我会对函数有更多的说明。)
>>> input("The meaning of life: ")
The meaning of life: 42
'42'
这里发生的是在交互式解释器中执行第一行(input(...))。它打印出字符串"The meaning of life: "作为新的提示。我输入42,然后按回车键。input的结果值就是那个数字(作为一段文本或字符串),它自动打印在最后一行。使用int将字符串转换成整数,我们可以构造一个稍微有趣一点的例子:
>>> x = input("x: ")
x: 34
>>> y = input("y: ")
y: 42
>>> print(int(x) * int(y))
1428
这里,Python 提示符处的语句(>>>)可能是一个已完成程序的一部分,输入的值(34和42)将由某个用户提供。然后你的程序会打印出值1428,它是两者的乘积。当你写程序的时候,你不需要知道这些值,对吗?
Note
当您将程序保存在一个单独的文件中以便其他用户可以执行它们时,像这样获得输入会有用得多。在本章后面的“保存和执行程序”一节中,你会学到如何做到这一点
Sneak Peek: The if Statement
为了增加一点趣味,我将让你先睹为快一些你在第五章之前不应该了解的东西:语句。如果给定的条件为真,那么if语句允许您执行一个动作(另一个语句)。一种类型的条件是相等测试,使用相等运算符==。是的,这是一个双等号。(单人的是用来做作业的,记得吗?)
您将这个条件放在单词if之后,然后用冒号将其与下面的语句分开。
>>> if 1 == 2: print('One equals two')
...
>>> if 1 == 1: print('One equals one')
...
One equals one
>>>
当条件为假时,什么都不会发生。然而,当它为真时,冒号后面的语句(在本例中是一个print语句)被执行。还要注意,在交互式解释器中使用if语句时,需要在执行之前按两次 Enter 键。(原因将在第五章中变得清楚。)
因此,如果变量time被绑定到以分钟为单位的当前时间,您可以使用下面的语句来检查您是否“在整点”:
if time % 60 == 0: print('On the hour!')
功能
在“数字和表达式”一节中,我使用了指数运算符(**)来计算幂。事实上,您可以使用一个名为pow的函数来代替。
>>> 2 ** 3
8
>>> pow(2, 3)
8
函数就像一个小程序,你可以用它来执行一个特定的动作。Python 有很多函数,可以做很多奇妙的事情。事实上,您也可以创建自己的函数(稍后会详细介绍);所以我们经常把pow等标准函数称为内置函数。
像我在前面的例子中所做的那样使用一个函数叫做调用函数。您向它提供参数(在本例中是2和3),它向您返回值。因为它返回一个值,函数调用只是另一种类型的表达式,就像本章前面讨论的算术表达式一样。 6 事实上,你可以结合函数调用和运算符来创建更复杂的表达式(就像我之前对int所做的那样)。
>>> 10 + pow(2, 3 * 5) / 3.0
10932.666666666666
像这样的数字表达式中可以使用几个内置函数。例如,abs给出一个数的绝对值,round将浮点数舍入到最接近的整数。
>>> abs(-10)
10
>>> 2 // 3
0
>>> round(2 / 3)
1.0
注意最后两个表达式的区别。整数除法总是向下舍入,而round舍入到最接近的整数,平局则舍入到偶数。但是如果你想把一个给定的数字四舍五入呢?例如,您可能知道一个人 32.9 岁,但您想将其四舍五入到 32 岁,因为她实际上还没有 33 岁。Python 为此提供了一个函数(称为floor)——只是不能直接使用。和许多有用的函数一样,它可以在一个模块中找到。
模块
您可能认为模块是可以导入 Python 来扩展其功能的扩展。您可以用一个名为(很自然)import的特殊命令导入模块。上一节提到的函数floor位于一个名为math的模块中。
>>> import math
>>> math.floor(32.9)
32
注意这是如何工作的:我们用import导入一个模块,然后通过编写module.function来使用该模块中的函数。特别是对于这个操作,你实际上可以把这个数字转换成一个整数,就像我之前做的那样,使用来自input的结果。
>>> int(32.9)
32
Note
还有类似的函数可以转换成其他类型(例如,str和float)。事实上,这些并不是真正的函数——它们是类。稍后我会有更多关于课程的内容。
然而,math模块还有其他几个有用的功能。比如floor的反义词是ceil(“天花板”的简称),求大于等于给定数的最小整数值。
>>> math.ceil(32.3)
33
>>> math.ceil(32)
32
如果您确定不会用给定的名称(从不同的模块)导入多个函数,您可能不想在每次调用函数时都写模块名。然后,您可以使用import命令的变体。
>>> from math import sqrt
>>> sqrt(9)
3.0
使用from module import function后,可以使用不带模块前缀的功能。
Tip
事实上,您可以使用变量来引用函数(以及 Python 中的大多数其他东西)。通过执行赋值foo = math.sqrt,可以开始使用foo计算平方根;例如,foo(4)产出2.0。
cmath 和复数
sqrt函数用于计算一个数的平方根。让我们看看如果给它提供一个负数会发生什么:
>>> from math import sqrt
>>> sqrt(-1)
Traceback (most recent call last): ...
ValueError: math domain error
或者,在某些平台上:
>>> sqrt(-1)
nan
Note
nan只是一个特殊值,意思是“不是数字”
如果我们把自己限制在实数及其浮点数形式的近似实现上,我们就不能求负数的平方根。负数的平方根是所谓的虚数,实数和虚数之和称为复数。Python 标准库有一个单独的处理复数的模块。
>>> import cmath
>>> cmath.sqrt(-1)
1j
注意,我在这里没有使用from ... import ...。如果我有,我就失去了我普通的sqrt。像这样的名字冲突可能是偷偷摸摸的,所以除非你真的想使用from版本,否则你可能应该坚持使用普通的import。
值1j是虚数的一个例子。这些数字后面有一个j(或J)。复杂的算术基本上是根据定义1j为-1的平方根得出的。在不深入探讨这个话题的情况下,让我来展示最后一个例子:
>>> (1 + 3j) * (9 + 4j)
(-3 + 31j)
如您所见,这种语言内置了对复数的支持。
Note
Python 中没有单独的虚数类型。它们被视为实部为零的复数。
回到 __ 未来 _ _ _
有传言说吉多·范·罗苏姆(Python 的创造者)有一台时间机器——不止一次,当人们请求该语言的特性时,他们发现这些特性已经被实现了。当然,我们并不都被允许进入这个时间机器,但是 Guido 已经很好地将它的一部分以魔法模块__future__的形式构建到 Python 中。从中,我们可以导入将来会成为 Python 标准的特性,但这些特性还不是该语言的一部分。你在“数字和表达式”部分看到了这一点,在本书中你会不时碰到它。
保存和执行您的程序
交互式解释器是 Python 的一大优势。它使得实时测试解决方案和语言实验成为可能。如果你想知道某样东西是如何工作的,试一试就知道了!但是,当您退出时,您在交互式解释器中写的所有内容都将丢失。你真正想做的是编写你和其他人都能运行的程序。在本节中,您将学习如何做到这一点。
首先,你需要一个文本编辑器,最好是用于编程的。(如果你用的是微软 Word 之类的东西,我真的不太推荐,一定要把你的代码存成纯文本。)如果你已经在用 IDLE 了,那你就走运了。使用 IDLE,您可以简单地用文件新建文件创建一个新的编辑器窗口。将出现另一个窗口,但没有交互式提示。咻!首先输入以下内容:
print("Hello, world!")
现在选择 File Save 保存您的程序(实际上,它是一个纯文本文件)。一定要把它放在以后可以找到的地方,并给你的文件取一个合理的名字,比如hello.py。(.py结局意义重大。)
明白了吗?不要关闭有你的程序的窗口。如果有,只需再次打开它(文件打开)。现在,您可以使用运行模块来运行它。(如果您没有使用 IDLE,请参见下一节关于从命令提示符运行程序的内容。)
会发生什么?Hello, world!被打印在解释器窗口中,这正是我们想要的。解释器提示符可能消失了(取决于您使用的版本),但您可以通过按 Enter 键(在解释器窗口中)恢复它。
让我们将脚本扩展如下:
name = input("What is your name? ")
print("Hello, " + name + "!")
如果您运行这个命令(记得首先保存它),您应该在解释器窗口中看到以下提示:
What is your name?
输入您的姓名(例如,Gumby)并按回车键。您应该得到这样的结果:
Hello, Gumby!
Turtle Power!
print语句对于基本示例很有用,因为它几乎在任何地方都有效。如果你想尝试视觉上更有趣的输出,你应该看看turtle模块,它实现了所谓的海龟图形。如果你有空闲和运行,turtle模块应该工作得很好,它让你画数字,而不是打印文本。虽然这是一种通常应该小心的做法,但是在玩海龟图形的时候,简单地从模块中导入所有的名字会很方便。
from turtle import *
一旦你弄清楚了你需要哪些函数,你就可以回到只导入那些函数的状态。
海龟图形的想法源于真正的海龟机器人,它们可以前后移动,并向左或向右转动给定的角度。此外,他们还携带一支笔,可以上下移动来确定笔是否接触到他们正在移动的纸张。turtle模块给你这样一个机器人的模拟。例如,你可以这样画一个三角形:
forward(100)
left(120)
forward(100)
left(120)
forward(100)
如果你运行这个,应该会出现一个新的窗口,有一个小箭头形状的“海龟”在移动,后面拖着一条线。要让它提起笔,你用penup(),再放下笔,pendown()。更多命令,请参考 Python 库参考( https://docs.python.org/3/library/turtle.html )的相关章节,对于绘图想法,请尝试在网上搜索海龟图形。当你学习更多的概念时,你可能想用海龟来代替更普通的print例子。玩海龟图形很快证明了我将向您展示的一些基本编程结构的必要性。(例如,你如何避免重复前面例子中的forward和left命令?比如说,你会怎么画一个八边形而不是三角形?或者几个边数不同的正多边形,代码行尽量少?)
从命令提示符运行 Python 脚本
实际上,有几种方法可以运行你的程序。首先,让我们假设您面前有一个 DOS 窗口或 UNIX shell 提示符,并且包含 Python 可执行文件的目录(在 Windows 中称为python.exe,在 UNIX 中称为python)或包含可执行文件的目录(在 Windows 中)已经放在您的PATH环境变量中。 7 同样,让我们假设上一节的脚本(hello.py)在当前目录中。然后,您可以在 Windows 中使用以下命令执行您的脚本:
C:\>python hello.py
或 UNIX:
$ python hello.py
如您所见,命令是相同的。只有系统提示改变。
让你的脚本像普通程序一样运行
有时,您希望像执行其他程序(如 web 浏览器或文本编辑器)一样执行 Python 程序(也称为脚本),而不是显式使用 Python 解释器。在 UNIX 中,有一种标准的方法:让脚本的第一行以字符序列#!(称为 pound bang 或 shebang)开始,后跟解释脚本的程序的绝对路径(在我们的例子中是 Python)。即使您不太明白这一点,如果您想让它在 UNIX 上轻松运行,只需在脚本的第一行中输入以下内容:
#!/usr/bin/env python
无论 Python 二进制文件位于何处,这都应该运行脚本。如果您安装了不止一个版本的 Python,您可以使用更具体的可执行文件名称,比如python3,而不是简单的python。
在实际运行脚本之前,您必须使其可执行。
$ chmod a+x hello.py
现在它可以这样运行(假设您的路径中有当前目录):
$ hello.py
如果这不起作用,请尝试使用./hello.py来代替,即使当前目录(.)不在您的执行路径中(一个负责任的系统管理员可能会告诉您这不应该),这也会起作用。
如果你愿意,你可以重命名你的文件,去掉后缀py,让它看起来更像一个普通的程序。
双击呢?
在 Windows 中,后缀(.py)是让你的脚本像程序一样运行的关键。尝试双击您在上一节中保存的文件hello.py。如果 Python 安装正确,会出现一个 DOS 窗口,提示“你叫什么名字?”然而,这样运行你的程序有一个问题。一旦你输入了你的名字,程序窗口会在你阅读结果之前关闭。程序完成时,窗口关闭。尝试通过在末尾添加以下行来更改脚本:
input("Press <enter>")
现在,在运行程序并输入您的名字后,您应该有一个包含以下内容的 DOS 窗口:
What is your name? Gumby
Hello, Gumby!
Press <enter>
一旦你按下回车键,窗口关闭(因为程序已经完成)。
评论
散列符号(#)在 Python 中有点特殊。当你把它放入你的代码时,它右边的所有东西都被忽略了(这就是为什么 Python 解释器没有被之前使用的/usr/bin/env卡住)。这里有一个例子:
# Print the circumference of the circle:
print(2 * pi * radius)
这里的第一行被称为注释,它有助于让程序更容易理解——当你回到旧代码时,对其他人和你自己都是如此。据说程序员的第一条戒律是“你应该评论”(尽管一些不太仁慈的程序员发誓说“如果很难写,就应该很难读”)。确保你的评论是有意义的,不要简单地重复代码中已经很明显的内容。无用的、多余的评论可能比没有更糟糕。例如,在下面的例子中,并不真正需要注释:
# Get the user's name:
user_name = input("What is your name?")
即使没有注释,让代码本身也是可读的,这总是一个好主意。幸运的是,Python 是编写可读程序的优秀语言。
用线串
那些东西是怎么回事?本章的第一个程序很简单
print("Hello, world!")
在编程教程中,习惯上是从这样的程序开始。问题是我还没有真正解释它是如何工作的。你知道print语句的基本知识(我稍后会对此进行更多的说明),但是什么是"Hello, world!"?它被称为字符串(如“一串字符”)。几乎每一个有用的、真实世界的 Python 程序中都有字符串,并且有许多用途。它们的主要用途是表示文本,例如感叹词“你好,世界!”
单引号字符串和转义引号
字符串是值,就像数字一样:
>>> "Hello, world!"
'Hello, world!'
不过,这个例子中有一点可能有点令人惊讶:当 Python 打印出我们的字符串时,它使用了单引号,而我们使用了双引号。有什么区别?其实没什么区别。
>>> 'Hello, world!'
'Hello, world!'
这里我们用单引号,结果是一样的。那么,为什么两者都允许呢?因为在某些情况下可能有用。
>>> "Let's go!"
"Let's go!"
>>> '"Hello, world!" she said'
'"Hello, world!" she said'
在前面的代码中,第一个字符串包含一个单引号(或者一个撇号,在这个上下文中我们应该这样称呼它),因此我们不能用单引号将字符串括起来。如果我们这样做了,翻译会抱怨(这是理所当然的)。
>>> 'Let's go!'
SyntaxError: invalid syntax
在这里,字符串是'Let',Python 不太知道如何处理后面的s(或者说,该行的其余部分)。
在第二个字符串中,我们使用双引号作为句子的一部分。因此,我们必须用单引号将字符串括起来,原因与前面所述相同。或者,实际上我们不必。只是方便而已。另一种方法是使用反斜杠字符(\)来转义字符串中的引号,如下所示:
>>> 'Let\'s go!'
"Let's go!"
Python 知道中间的单引号是字符串中的一个字符,而不是字符串的结尾。(尽管如此,Python 还是选择在打印字符串时使用双引号。)如你所料,双引号也是如此。
>>> "\"Hello, world!\" she said"
'"Hello, world!" she said'
像这样的转义引用可能是有用的,有时也是必要的。例如,如果您的字符串既包含单引号又包含双引号,就像在字符串'Let\'s say "Hello, world!"'中一样,如果没有反斜杠,您会怎么做?
Note
厌倦了反斜杠?正如你将在本章后面看到的,你可以通过使用长字符串和原始字符串(可以组合)来避免它们中的大多数。
串联字符串
只是为了继续鞭笞这个有点折磨人的例子,让我给你看另一种写同样字符串的方法:
>>> "Let's say " '"Hello, world!"'
'Let\'s say "Hello, world!"'
我简单地写了两个字符串,一个接一个,Python 自动将它们连接起来(使它们成为一个字符串)。这种机制不常使用,但有时会很有用。然而,只有当你实际上同时写两个字符串,直接一个接一个的时候,它才起作用。
>>> x = "Hello, "
>>> y = "world!"
>>> x y
SyntaxError: invalid syntax
换句话说,这只是编写字符串的一种特殊方式,而不是连接字符串的通用方法。那么,如何连接字符串呢?就像你添加数字一样:
>>> "Hello, " + "world!"
'Hello, world!'
>>> x = "Hello, "
>>> y = "world!"
>>> x + y
'Hello, world!'
字符串表示,str 和 repr
在这些例子中,您可能已经注意到 Python 打印出的所有字符串仍然是带引号的。这是因为它打印出的值可能是用 Python 代码编写的,而不是用户希望的样子。但是,如果使用print,结果就不同了。
>>> "Hello, world!"
'Hello, world!'
>>> print("Hello, world!")
Hello, world!
如果我们加入特殊的换行符代码\n,这种差异会更加明显。
>>> "Hello,\nworld!"
'Hello,\nworld!'
>>> print("Hello,\nworld!")
Hello,
world!
值通过两种不同的机制转换为字符串。您可以通过使用函数str和repr来访问这两种机制。 9 使用str,你可以以某种用户可能理解的合理方式将一个值转换成一个字符串,例如,在可能的情况下,将任何特殊的字符代码转换成相应的字符。但是,如果使用repr,通常会得到合法 Python 表达式形式的值表示。
>>> print(repr("Hello,\nworld!"))
'Hello,\nworld!'
>>> print(str("Hello,\nworld!"))
Hello,
world!
长字符串、原始字符串和字节
有一些有用的、稍微专门化的编写字符串的方法。例如,有一个自定义的语法用于编写包含换行符(长字符串)或反斜杠(原始字符串)的字符串。在 Python 2 中,也有一个单独的语法来编写带有不同种类的特殊符号的字符串,产生unicode类型的对象。语法仍然有效,但现在是多余的,因为 Python 3 中的所有字符串都是 Unicode 字符串。相反,引入了一种新的语法来指定一个bytes对象,大致相当于老式的字符串。正如我们将看到的,这些在 Unicode 编码的处理中仍然扮演着重要的角色。
采油套管
如果你想写一个很长的字符串,跨越几行,你可以用三重引号代替普通引号。
print('''This is a very long string. It continues here.
And it's not over yet. "Hello, world!"
Still here.''')
也可以用三重双引号,"""like this"""。请注意,由于独特的括起的引号,单引号和双引号都允许包含在内,而不会被反斜杠转义。
Tip
普通的字符串也可以跨几行。如果一行的最后一个字符是反斜杠,换行符本身会被“转义”并被忽略。例如:
print("Hello, \ world!")
会打印出Hello, world!。一般来说,表达式和语句也是如此。
>>> 1 + 2 + \
4 + 5
12
>>> print \
('Hello, world')
Hello, world
原始字符串
Raw strings 对反斜杠不太挑剔,有时候会非常有用。 10 在普通的字符串中,反斜杠有一个特殊的作用:它转义东西,让你把平时不能直接写的东西放到你的字符串中。例如,正如我们所见,一个换行符被写成\n并可以被放入一个字符串中,如下所示:
>>> print('Hello,\nworld!')
Hello,
world!
这通常很好,但在某些情况下,这不是你想要的。如果您希望字符串包含一个反斜杠,后跟一个n会怎么样?你可能想把 DOS 路径名C:\nowhere放到一个字符串中。
>>> path = 'C:\nowhere'
>>> path
'C:\nowhere'
这看起来是正确的,直到你打印出来并发现它的缺陷。
>>> print(path)
C:
owhere
这不是我们想要的,对吧?那我们该怎么办?我们可以对反斜杠本身进行转义。
>>> print('C:\\nowhere')
C:\nowhere
这很好。但是对于长路径,你会有很多反斜线。
path = 'C:\\Program Files\\fnord\\foo\\bar\\baz\\frozz\\bozz'
在这种情况下,原始字符串非常有用。他们根本不把反斜杠当作特殊字符。你放入原始字符串的每个字符都保持你写的样子。
>>> print(r'C:\nowhere')
C:\nowhere
>>> print(r'C:\Program Files\fnord\foo\bar\baz\frozz\bozz')
C:\Program Files\fnord\foo\bar\baz\frozz\bozz
如您所见,原始字符串带有前缀r。看起来你可以在一个原始字符串中放入任何东西,这几乎是真的。引号必须照常转义,尽管这意味着在最后的字符串中也会有一个反斜杠。
>>> print(r'Let\'s go!')
Let\'s go!
原始字符串中唯一不能有的是一个单独的最后一个反斜杠。换句话说,原始字符串中的最后一个字符不能是反斜杠,除非您对它进行转义(然后您用来转义它的反斜杠也将是字符串的一部分)。鉴于前面的例子,这应该是显而易见的。如果最后一个字符(在最后一个引号之前)是一个未转义的反斜杠,Python 不知道是否结束字符串。
>>> print(r"This is illegal\")
SyntaxError: EOL while scanning string literal
好的,这是合理的,但是如果你希望原始字符串的最后一个字符是反斜杠呢?(例如,这可能是 DOS 路径的结尾。)好了,在这一节中我已经给了你一整套技巧,应该可以帮助你解决那个问题,但是基本上你需要把反斜杠放在一个单独的字符串中。下面是一个简单的方法:
>>> print(r'C:\Program Files\foo\bar' '\\')
C:\Program Files\foo\bar\
请注意,您可以对原始字符串使用单引号和双引号。甚至三重引号字符串也可能是原始的。
Unicode、bytes 和 bytearray
Python 字符串使用一种称为 Unicode 的方案来表示文本。对于大多数 basic 程序来说,这种工作方式是相当透明的,所以如果你愿意,你现在可以跳过这一节,根据需要阅读这个主题。然而,由于字符串和文本文件处理是 Python 代码的主要用途之一,所以至少浏览一下这一部分可能不会有什么坏处。
抽象地说,每个 Unicode 字符都由一个所谓的码位表示,码位就是它在 Unicode 标准中的编号。这使你能够以任何现代软件都可以识别的方式引用 129 种书写系统中的 120,000 多个字符。当然,您的键盘不会有成千上万个键,所以有一些通用的机制来指定 Unicode 字符,要么通过 16 位或 32 位十六进制文字(分别以\u或\U作为前缀),要么通过它们的 Unicode 名称(使用\N{ name })。
>>> "\u00C6"
'Æ'
>>> "\U0001F60A"
'☺'
>>> "This is a cat: \N{Cat}"
'This is a cat:
你可以通过搜索网页找到各种代码点和名称,使用你需要的字符的描述,或者你可以使用一个特定的网站,如 http://unicode-table.com 。
Unicode 的概念非常简单,但是它带来了一些挑战,其中之一就是编码问题。所有对象在内存或磁盘上都表示为一系列二进制数字——0 和 1——以八个字节或字节为一组,字符串也不例外。在像 C 这样的编程语言中,这些字节是完全公开的。字符串只是字节序列。例如,为了与 C 互操作,以及将文本写入文件或通过网络套接字发送,Python 有两种类似的类型,不可变的bytes和可变的bytearray。如果你愿意,你可以使用前缀b直接产生一个字节对象,而不是一个字符串:
>>> b'Hello, world!'
b'Hello, world!'
然而,一个字节只能容纳 256 个值,比 Unicode 标准所要求的要少得多。Python bytes文字只允许 ASCII 标准的 128 个字符,剩余的 128 个字节值需要转义序列,如十六进制值0xf0(即240)的\xf0。
似乎这里唯一的区别是我们可用的字母表的大小。然而,这并不准确。乍一看,ASCII 和 Unicode 似乎都指非负整数和字符之间的映射,但有一个微妙的区别:Unicode 码位被定义为整数,而 ASCII 字符是由数字和二进制编码定义的。这看起来完全不起眼的一个原因是,整数 0–255 和一个八位二进制数之间的映射是完全标准的,几乎没有回旋的余地。问题是,一旦我们超越了单字节,事情就没那么简单了。简单地将每个码位表示为相应的二进制数的直接概括可能不合适。不仅存在字节顺序的问题(即使在对整数值进行编码时也会遇到这一问题),还存在空间浪费的问题:如果我们对每个代码点使用相同数量的字节进行编码,那么所有的文本都必须适应这样一个事实,即您可能希望包含一些安纳托利亚象形文字或一点帝国阿拉姆语。这种 Unicode 编码有一个标准,称为 UTF-32(Unicode 转换格式 32 位),但是如果您主要处理的是互联网上一种更常见语言的文本,这就相当浪费了。
然而,有一个绝对聪明的选择,主要是由计算机先驱肯·汤普逊设计的。它不使用完整的 32 位,而是使用可变编码,一些脚本的字节数比其他的少。假设你会更经常地使用这些脚本,这将节省你的空间,就像莫尔斯电码通过使用更少的点和破折号来节省你的精力一样。 11 特别是,ASCII 编码仍然用于单字节编码,保留了与旧系统的兼容性。但是,超出此范围的字符使用多个字节(最多六个)。让我们尝试使用 ASCII、UTF-8 和 UTF-32 编码将字符串编码成字节。
>>> "Hello, world!".encode("ASCII")
b'Hello, world!'
>>> "Hello, world!".encode("UTF-8")
b'Hello, world!'
>>> "Hello, world!".encode("UTF-32")
b'\xff\xfe\x00\x00H\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00,\x00\x00\x00 \x00\x00\x00w\x00\x00\x00o\x00\x00\x00r\x00\x00\x00l\x00\x00\x00d\x00\x00\x00!\x00\x00\x00'
如你所见,前两个是相等的,而最后一个要长得多。这是另一个例子:
>>> len("How long is this?".encode("UTF-8"))
17
>>> len("How long is this?".encode("UTF-32"))
72
一旦我们使用一些稍微奇特的字符,ASCII 和 UTF-8 之间的区别就显现出来了:
>>> "Hællå, wørld!".encode("ASCII")
Traceback (most recent call last):
...
UnicodeEncodeError: 'ascii' codec can't encode character '\xe6' in position 1: ordinal not in range(128)
这里的斯堪的纳维亚字母在 ASCII 中没有编码。如果我们真的需要 ASCII 编码(这肯定会发生),我们可以向encode提供另一个参数,告诉它如何处理错误。这里的正常模式是'strict',但是您可以使用其他模式来忽略或替换违规字符。
>>> "Hællå, wørld!".encode("ASCII", "ignore")
b'Hll, wrld!'
>>> "Hællå, wørld!".encode("ASCII", "replace")
b'H?ll?, w?rld!'
>>> "Hællå, wørld!".encode("ASCII", "backslashreplace")
b'H\\xe6ll\\xe5, w\\xf8rld!'
>>> "Hællå, wørld!".encode("ASCII", "xmlcharrefreplace")
b'Hællå, wørld!'
不过,在几乎所有情况下,使用 UTF-8 会更好,事实上这甚至是默认的编码。
>>> "Hællå, wørld!".encode()
b'H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!'
这比"Hello, world!"示例稍长,而 UTF-32 编码在两种情况下长度完全相同。
就像字符串可以编码成字节一样,字节也可以解码成字符串。
>>> b'H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!'.decode()
'Hællå, wørld!'
和以前一样,默认编码是 UTF-8。我们可以指定一个不同的编码,但是如果我们使用了错误的编码,我们要么得到一个错误消息,要么得到一个乱码字符串。对象本身不知道编码,所以你有责任跟踪你使用了哪一种编码。
与其使用encode和decode方法,不如简单地构造bytes和str(即字符串)对象,如下所示:
>>> bytes("Hællå, wørld!", encoding="utf-8")
b'H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!'
>>> str(b'H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!', encoding="utf-8")
'Hællå, wørld!'
如果您不确切知道您正在处理的类似字符串或类似字节的对象的类,那么使用这种方法会更通用一些,并且作为一般规则,您不应该对此太严格。
编码和解码最重要的用途之一是将文本存储在磁盘上的文件中。然而,Python 读写文件的机制通常会为您完成这项工作!只要你能接受 UTF-8 编码的文件,你就不需要担心这个问题。但是,如果您最终在应该是文本的地方看到了乱码,也许文件实际上是其他编码的,那么了解一下正在发生的事情会很有用。如果您想了解更多关于 Python 中 Unicode 的知识,请查看该主题的指南。 12
Note
你的源代码也是编码的,默认也是 UTF 8。如果您想使用其他编码(例如,如果您的文本编辑器坚持保存为除 UTF-8 以外的其他格式),您可以用特殊的注释来指定编码。
# -*- coding: encoding name -*-
将encoding name替换为您正在使用的任何编码(大写或小写),例如utf-8或者更有可能是latin-1。
最后,我们有bytearray,它是bytes的可变版本。在某种意义上,它就像一个可以修改字符的字符串——这是普通字符串做不到的。然而,它实际上更多地被设计成在幕后使用,如果作为类似字符串使用,就不太用户友好。例如,要替换一个字符,你必须给它分配一个在 0…255 范围内的int。所以如果你想真正插入一个字符,你必须得到它的序数值,使用ord。
>>> x = bytearray(b"Hello!")
>>> x[1] = ord(b"u")
>>> x
bytearray(b'Hullo!')
快速总结
这一章涵盖了相当多的材料。在继续之前,让我们看看你学到了什么。
- 算法:算法是告诉你如何执行一项任务的处方。当你给计算机编程时,你实际上是在用计算机能理解的语言描述一种算法,比如 Python。这种机器友好的描述称为程序,它主要由表达式和语句组成。
- 表达式:表达式是代表一个值的计算机程序的一部分。例如,
2 + 2是一个表达式,代表值4。简单表达式是通过使用运算符(如+或%)和函数(如pow)从文字值(如2或"Hello")构建的。更复杂的表达式可以通过组合更简单的表达式来创建(例如(2 + 2) * (3 - 1))。表达式也可能包含变量。 - 变量:变量是代表一个值的名称。可以通过
x = 2之类的赋值将新值赋给变量。作业是一种陈述。 - 语句:语句是告诉计算机做某事的指令。这可能涉及到改变变量(通过赋值),把东西打印到屏幕上(比如
print("Hello, world!")),导入模块,或者做许多其他的事情。 - 函数:Python 中的函数就像数学中的函数一样:它们可以接受一些参数,然后返回一个结果。(在返回之前,它们实际上可能会做很多有趣的事情,当你在第六章中学习编写自己的函数时,你会发现这一点。)
- 模块:模块是可以导入 Python 来扩展其功能的扩展。例如,
math模块中有几个有用的数学函数。 - 程序:您已经看到了编写、保存和运行 Python 程序的实用性。
- 字符串:字符串非常简单——它们只是一些文本,用 Unicode 码表示字符。然而关于它们还有很多需要了解的。在这一章中,你已经看到了许多编写它们的方法,在第三章中,你将学到许多使用它们的方法。
本章的新功能
| 功能 | 描述 | | --- | --- | | `abs(number)` | 返回一个数字的绝对值。 | | `bytes(string, encoding[, errors])` | 用指定的错误行为对给定的字符串进行编码。 | | `cmath.sqrt(number)` | 返回平方根;适用于负数。 | | `float(object)` | 将字符串或数字转换为浮点数。 | | `help([object])` | 提供交互式帮助。 | | `input(prompt)` | 以字符串形式从用户处获取输入。 | | `int(object)` | 将字符串或数字转换为整数。 | | `math.ceil(number)` | 以浮点数形式返回一个数的上限。 | | `math.floor(number)` | 以浮点形式返回数字的下限。 | | `math.sqrt(number)` | 返回平方根;对负数不起作用。 | | `pow(x, y[, z])` | 返回 x 的 y 次方(以 z 为模)。 | | `print(object, ...)` | 打印参数,用空格分隔。 | | `repr(object)` | 返回值的字符串表示形式。 | | `round(number[, ndigits])` | 将数字舍入到给定的精度,并舍入到偶数。 | | `str(object)` | 将值转换为字符串。如果从`bytes`转换,您可以指定编码和错误行为。 |方括号中的参数是可选的。
什么现在?
现在你已经知道了表达式的基础,让我们继续学习更高级的东西:数据结构。与处理简单的值(比如数字)不同,您将看到如何将它们组合成更复杂的结构,比如列表和字典。此外,您将进一步了解字符串。在第五章中,你会学到更多关于语句的知识,之后你就可以编写一些真正漂亮的程序了。
Footnotes 1
黑魔法攻击不同于破解,破解是一个描述计算机犯罪的术语。两者经常混淆,用法也在逐渐变化。正如我在这里所使用的,黑魔法的基本意思是“一边编程一边享受乐趣”
2
毕竟,没人指望西班牙宗教裁判所。。。
3
稍微简单一点的是,标识符名称的规则部分基于 Unicode 标准,正如在 https://docs.python.org/3/reference/lexical_analysis.html 的 Python 语言参考中所记录的。
4
如果你想知道——是的,它确实做了一些事情。它计算 2 和 2 的乘积。但是,结果不会保存在任何地方,也不会显示给用户;除了计算本身,它没有任何副作用。
5
请注意关于存储的报价。值不是存储在变量中的——它们存储在计算机内存的某个阴暗的深处,由变量引用。随着阅读的深入,你会发现不止一个变量可以引用同一个值。
6
如果您只是忽略返回值,函数调用也可以用作语句。
7
如果你不理解这句话,你也许应该跳过这一节。你其实不需要。
8
这种行为取决于您的操作系统和安装的 Python 解释器。例如,如果你已经在 macOS 中使用 IDLE 保存了文件,双击该文件就可以在 IDLE 代码编辑器中打开它。
9
其实,str是一个类,然而就像int. repr是一个函数一样。
10
在编写正则表达式时,原始字符串可能特别有用。你可以在第十章中了解更多。
11
这通常是一种重要的压缩方法,例如在霍夫曼编码中使用,霍夫曼编码是几种现代压缩工具的组成部分。
12
https://docs.python.org/3/howto/unicode.html见。
二、列表和元组
本章介绍了一个新概念:数据结构。数据结构是以某种方式构造的数据元素(例如数字或字符,甚至其他数据结构)的集合,例如通过对元素进行编号。Python 中最基本的数据结构是序列。序列中的每个元素都被分配了一个数字,即它的位置或索引。第一个索引是零,第二个索引是一,依此类推。一些编程语言从 1 开始给它们的序列元素编号,但是零索引约定有一个从序列开始的偏移的自然解释,负索引绕到结尾。如果你觉得这种编号有点奇怪,我可以保证你很快就会习惯。
本章从序列的概述开始,然后涵盖所有序列共有的一些操作,包括列表和元组。这些操作也适用于字符串,字符串将在一些例子中使用,尽管要完整地处理字符串操作,你必须等到下一章。处理完这些基础知识后,我们开始处理列表,看看它们有什么特别之处。在列表之后,我们来到元组,一种特殊用途的序列类型,类似于列表,除了你不能改变它们。
序列概述
Python 有几种内置的序列类型。本章集中讨论两个最常见的:列表和元组。字符串是另一种重要的类型,我将在下一章再次讨论。
列表和元组的主要区别在于,你可以改变列表,但不能改变元组。这意味着,如果您需要在进行过程中添加元素,列表可能是有用的,而如果出于某种原因,您不允许序列改变,元组可能是有用的。后者的原因通常是技术性的,与 Python 内部的工作方式有关。这就是为什么你可能会看到内置函数返回元组。对于你自己的程序,几乎在所有情况下你都可以用列表代替元组。(如第四章所述,一个显著的例外是使用元组作为字典键。这些列表是不允许的,因为不允许你修改键。)
当您想要处理值的集合时,序列非常有用。您可能有一个序列代表数据库中的一个人,第一个元素是他们的名字,第二个元素是他们的年龄。写成一个列表(列表中的项目用逗号分隔,并用方括号括起来),如下所示:
>>> edward = ['Edward Gumby', 42]
但是序列也可以包含其他序列,所以你可以列出这样的人,这就是你的数据库。
>>> edward = ['Edward Gumby', 42]
>>> john = ['John Smith', 50]
>>> database = [edward, john]
>>> database
[['Edward Gumby', 42], ['John Smith', 50]]
Note
Python 有一种称为容器的数据结构的基本概念,容器基本上是可以包含其他对象的任何对象。两种主要的容器是序列(比如列表和元组)和映射(比如字典)。虽然序列的元素是编号的,但是映射中的每个元素都有一个名称(也称为键)。在第四章中你会学到更多关于映射的知识。关于既不是序列也不是映射的容器类型的例子,请参见第十章中对集合的讨论。
常见顺序操作
您可以对所有序列类型执行某些操作。这些操作包括索引、切片、加法、乘法和成员检查。此外,Python 具有用于查找序列长度以及查找其最大和最小元素的内置函数。
Note
这里没有提到的一个重要操作是迭代。迭代序列意味着重复执行某些操作,对序列中的每个元素重复一次。要了解更多信息,请参见第五章中的“循环”一节。
索引
序列中的所有元素都从零开始向上编号。您可以使用一个数字单独访问它们,如下所示:
>>> greeting = 'Hello'
>>> greeting[0]
'H'
Note
字符串只是一系列字符。索引 0 指的是第一个元素,在本例中是字母 h。但是,与其他一些语言不同,它没有单独的字符类型。字符只是一个单元素字符串。
这就是所谓的索引。您使用索引来获取元素。所有的序列都可以用这种方式索引。当使用负索引时,Python 从右边开始计数,即从最后一个元素开始计数。最后一个元素位于位置–1。
>>> greeting[-1]
'o'
字符串文字(和其他序列文字,就此而言)可以直接索引,而不使用变量来引用它们。效果完全一样。
>>> 'Hello'[1]
'e'
如果函数调用返回一个序列,你可以直接索引它。例如,如果您只是对用户输入的年份的第四位数字感兴趣,您可以这样做:
>>> fourth = input('Year: ')[3]
Year: 2005
>>> fourth
'5'
清单 2-1 包含一个示例程序,它询问您年份、月份(从 1 到 12 的数字)和日期(1 到 31),然后打印出带有正确月份名称的日期等等。
# Print out a date, given year, month, and day as numbers
months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]
# A list with one ending for each number from 1 to 31
endings = ['st', 'nd', 'rd'] + 17 * ['th'] \
+ ['st', 'nd', 'rd'] + 7 * ['th'] \
+ ['st']
year = input('Year: ')
month = input('Month (1-12): ')
day = input('Day (1-31): ')
month_number = int(month)
day_number = int(day)
# Remember to subtract 1 from month and day to get a correct index
month_name = months[month_number-1]
ordinal = day + endings[day_number-1]
print(month_name + ' ' + ordinal + ', ' + year)
Listing 2-1.Indexing Example
使用此程序的会话示例如下:
Year: 1974
Month (1-12): 8
Day (1-31): 16
August 16th, 1974
最后一行是程序的输出。
限幅
正如您使用索引来访问单个元素一样,您也可以使用切片来访问一定范围的元素。您可以通过使用两个索引来实现,用冒号分隔。
>>> tag = '<a href="http://www.python.org">Python web site</a>'
>>> tag[9:30]
'http://www.python.org'
>>> tag[32:-4]
'Python web site'
如您所见,切片对于提取部分序列非常有用。这里的编号非常重要。第一个索引是要包含的第一个元素的编号。但是,最后一个索引是切片后第一个元素的编号。请考虑以下几点:
>>> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> numbers[3:6] [4, 5, 6]
>>> numbers[0:1] [1]
简而言之,您提供两个指数作为切片的限制,其中第一个是包含性的,第二个是排他性的。
一条漂亮的捷径
假设您想要访问数字的最后三个元素(来自前面的示例)。当然,你可以明确地这样做。
>>> numbers[7:10]
[8, 9, 10]
现在,索引 10 引用了元素 11——它并不存在,但在您想要的最后一个元素之后一步。明白了吗?如果你想从末尾开始数,你可以使用负指数。
>>> numbers[-3:-1]
[8, 9]
然而,看起来你不能这样访问最后一个元素。用 0 作为终点“一步之遥”的元素怎么样?
>>> numbers[-3:0]
[]
这不完全是想要的结果。事实上,任何时候一个切片中最左边的索引在序列中比第二个晚(在这种情况下,倒数第三个比第一个晚),结果总是一个空序列。幸运的是,您可以使用一个快捷方式:如果切片继续到序列的末尾,您可以简单地省略最后一个索引。
>>> numbers[-3:]
[8, 9, 10]
同样的事情从一开始就起作用。
>>> numbers[:3]
[1, 2, 3]
事实上,如果你想复制整个序列,你可以省去两个索引。
>>> numbers[:]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
清单 2-2 包含一个小程序,提示您输入一个 URL(假设它是相当有限的形式 http://www.somedomainname.com )并提取域名。
# Split up a URL of the form http://www.something.com
url = input('Please enter the URL:')
domain = url[11:-4]
print("Domain name: " + domain)
Listing 2-2.Slicing Example
以下是该程序的运行示例:
Please enter the URL: http://www.python.org
Domain name: python
更长的台阶
切片时,可以指定(显式或隐式)切片的起点和终点。另一个参数通常是隐式的,就是步长。在常规切片中,步长为 1,这意味着切片从一个元素“移动”到下一个元素,返回开始和结束之间的所有元素。
>>> numbers[0:10:1]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
在本例中,您可以看到切片包含另一个数字。正如你可能已经猜到的,这是明确的步长。如果步长设置为大于 1 的数,元素将被跳过。例如,步长为 2 将只包括开始和结束之间间隔的每隔一个元素。
>>> numbers[0:10:2]
[1, 3, 5, 7, 9]
numbers[3:6:3]
[4]
您仍然可以使用前面提到的快捷方式。例如,如果您想要序列中的第四个元素,您只需要提供步长为 4 的元素。
>>> numbers[::4]
[1, 5, 9]
自然地,步长不能为零——这不会让你去任何地方——但是它可以是负的,这意味着从右到左提取元素。
>>> numbers[8:3:-1]
[9, 8, 7, 6, 5]
>>> numbers[10:0:-2]
[10, 8, 6, 4, 2]
>>> numbers[0:10:-2]
[]
>>> numbers[::-2]
[10, 8, 6, 4, 2]
>>> numbers[5::-2]
[6, 4, 2]
>>> numbers[:5:-2]
[10, 8]
把事情做好可能需要一点思考。可以看到,第一个限制(最左边的)仍然是包含性的,而第二个限制(最右边的)是排他性的。当使用负步长时,您需要有一个高于第二个的第一个限制(起始索引)。可能有点令人困惑的是,当您隐式保留开始和结束索引时,Python 会做“正确的事情”——对于正步长,它会从开始向结束移动,对于负步长,它会从结束向开始移动。
添加序列
序列可以用加法(加)运算符连接。
>>> [1, 2, 3] + [4, 5, 6]
[1, 2, 3, 4, 5, 6]
>>> 'Hello,' + 'world!'
'Hello, world!'
>>> [1, 2, 3] + 'world!'
Traceback (innermost last):
File "<pyshell>", line 1, in ?
[1, 2, 3] + 'world!'
TypeError: can only concatenate list (not "string") to list
正如您在错误消息中看到的,您不能连接一个列表和一个字符串,尽管两者都是序列。一般来说,您不能连接不同类型的序列。
增加
将一个序列乘以数字 x 会创建一个新序列,其中原始序列重复 x 次:
>>> 'python' * 5
'pythonpythonpythonpythonpython'
>>> [42] * 10
[42, 42, 42, 42, 42, 42, 42, 42, 42, 42]
无、空列表和初始化
空列表简单地写成两个括号([])——里面什么也没有。如果你想要一个有 10 个元素的列表,但是里面没有任何有用的东西,你可以像以前一样使用[42]*10,或者更实际的使用[0]*10。你现在有一个包含十个零的列表。然而,有时您可能想要一个表示“什么都没有”的值,比如“我们还没有在这里放任何东西。”这时你使用的None. None是一个 Python 值,意思就是“这里什么都没有”所以如果你想初始化一个长度为 10 的列表,你可以这样做:
>>> sequence = [None] * 10
>>> sequence
[None, None, None, None, None, None, None, None, None, None]
清单 2-3 包含一个程序,它打印(到屏幕上)一个由字符组成的“框”,这个框在屏幕上居中,并适应用户提供的句子的大小。代码可能看起来很复杂,但基本上只是算术——计算出需要多少空格、破折号等等,以便正确放置。
# Prints a sentence in a centered "box" of correct width
sentence = input("Sentence: ")
screen_width = 80
text_width = len(sentence)
box_width = text_width + 6
left_margin = (screen_width - box_width) // 2
print()
print(' ' * left_margin + '+' + '-' * (box_width-2) + '+')
print(' ' * left_margin + '| ' + ' ' * text_width + ' |')
print(' ' * left_margin + '| ' + sentence + ' |')
print(' ' * left_margin + '| ' + ' ' * text_width + ' |')
print(' ' * left_margin + '+' + '-' * (box_width-2) + '+')
print()
Listing 2-3.Sequence (String) Multiplication Example
以下是运行示例:
Sentence: He's a very naughty boy!
+-----------------------------+
| |
| He's a very naughty boy! |
| |
+-----------------------------+
成员资格
要检查一个值是否能在序列中找到,可以使用in操作符。这个运算符与目前讨论的有些不同(比如乘法或加法)。它检查某事是否为真,并相应地返回一个值:True为真,False为假。这样的运算符称为布尔运算符,真值称为布尔值。在第五章的条件语句一节中,你会学到更多关于布尔表达式的知识。
下面是一些使用in操作符的例子:
>>> permissions = 'rw'
>>> 'w' in permissions
True
>>> 'x' in permissions
False
>>> users = ['mlh', 'foo', 'bar']
>>> input('Enter your user name: ') in users
Enter your user name: mlh
True
>>> subject = '$$$ Get rich now!!! $$$'
>>> '$$$' in subject
True
前两个例子使用成员测试来检查'w'和'x'是否分别出现在字符串permissions中。这可能是 UNIX 机器上的一个脚本,用于检查文件的写入和执行权限。下一个示例检查提供的用户名(mlh)是否在用户列表中。如果你的程序执行一些安全策略,这可能是有用的。(在这种情况下,您可能也想使用密码。)最后一个例子检查字符串subject是否包含字符串'$$$'。例如,这可以用作垃圾邮件过滤器的一部分。
Note
检查字符串是否包含' $$$ '的示例与其他示例略有不同。一般来说,in操作符检查一个对象是否是一个序列(或其他集合)的成员(即一个元素)。然而,字符串的唯一成员或元素是它的字符。所以,下面的话很有道理:
>>> 'P' in 'Python'
True
事实上,在 Python 的早期版本中,这是唯一处理字符串的成员资格检查——找出一个字符是否在字符串中。现在,您可以使用in操作符来检查任何字符串是否是另一个字符串的子字符串。
清单 2-4 显示了一个程序,它读入一个用户名,并对照一个数据库(实际上是一个列表)检查输入的 PIN 码,该数据库包含成对的(更多的列表)用户名和 PIN 码。如果在数据库中找到名称/PIN 对,则打印字符串'Access granted'。(在第一章中提到了if声明,并将在第五章中进行全面解释。)
# Check a user name and PIN code
database = [
['albert', '1234'],
['dilbert', '4242'],
['smith', '7524'],
['jones', '9843']
]
username = input('User name: ')
pin = input('PIN code: ')
if [username, pin] in database: print('Access granted')
Listing 2-4.Sequence Membership Example
长度、最小值和最大值
内置函数len、min和max可能非常有用。函数len返回一个序列包含的元素数量。min和max分别返回序列的最小和最大元素。(在第五章的“比较操作符”一节中,你会学到更多关于比较对象的知识)
>>> numbers = [100, 34, 678]
>>> len(numbers)
3
>>> max(numbers)
678
>>> min(numbers)
34
>>> max(2, 3)
3
>>> min(9, 3, 2, 5)
2
除了可能的最后两个表达式之外,从前面的解释中应该清楚这是如何工作的。在这些函数中,max和min不是用序列参数调用的;这些数字直接作为参数提供。
列表:Python 的主力
在前面的例子中,我使用了很多列表。您已经看到了它们是多么有用,但是本节讨论的是它们与元组和字符串的不同之处:列表是可变的——也就是说,您可以更改它们的内容——并且它们有许多有用的专用方法。
列表功能
因为字符串不能像列表一样被修改,所以有时从字符串创建列表会很有用。您可以使用list功能来完成此操作。1
>>> list('Hello')
['H', 'e', 'l', 'l', 'o']
注意list适用于所有类型的序列,而不仅仅是字符串。
Tip
要将字符列表(如前面的代码)转换回字符串,可以使用以下表达式:
''.join(somelist)
其中somelist是您的列表。关于这真正含义的解释,参见第三章中关于join的部分。
基本列表操作
您可以对列表执行所有标准的序列操作,如索引、切片、连接和乘法。但是列表的有趣之处在于它们可以被修改。在本节中,您将看到一些更改列表的方法:项目分配、项目删除、切片分配和列表方法。(注意,并不是所有的列表方法都会改变它们的列表。)
更改列表:项目分配
更改列表很容易。你只需使用普通赋值,如第一章所述。但是,您不用编写类似于x = 2的代码,而是使用索引符号来指定一个特定的现有位置,比如x[1] = 2。
>>> x = [1, 1, 1]
>>> x[1] = 2
>>> x
[1, 2, 1]
Note
您不能分配到不存在的职位;如果您的列表长度为 2,则不能为索引 100 赋值。为此,您必须创建一个长度为 101(或更长)的列表。请参阅本章前面的“无、空列表和初始化”一节。
删除元素
从列表中删除元素也很容易。您可以简单地使用del语句。
>>> names = ['Alice', 'Beth', 'Cecil', 'Dee-Dee', 'Earl']
>>> del names[2]
>>> names
['Alice', 'Beth', 'Dee-Dee', 'Earl']
请注意塞西尔是如何完全消失的,列表的长度从五个缩减到四个。del语句可以用来删除列表元素以外的东西。它可以用于字典(见第四章)甚至变量。更多信息,请参见第五章。
分配给切片
切片是一个非常强大的特性,而且您可以为切片赋值,这使得它更加强大。
>>> name = list('Perl')
>>> name
['P', 'e', 'r', 'l']
>>> name[2:] = list('ar')
>>> name
['P', 'e', 'a', 'r']
所以你可以一次分配到几个位置。你可能想知道这有什么大不了的。你就不能一次分配一个给他们吗?当然,但是当您使用片段分配时,您也可以用长度不同于原始长度的序列替换片段。
>>> name = list('Perl')
>>> name[1:] = list('ython')
>>> name
['P', 'y', 't', 'h', 'o', 'n']
切片分配甚至可以用于插入元素,而无需替换任何原始元素。
>>> numbers = [1, 5]
>>> numbers[1:1] = [2, 3, 4]
>>> numbers
[1, 2, 3, 4, 5]
在这里,我基本上“替换”了一个空切片,从而真正插入了一个序列。您可以执行相反的操作来删除切片。
>>> numbers
[1, 2, 3, 4, 5]
>>> numbers[1:4] = []
>>> numbers
[1, 5]
你可能已经猜到了,最后这个例子相当于del numbers[1:4]。(现在为什么不尝试步长不为 1 的切片赋值呢?甚至可能是负面的?)
列出方法
方法是一个与某个对象紧密耦合的函数,可以是一个列表、一个数字、一个字符串或其他任何东西。一般来说,方法是这样调用的:
object.method(arguments)
一个方法调用看起来就像一个函数调用,除了对象放在方法名的前面,用一个点把它们分开。(在第七章中,你会得到关于什么是真正的方法的更详细的解释。)列表有几种方法允许您检查或修改其内容。
附加
方法用于将一个对象附加到列表的末尾。
>>> lst = [1, 2, 3]
>>> lst.append(4)
>>> lst
[1, 2, 3, 4]
你可能想知道为什么我选择了一个如此丑陋的名字作为我的名单。为什么不叫list?我可以这样做,但是您可能还记得,list是一个内置函数。如果我使用列表的名字,我就不能再调用这个函数了。对于给定的应用,您通常可以找到更好的名称。一个名字比如lst真的不能告诉你什么。例如,如果你的列表是一个价格列表,你可能应该称它为prices、prices_of_eggs或pricesOfEggs。
同样重要的是要注意到,append像几个类似的方法一样,就地改变列表。这意味着它不是简单地返回一个新的、修改过的列表;而是直接修改旧的。这通常是您想要的,但有时可能会带来麻烦。当我在本章后面描述sort时,我将回到这个讨论。
清楚的
方法就地清除列表的内容。
>>> lst = [1, 2, 3]
>>> lst.clear()
>>> lst
[]
类似于切片赋值lst[:] = []。
复制
方法复制一个列表。回想一下,普通的赋值只是将另一个名字绑定到同一个列表。
>>> a = [1, 2, 3]
>>> b = a
>>> b[1] = 4
>>> a
[1, 4, 3]
如果你想让a和b成为单独的列表,你必须将b绑定到a的副本上。
>>> a = [1, 2, 3]
>>> b = a.copy()
>>> b[1] = 4
>>> a
[1, 2, 3]
这类似于使用a[:]或list(a),两者都会复制a。
数数
方法统计一个元素在列表中出现的次数。
>>> ['to', 'be', 'or', 'not', 'to', 'be'].count('to')
2
>>> x = [[1, 2], 1, 1, [2, 1, [1, 2]]]
>>> x.count(1)
2
>>> x.count([1, 2])
1
扩展
extend方法允许您通过提供一系列想要追加的值来一次追加几个值。换句话说,你原来的列表被另一个扩展了。
>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> a.extend(b)
>>> a
[1, 2, 3, 4, 5, 6]
这看起来类似于串联,但重要的区别是扩展序列(在本例中为a)被修改了。这与普通的连接不同,在普通的连接中会返回一个全新的序列。
>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> a + b
[1, 2, 3, 4, 5, 6]
>>> a
[1, 2, 3]
如您所见,连接列表看起来与上一个例子中的扩展列表完全相同,但是这次a没有改变。因为普通的连接必须创建一个包含a和b副本的新列表,如果您想要的是这样的内容,那么它就不如使用extend有效:
>>> a = a + b
此外,这不是一个就地操作——它不会修改原始文件。extend的效果可以通过分配给切片来实现,如下所示:
>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> a[len(a):] = b
>>> a
[1, 2, 3, 4, 5, 6]
虽然这种方法有效,但可读性不太好。
指数
index方法用于搜索列表,以找到某个值第一次出现的索引。
>>> knights = ['We', 'are', 'the', 'knights', 'who', 'say', 'ni']
>>> knights.index('who')
4
>>> knights.index('herring')
Traceback (innermost last):
File "<pyshell>", line 1, in ?
knights.index('herring')
ValueError: list.index(x): x not in list
当您搜索单词'who'时,您会发现它位于索引 4。
>>> knights[4]
'who'
但是,当你搜索'herring'时,你会得到一个异常,因为根本找不到这个单词。
插入
方法用于将一个对象插入到一个列表中。
>>> numbers = [1, 2, 3, 5, 6, 7]
>>> numbers.insert(3, 'four')
>>> numbers
[1, 2, 3, 'four', 5, 6, 7]
与extend一样,您可以用片分配来实现insert。
>>> numbers = [1, 2, 3, 5, 6, 7]
>>> numbers[3:3] = ['four']
>>> numbers
[1, 2, 3, 'four', 5, 6, 7]
这可能很奇特,但是它很难像使用insert那样易读。
流行音乐
pop方法从列表中移除一个元素(默认情况下是最后一个)并返回它。
>>> x = [1, 2, 3]
>>> x.pop()
3
>>> x
[1, 2]
>>> x.pop(0)
1
>>> x
[2]
Note
pop 方法是唯一一个既修改列表又返回值的列表方法(除了None)。
使用pop,您可以实现一个称为堆栈的公共数据结构。像这样的一堆工作起来就像一堆盘子。你可以把盘子放在上面,也可以把盘子从上面拿走。最后一个放入堆栈的是第一个被移除的。(这个原则叫做后进先出,或 LIFO。)
两种堆栈操作(放入和取出)通常被称为 push 和 pop。Python 没有 push,但是可以用append代替。pop和append方法反转彼此的结果,所以如果你压入(或追加)刚刚弹出的值,你会得到相同的堆栈。
>>> x = [1, 2, 3]
>>> x.append(x.pop())
>>> x
[1, 2, 3]
Tip
如果你想要一个先进先出(FIFO)队列,你可以用insert(0, ...)代替append。或者,你可以继续使用append,但是用pop(0)代替pop()。更好的解决方案是使用集合模块中的deque。更多信息见第十章。
移动
remove方法用于删除第一次出现的值。
>>> x = ['to', 'be', 'or', 'not', 'to', 'be']
>>> x.remove('be')
>>> x
['to', 'or', 'not', 'to', 'be']
>>> x.remove('bee')
Traceback (innermost last):
File "<pyshell>", line 1, in ?
x.remove('bee')
ValueError: list.remove(x): x not in list
如您所见,只有第一个匹配项被删除,如果它不在列表中,您就不能删除它(在本例中是字符串'bee')。
需要注意的是,这是一种“不返回原位更改”的方法。它修改列表,但不返回任何内容(与pop相反)。
反面的
reverse方法反转列表中的元素。(我想这并不奇怪。)
>>> x = [1, 2, 3]
>>> x.reverse()
>>> x
[3, 2, 1]
注意,reverse改变了列表并且不返回任何东西(例如,就像remove和sort)。
Tip
如果你想反向迭代一个序列,你可以使用reversed函数。不过,这个函数不返回列表;它返回一个迭代器。(你会在第九章学到更多关于迭代器的知识。)可以用list转换返回的对象。
>>> x = [1, 2, 3]
>>> list(reversed(x))
[3, 2, 1]
分类
sort方法用于就地排序列表。 3 排序“就地”意味着改变原始列表,使其元素按排序顺序排列,而不是简单地返回列表的排序副本。
>>> x = [4, 6, 2, 1, 7, 9]
>>> x.sort()
>>> x
[1, 2, 4, 6, 7, 9]
您已经遇到了几个修改列表而不返回任何内容的方法,在大多数情况下,这种行为是很自然的(例如,append)。但是我想在sort的例子中强调一下这种行为,因为很多人似乎被它迷惑了。当用户想要一个列表的排序副本,而不去管原始列表时,通常会出现这种混乱。一种直观(但错误)的方法如下:
>>> x = [4, 6, 2, 1, 7, 9]
>>> y = x.sort() # Don't do this!
>>> print(y)
None
因为sort修改了x但没有返回任何内容,所以最终得到一个排序的x和一个包含None的y。一种正确的做法是首先将y绑定到x的副本,然后对y进行排序,如下所示:
>>> x = [4, 6, 2, 1, 7, 9]
>>> y = x.copy()
>>> y.sort()
>>> x
[4, 6, 2, 1, 7, 9]
>>> y
[1, 2, 4, 6, 7, 9]
简单地将x赋给y是行不通的,因为x和y会引用同一个列表。另一种获得列表排序副本的方法是使用sorted函数。
>>> x = [4, 6, 2, 1, 7, 9]
>>> y = sorted(x)
>>> x
[4, 6, 2, 1, 7, 9]
>>> y
[1, 2, 4, 6, 7, 9]
这个函数实际上可以用在任何序列上,但总是返回一个列表。 4
>>> sorted('Python')
['P', 'h', 'n', 'o', 't', 'y']
如果您想对元素进行逆序排序,您可以使用sort(或sorted),然后调用reverse方法,或者您可以使用reverse参数,如下一节所述。
高级排序
sort方法有两个可选参数:key和reverse。如果您想使用它们,通常通过名称来指定它们(所谓的关键字参数;你会在第六章了解更多。key参数类似于cmp参数:你提供一个函数,它被用在排序过程中。但是,函数不是直接用于确定一个元素是否小于另一个元素,而是用于为每个元素创建一个键,并根据这些键对元素进行排序。因此,举例来说,如果您想根据元素的长度对它们进行排序,您可以使用len作为键函数。
>>> x = ['aardvark', 'abalone', 'acme', 'add', 'aerate']
>>> x.sort(key=len)
>>> x
['add', 'acme', 'aerate', 'abalone', 'aardvark']
另一个关键字参数reverse,仅仅是一个真值(True或False);你将在第五章中了解更多关于这些的内容,指出列表是否应该反向排序。
>>> x = [4, 6, 2, 1, 7, 9]
>>> x.sort(reverse=True)
>>> x
[9, 7, 6, 4, 2, 1]
在sorted函数中也有key和reverse参数。在许多情况下,为key使用自定义函数会很有用。你将在第六章中学习如何定义你自己的函数。
Tip
如果你想了解更多关于排序的内容,你可以看看 https://wiki.python.org/moin/HowTo/Sorting 的“排序小指南”。
元组:不可变序列
元组是序列,就像列表一样。唯一的区别是元组不能改变。(您可能已经注意到了,字符串也是如此。)元组语法很简单——如果用逗号分隔一些值,就会自动得到一个元组。
>>> 1, 2, 3
(1, 2, 3)
正如您所看到的,元组也可能(并且经常)包含在括号中。
>>> (1, 2, 3)
(1, 2, 3)
空元组被写成两个不包含任何内容的括号。
>>> ()
()
因此,您可能想知道如何编写包含单个值的元组。这有点奇怪——即使只有一个值,也必须包含一个逗号。
>>> 42
42
>>> 42,
(42,)
>>> (42,)
(42,)
最后两个例子产生长度为 1 的元组,而第一个例子根本不是元组。逗号至关重要。单纯加括号也无济于事:(42)和42一模一样。然而,一个单独的逗号可以完全改变表达式的值。
>>> 3 * (40 + 2)
126
>>> 3 * (40 + 2,)
(42, 42, 42)
tuple函数的工作方式与list非常相似:它接受一个序列参数并将其转换成一个元组。5 如果自变量已经是元组,则不变返回。
>>> tuple([1, 2, 3])
(1, 2, 3)
>>> tuple('abc')
('a', 'b', 'c')
>>> tuple((1, 2, 3))
(1, 2, 3)
正如您可能已经收集到的,元组并不复杂——除了创建它们和访问它们的元素之外,您真的不能对它们做太多事情,您可以像对其他序列一样做这些事情。
>>> x = 1, 2, 3
>>> x[1]
2
>>> x[0:2]
(1, 2)
元组的片也是元组,就像列表片本身是列表一样。
您需要了解元组有两个重要原因。
- 它们可以用作映射中的键(和集合的成员);列表不能这样使用。你将在第四章学到更多的映射。
- 它们由一些内置的函数和方法返回,这意味着您必须处理它们。只要你不试图去改变它们,“处理”它们最常见的方式就是把它们当成列表一样对待(除非你需要像
index和count这样的方法,而元组是没有的)。
一般来说,列表可能足以满足您的排序需求。
快速总结
让我们回顾一下本章中涉及的一些最重要的概念。
- 序列:序列是一种数据结构,其中的元素被编号(从零开始)。序列类型的例子有列表、字符串和元组。其中,列表是可变的(您可以更改它们),而元组和字符串是不可变的(一旦创建,它们就是固定的)。可以通过切片来访问序列的各个部分,提供两个索引来指示切片的开始和结束位置。要更改列表,可以为其位置指定新值,或者使用赋值来覆盖整个切片。
- 成员资格:用操作符
in检查一个值是否能在一个序列(或其他容器)中找到。对字符串使用in是一个特例——它将允许您查找子字符串。 - 方法:一些内置类型(比如列表和字符串,但不是元组)有许多有用的方法。这些有点像函数,除了它们与一个特定的值紧密相连。方法是面向对象编程的一个重要方面,我们在第七章中会讲到。
本章的新功能
| 功能 | 描述 | | --- | --- | | `len(seq)` | 返回序列的长度 | | `list(seq)` | 将序列转换为列表 | | `max(args)` | 返回一个序列或一组参数的最大值 | | `min(args)` | 返回一个序列或一组参数的最小值 | | `reversed(seq)` | 允许您反向迭代序列 | | `sorted(seq)` | 返回`seq`元素的排序列表 | | `tuple(seq)` | 将序列转换为元组 |现在怎么办?
现在你已经熟悉了序列,让我们继续学习字符序列,也称为字符串。
Footnotes 1
它实际上是一个类,而不是一个函数,但是区别现在并不重要。
2
其实从 Python 2.2 版本开始,list就是一个类型,而不是一个函数。(这也是tuple和str的情况。)关于这个的完整故事,参见第九章的“子类化列表、字典和字符串”一节。
3
如果您感兴趣,从 Python 2.3 开始,sort方法使用稳定的排序算法。
4
实际上,sorted函数可以用在任何可迭代的对象上。在第九章中你会学到更多关于可迭代对象的知识。
5
像list,tuple不是一个真正的函数——它是一种类型。和list一样,你现在可以放心地忽略这一点。