Python 入门指南第二版(二)
原文:
annas-archive.org/md5/4b0fd2cf0da7c8edae4b5ecfd40159bf译者:飞龙
第四章:选择使用 if
如果你能保持头脑冷静
如果你能保持头脑冷静,当周围的一切都在失控
鲁道德·吉卜林,《如果——》
在前面的章节中,你已经看到了许多数据示例,但还没有深入研究过。大多数代码示例都使用交互式解释器,并且很简短。在这一章中,你将学习如何构建 Python 代码,而不仅仅是数据。
许多计算机语言使用诸如花括号 ({ 和 }) 或关键字如 begin 和 end 来标记代码段落。在这些语言中,使用一致的缩进习惯是提高代码可读性的良好实践,不仅适合自己阅读,也便于他人理解。甚至有工具可以帮助你使代码排列整齐。
当他设计成为 Python 的语言时,吉多·范罗苏姆决定使用缩进来定义程序结构,避免使用所有那些括号和花括号。Python 在使用空格来定义程序结构方面非常不同寻常。这是新手注意到的第一个方面,对有其他语言经验的人来说可能会感觉奇怪。但事实证明,使用 Python 一段时间后,这种方式会变得自然,你甚至不再注意到它。你甚至会习惯于在输入更少的情况下做更多事情。
我们最初的代码示例都是单行代码。让我们先看看如何进行注释和多行命令。
使用 # 进行注释
注释 是程序中被 Python 解释器忽略的文本片段。你可以使用注释来澄清附近的 Python 代码,做笔记提醒自己以后修复问题,或者任何你喜欢的目的。通过使用 # 字符标记注释;从该点到当前行末尾的所有内容都是注释。通常你会在单独的一行上看到注释,如下所示:
>>> # 60 sec/min * 60 min/hr * 24 hr/day
>>> seconds_per_day = 86400
或者,在代码同一行上进行注释:
>>> seconds_per_day = 86400 # 60 sec/min * 60 min/hr * 24 hr/day
# 字符有很多名称:井号、sharp、pound 或听起来邪恶的 octothorpe。¹ 无论你如何称呼它,² 它的效果仅限于出现在该行的末尾。
Python 没有多行注释。你需要明确地用 # 开始每一行或每一节注释:
>>> # I can say anything here, even if Python doesn't like it,
... # because I'm protected by the awesome
... # octothorpe.
...
>>>
然而,如果它在文本字符串中,强大的井号将恢复其作为普通旧#字符的角色:
>>> print("No comment: quotes make the # harmless.")
No comment: quotes make the # harmless.
使用 \ 继续多行
当行长度合理时,程序更易读。推荐(非必须)的最大行长度为 80 个字符。如果你无法在这个长度内表达所有想要说的内容,你可以使用续行字符:\(反斜杠)。只需在行尾加上 \,Python 就会认为你仍然在同一行上。
例如,如果我想要添加前五个数字,我可以一行一行地进行:
>>> sum = 0
>>> sum += 1
>>> sum += 2
>>> sum += 3
>>> sum += 4
>>> sum
10
或者,我可以使用续行字符一步到位:
>>> sum = 1 + \
... 2 + \
... 3 + \
... 4
>>> sum
10
如果我们在表达式中跳过中间的反斜杠,我们会得到一个异常:
>>> sum = 1 +
File "<stdin>", line 1
sum = 1 +
^
SyntaxError: invalid syntax
这里有一个小技巧——如果你在成对的括号(或方括号或花括号)中间,Python 不会对行结束发出警告:
>>> sum = (
... 1 +
... 2 +
... 3 +
... 4)
>>>
>>> sum
10
在第五章中,你还会看到成对的三重引号让你创建多行字符串。
与 if、elif 和 else 比较
现在,我们终于迈出了进入编程的第一步,这是一个小小的 Python 程序,检查布尔变量disaster的值,并打印相应的注释:
>>> disaster = True
>>> if disaster:
... print("Woe!")
... else:
... print("Whee!")
...
Woe!
>>>
if和else行是 Python 的语句,用于检查条件(这里是disaster的值)是否为布尔True值,或者可以评估为True。记住,print()是 Python 的内置函数,用于打印东西,通常打印到屏幕上。
注意
如果你在其他语言中编程过,请注意,对于if测试,不需要括号。例如,不要写像if (disaster == True)这样的内容(相等操作符==在几段后面描述)。但是需要在末尾加上冒号(:)。如果像我一样有时会忘记输入冒号,Python 会显示错误消息。
每个print()行在其测试下缩进。我使用四个空格来缩进每个子节。虽然你可以使用任何你喜欢的缩进方式,但 Python 期望你在一个部分内保持一致——每行都需要缩进相同的数量,左对齐。推荐的风格,称为PEP-8,是使用四个空格。不要使用制表符,也不要混合制表符和空格;这会搞乱缩进计数。
在本节逐渐展开时,我们做了很多事情,我会详细解释:
-
将布尔值
True赋给名为disaster的变量。 -
通过使用
if和else执行条件比较。 -
调用
print()函数来打印一些文本。
你可以进行嵌套测试,需要多少层都可以:
>>> furry = True
>>> large = True
>>> if furry:
... if large:
... print("It's a yeti.")
... else:
... print("It's a cat!")
... else:
... if large:
... print("It's a whale!")
... else:
... print("It's a human. Or a hairless cat.")
...
It's a yeti.
在 Python 中,缩进决定了如何配对if和else部分。我们的第一个测试是检查furry。因为furry是True,Python 进入缩进的if large测试。因为我们将large设为True,if large评估为True,忽略以下的else行。这使得 Python 运行缩进在if large:下的行,并打印It's a yeti.。
如果有超过两个可能性需要测试,使用if来进行第一个测试,elif(意为else if)来进行中间的测试,else用于最后一个:
>>> color = "mauve"
>>> if color == "red":
... print("It's a tomato")
... elif color == "green":
... print("It's a green pepper")
... elif color == "bee purple":
... print("I don't know what it is, but only bees can see it")
... else:
... print("I've never heard of the color", color)
...
I've never heard of the color mauve
在上面的例子中,我们使用==操作符进行了相等性测试。这里是 Python 的比较操作符:
| 等于 | == |
|---|---|
| 不等于 | != |
| 小于 | < |
| 小于或等于 | <= |
| 大于 | > |
| 大于或等于 | >= |
这些返回布尔值True或False。让我们看看它们如何工作,但首先,给x赋一个值:
>>> x = 7
现在,让我们尝试一些测试:
>>> x == 5
False
>>> x == 7
True
>>> 5 < x
True
>>> x < 10
True
注意,两个等号 (==) 用于测试相等性;记住,单个等号 (=) 用于给变量赋值。
如果你需要同时进行多个比较,可以使用逻辑(或布尔)运算符 and、or 和 not 来确定最终的布尔结果。
逻辑运算符比它们比较的代码块具有较低的优先级。这意味着首先计算这些代码块,然后再比较。在这个例子中,因为我们将 x 设置为 7,5 < x 计算为 True,x < 10 也是 True,所以最终我们得到 True and True:
>>> 5 < x and x < 10
True
正如“优先级”所指出的,避免关于优先级混淆的最简单方法是添加括号:
>>> (5 < x) and (x < 10)
True
这里有一些其他的测试:
>>> 5 < x or x < 10
True
>>> 5 < x and x > 10
False
>>> 5 < x and not x > 10
True
如果你在一个变量上进行多个 and 运算的比较,Python 允许你这样做:
>>> 5 < x < 10
True
这与 5 < x and x < 10 是一样的。你也可以编写更长的比较:
>>> 5 < x < 10 < 999
True
什么是真?
如果我们检查的元素不是布尔值,Python 认为什么是 True 和 False?
一个 false 值并不一定需要显式地是布尔 False。例如,下面这些都被认为是 False:
| 布尔 | False |
|---|---|
| 空 | None |
| 零整数 | 0 |
| 零浮点数 | 0.0 |
| 空字符串 | '' |
| 空列表 | [] |
| 空元组 | () |
| 空字典 | {} |
| 空集合 | set() |
其他任何情况都被认为是 True。Python 程序使用这些“真实性”和“虚假性”的定义来检查空数据结构以及 False 条件:
>>> some_list = []
>>> if some_list:
... print("There's something in here")
... else:
... print("Hey, it's empty!")
...
Hey, it's empty!
如果你要测试的是一个表达式而不是一个简单的变量,Python 会评估该表达式并返回一个布尔结果。因此,如果你输入:
if color == "red":
Python 评估 color == "red"。在我们之前的例子中,我们将字符串 "mauve" 分配给 color,所以 color == "red" 是 False,Python 继续下一个测试:
elif color == "green":
使用 in 进行多个比较
假设你有一个字母,并想知道它是否是元音字母。一种方法是编写一个长长的 if 语句:
>>> letter = 'o'
>>> if letter == 'a' or letter == 'e' or letter == 'i' \
... or letter == 'o' or letter == 'u':
... print(letter, 'is a vowel')
... else:
... print(letter, 'is not a vowel')
...
o is a vowel
>>>
每当你需要进行大量使用 or 分隔的比较时,使用 Python 的成员运算符 in 更加 Pythonic。下面是如何使用由元音字符组成的字符串与 in 结合来检查元音性:
>>> vowels = 'aeiou'
>>> letter = 'o'
>>> letter in vowels
True
>>> if letter in vowels:
... print(letter, 'is a vowel')
...
o is a vowel
下面是如何在接下来的几章节中详细阅读的一些数据类型的使用示例:
>>> letter = 'o'
>>> vowel_set = {'a', 'e', 'i', 'o', 'u'}
>>> letter in vowel_set
True
>>> vowel_list = ['a', 'e', 'i', 'o', 'u']
>>> letter in vowel_list
True
>>> vowel_tuple = ('a', 'e', 'i', 'o', 'u')
>>> letter in vowel_tuple
True
>>> vowel_dict = {'a': 'apple', 'e': 'elephant',
... 'i': 'impala', 'o': 'ocelot', 'u': 'unicorn'}
>>> letter in vowel_dict
True
>>> vowel_string = "aeiou"
>>> letter in vowel_string
True
对于字典,in 查看的是键(: 的左边),而不是它们的值。
新内容:我是海象
在 Python 3.8 中引入了海象运算符,它看起来像这样:
*`name`* := *`expression`*
看到海象了吗?(像笑脸一样,但更多了一些象牙。)
通常,赋值和测试需要两个步骤:
>>> tweet_limit = 280
>>> tweet_string = "Blah" * 50
>>> diff = tweet_limit - len(tweet_string)
>>> if diff >= 0:
... print("A fitting tweet")
... else:
... print("Went over by", abs(diff))
...
A fitting tweet
通过我们的新的分配表达式,我们可以将这些组合成一个步骤:
>>> tweet_limit = 280
>>> tweet_string = "Blah" * 50
>>> if diff := tweet_limit - len(tweet_string) >= 0:
... print("A fitting tweet")
... else:
... print("Went over by", abs(diff))
...
A fitting tweet
“海象运算符”还与 for 和 while 很好地配合,我们将在第六章中详细讨论。
即将到来
玩弄字符串,并遇见有趣的字符。
要做的事情
4.1 选择一个 1 到 10 之间的数字,并将其赋给变量 secret。然后,再选择另一个 1 到 10 之间的数字,并将其赋给变量 guess。接下来,编写条件测试(if、else和elif)来打印字符串'too low',如果 guess 小于 secret,打印'too high',如果 guess 大于 secret,打印'just right',如果 guess 等于 secret。
4.2 为变量 small 和 green 赋值True或False。编写一些if/else语句来打印这些选择匹配哪些选项:cherry(樱桃)、pea(豌豆)、watermelon(西瓜)、pumpkin(南瓜)。
¹ 就像那只八脚的绿色东西就在你后面!
² 请不要打电话给它。它可能会回来。
第五章:文本字符串
我总是喜欢奇怪的字符。
蒂姆·伯顿
计算机书籍通常给人一种编程都是关于数学的印象。实际上,大多数程序员更常用于处理文本的字符串,而不是数字。逻辑(和创造性!)思维通常比数学技能更重要。
字符串是 Python 的第一个序列示例。在这种情况下,它们是字符的序列。但是什么是字符?它是书写系统中的最小单位,包括字母、数字、符号、标点符号,甚至空格或类似换行符的指令。字符由其含义(如何使用它)来定义,而不是它的外观。它可以有多个视觉表示(在不同字体中),而且多个字符可以具有相同的外观(比如在拉丁字母表中表示 H 音的视觉 H,但在西里尔字母表中表示拉丁 N 音)。
本章集中讨论如何制作和格式化简单文本字符串,使用 ASCII(基本字符集)示例。两个重要的文本主题推迟到第十二章:Unicode 字符(如我刚提到的 H 和 N 问题)和正则表达式(模式匹配)。
与其他语言不同,Python 中的字符串是不可变的。你不能直接改变一个字符串,但你可以将字符串的部分复制到另一个字符串以达到相同的效果。我们马上看看如何做到这一点。
用引号创建
通过将字符包含在匹配的单引号或双引号中,你可以创建一个 Python 字符串:
>>> 'Snap'
'Snap'
>>> "Crackle"
'Crackle'
交互式解释器用单引号回显字符串,但 Python 对所有字符串处理都是完全相同的。
注意
Python 有几种特殊类型的字符串,第一个引号前面的字母指示。f 或 F 开始一个f 字符串,用于格式化,在本章末尾描述。r 或 R 开始一个原始字符串,用于防止字符串中的转义序列(参见“用 \ 进行转义” 和 第十二章 中有关它在字符串模式匹配中的用法)。然后,有组合 fr(或 FR、Fr 或 fR)开始一个原始 f-string。u 开始一个 Unicode 字符串,它与普通字符串相同。b 开始一个 bytes 类型的值(参见第十二章)。除非我提到这些特殊类型之一,我总是在谈论普通的 Python Unicode 文本字符串。
为什么要有两种引号字符?主要目的是创建包含引号字符的字符串。你可以在双引号字符串中放单引号,或在单引号字符串中放双引号:
>>> "'Nay!' said the naysayer. 'Neigh?' said the horse."
"'Nay!' said the naysayer. 'Neigh?' said the horse."
>>> 'The rare double quote in captivity: ".'
'The rare double quote in captivity: ".'
>>> 'A "two by four" is actually 1 1⁄2" × 3 1⁄2".'
'A "two by four" is actually 1 1⁄2" × 3 1⁄2".'
>>> "'There's the man that shot my paw!' cried the limping hound."
"'There's the man that shot my paw!' cried the limping hound."
你也可以使用三个单引号(''')或三个双引号("""):
>>> '''Boom!'''
'Boom'
>>> """Eek!"""
'Eek!'
三重引号对于这些短字符串并不是很有用。它们最常见的用途是创建多行字符串,就像爱德华·利尔的这首经典诗歌:
>>> poem = '''There was a Young Lady of Norway,
... Who casually sat in a doorway;
... When the door squeezed her flat,
... She exclaimed, "What of that?"
... This courageous Young Lady of Norway.'''
>>>
(这是在交互式解释器中输入的,第一行我们用 >>> 提示,接着是 ... 直到我们输入最后的三重引号并进入下一行。)
如果你尝试在没有三重引号的情况下创建那首诗,当你转到第二行时,Python 会抱怨:
>>> poem = 'There was a young lady of Norway,
File "<stdin>", line 1
poem = 'There was a young lady of Norway,
^
SyntaxError: EOL while scanning string literal
>>>
如果在三重引号中有多行文本,行尾字符将保留在字符串中。如果有前导或尾随空格,它们也将被保留:
>>> poem2 = '''I do not like thee, Doctor Fell.
... The reason why, I cannot tell.
... But this I know, and know full well:
... I do not like thee, Doctor Fell.
... '''
>>> print(poem2)
I do not like thee, Doctor Fell.
The reason why, I cannot tell.
But this I know, and know full well:
I do not like thee, Doctor Fell.
>>>
顺便提一下,print()的输出与交互式解释器的自动回显是有区别的。
>>> poem2
'I do not like thee, Doctor Fell.\n The reason why, I cannot tell.\n But
this I know, and know full well:\n I do not like thee, Doctor Fell.\n'
print()会去除字符串的引号并打印它们的内容。它适用于人类输出。它会在打印的每个内容之间添加一个空格,并在末尾添加一个换行符:
>>> print('Give', "us", '''some''', """space""")
Give us some space
如果你不想要空格或换行符,请参阅第十四章中的说明以避免它们。
交互式解释器打印字符串时带有单独的引号和转义字符,例如\n,这些在“使用\进行转义”中有解释。
>>> """'Guten Morgen, mein Herr!'
... said mad king Ludwig to his wig."""
"'Guten Morgen, mein Herr!'\nsaid mad king Ludwig to his wig."
最后,还有空字符串,它完全没有字符但却是完全有效的。你可以用前述任何引号创建空字符串:
>>> ''
''
>>> ""
''
>>> ''''''
''
>>> """"""
''
>>>
使用str()创建字符串。
你可以使用str()函数从其他数据类型创建字符串:
>>> str(98.6)
'98.6'
>>> str(1.0e4)
'10000.0'
>>> str(True)
'True'
在调用print()时,Python 在对象不是字符串且在字符串格式化时内部使用str()函数,稍后在本章节中你会看到。
使用\进行转义
Python 允许你转义字符串中某些字符的含义,以实现其他难以表达的效果。通过在字符前加上反斜杠(\),你赋予它特殊的含义。最常见的转义序列是\n,表示开始新的一行。这样你可以从单行字符串创建多行字符串:
>>> palindrome = 'A man,\nA plan,\nA canal:\nPanama.'
>>> print(palindrome)
A man,
A plan,
A canal:
Panama.
你会看到\t(制表符)的转义序列用于对齐文本:
>>> print('\tabc')
abc
>>> print('a\tbc')
a bc
>>> print('ab\tc')
ab c
>>> print('abc\t')
abc
(最终字符串具有终止的制表符,当然,你看不到。)
你可能还需要\'或\"来指定一个由相同字符引用的字符串中的字面单引号或双引号:
>>> testimony = "\"I did nothing!\" he said. \"Or that other thing.\""
>>> testimony
'"I did nothing!" he said. "Or that other thing."'
>>> print(testimony)
"I did nothing!" he said. "Or that other thing."
>>> fact = "The world's largest rubber duck was 54'2\" by 65'7\" by 105'"
>>> print(fact)
The world's largest rubber duck was 54'2" by 65'7" by 105'
如果你需要一个字面上的反斜杠,请输入两个(第一个转义第二个):
>>> speech = 'The backslash (\\) bends over backwards to please you.'
>>> print(speech)
The backslash (\) bends over backwards to please you.
>>>
正如本章开头所提到的,原始字符串会取消这些转义。
>>> info = r'Type a \n to get a new line in a normal string'
>>> info
'Type a \\n to get a new line in a normal string'
>>> print(info)
Type a \n to get a new line in a normal string
(第一个info输出中的额外反斜杠是交互式解释器添加的。)
原始字符串不会取消任何真正的(不是'\n')换行符:
>>> poem = r'''Boys and girls, come out to play.
... The moon doth shine as bright as day.'''
>>> poem
'Boys and girls, come out to play.\nThe moon doth shine as bright as day.'
>>> print(poem)
Boys and girls, come out to play.
The moon doth shine as bright as day.
通过+进行组合
在 Python 中,你可以通过使用+运算符来组合字面字符串或字符串变量。
>>> 'Release the kraken! ' + 'No, wait!'
'Release the kraken! No, wait!'
你还可以通过简单地将一个字符串放在另一个字符串后面来组合字面字符串(而不是字符串变量):
>>> "My word! " "A gentleman caller!"
'My word! A gentleman caller!'
>>> "Alas! ""The kraken!"
'Alas! The kraken!'
如果有很多这样的内容,你可以通过将其用括号括起来来避免转义行尾。
>>> vowels = ( 'a'
... "e" '''i'''
... 'o' """u"""
... )
>>> vowels
'aeiou'
Python 在连接字符串时不会为你添加空格,因此在一些早期的示例中,我们需要显式地包含空格。Python 在print()语句的每个参数之间添加一个空格,并在末尾添加一个换行符。
>>> a = 'Duck.'
>>> b = a
>>> c = 'Grey Duck!'
>>> a + b + c
'Duck.Duck.Grey Duck!'
>>> print(a, b, c)
Duck. Duck. Grey Duck!
使用*进行复制
你可以使用*运算符来复制一个字符串。尝试在交互式解释器中输入这些行,并查看它们打印出什么:
>>> start = 'Na ' * 4 + '\n'
>>> middle = 'Hey ' * 3 + '\n'
>>> end = 'Goodbye.'
>>> print(start + start + middle + end)
请注意,* 比 + 优先级更高,因此在换行符附加之前字符串被复制。
通过 [] 获取一个字符
要从字符串中获取单个字符,请在字符串名称后的方括号内指定其 offset。第一个(最左边的)偏移量是 0,接下来是 1,依此类推。最后一个(最右边的)偏移量可以用 -1 指定,因此你不必计数;向左是 -2,-3,等等:
>>> letters = 'abcdefghijklmnopqrstuvwxyz'
>>> letters[0]
'a'
>>> letters[1]
'b'
>>> letters[-1]
'z'
>>> letters[-2]
'y'
>>> letters[25]
'z'
>>> letters[5]
'f'
如果指定的偏移量等于或超过字符串的长度(记住,偏移量从 0 到长度减 1),将会引发异常:
>>> letters[100]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range
索引在其他序列类型(列表和元组)中的工作方式相同,我在第七章中介绍。
因为字符串是不可变的,您无法直接插入字符或更改特定索引处的字符。让我们尝试将 'Henny' 更改为 'Penny' 看看会发生什么:
>>> name = 'Henny'
>>> name[0] = 'P'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
相反,您需要使用 replace() 或 slice 的某些组合(我们马上会看到的)等字符串函数:
>>> name = 'Henny'
>>> name.replace('H', 'P')
'Penny'
>>> 'P' + name[1:]
'Penny'
我们没有更改 name 的值。交互式解释器只是打印替换的结果。
使用 slice 提取一个 substring
您可以通过使用 slice 从字符串中提取 substring(字符串的一部分)。您可以通过使用方括号、start 偏移量、end 偏移量和它们之间的可选 step 计数来定义 slice。您可以省略其中一些。切片将包括从 start 偏移量到 end 偏移量之前的字符:
-
[:]提取从开头到结尾的整个序列。 -
[start:]指定从start偏移量到结尾。 -
[:end]指定从开头到end偏移量减 1。 -
[start:end]表示从start偏移量到end偏移量减 1。 -
[start:end:step]提取从start偏移量到end偏移量减 1,跳过step个字符。
如前所述,偏移量从左到右为 0、1 等等,从右到左为 -1、-2 等等。如果不指定 start,则切片使用 0(开头)。如果不指定 end,则使用字符串的末尾。
让我们创建一个包含小写英文字母的字符串:
>>> letters = 'abcdefghijklmnopqrstuvwxyz'
使用一个普通的 : 等同于 0:(整个字符串):
>>> letters[:]
'abcdefghijklmnopqrstuvwxyz'
这里是一个从偏移量 20 开始到结尾的示例:
>>> letters[20:]
'uvwxyz'
现在,从偏移量 10 到结尾:
>>> letters[10:]
'klmnopqrstuvwxyz'
另一个,偏移量从 12 到 14。Python 不包括切片中的结束偏移量。开始偏移量是 包含 的,结束偏移量是 不包含 的:
>>> letters[12:15]
'mno'
最后三个字符:
>>> letters[-3:]
'xyz'
在下一个示例中,我们从偏移量 18 到倒数第 4 个提取;请注意与上一个示例的区别,在上一个示例中,从 -3 开始获取 x,但在 -3 结束实际上停在 -4,w:
>>> letters[18:-3]
'stuvw'
在以下示例中,我们从倒数第 6 个到倒数第 3 个提取:
>>> letters[-6:-2]
'uvwx'
如果要使用除 1 外的步长大小,请在第二个冒号后指定它,如下一系列示例所示。
从开头到结尾,步长为 7 个字符:
>>> letters[::7]
'ahov'
从偏移量 4 到 19,步长为 3:
>>> letters[4:20:3]
'ehknqt'
从偏移量 19 到末尾,步进为 4:
>>> letters[19::4]
'tx'
从开头到偏移量 20 加 5:
>>> letters[:21:5]
'afkpu'
(同样,结束需要比实际偏移量多一位。)
这还不是全部!给定一个负步长,这个方便的 Python 切片器还可以向后步进。它从末尾开始,直到开头,跳过一切:
>>> letters[-1::-1]
'zyxwvutsrqponmlkjihgfedcba'
结果表明,你可以通过以下方式获得相同的结果:
>>> letters[::-1]
'zyxwvutsrqponmlkjihgfedcba'
切片对于错误的偏移量更宽容,不像单索引查找[]那样严格。一个早于字符串开始的切片偏移量将被视为0,一个超过末尾的将被视为-1,正如在下面的示例中展示的那样。
从倒数第 50 位到末尾:
>>> letters[-50:]
'abcdefghijklmnopqrstuvwxyz'
从倒数第 51 位到倒数第 50 位之前:
>>> letters[-51:-50]
''
从开头到开头后的第 69 位:
>>> letters[:70]
'abcdefghijklmnopqrstuvwxyz'
从开头后的第 70 位到开头后的第 70 位:
>>> letters[70:71]
''
使用 len()获取长度
到目前为止,我们已经使用特殊的标点字符如+来操作字符串。但这些字符并不多。现在让我们开始使用一些 Python 内置的函数:这些是执行特定操作的命名代码片段。
len()函数用于计算字符串中的字符数:
>>> len(letters)
26
>>> empty = ""
>>> len(empty)
0
你可以像在第七章中看到的那样,使用len()处理其他序列类型。
使用 split()分割
与len()不同,有些函数专门用于字符串。要使用字符串函数,输入字符串名称,一个点,函数名称和函数需要的参数:*string*.*function*(*arguments*)。关于函数的更长讨论请参见第九章。
你可以使用内置的字符串split()函数根据某个分隔符将字符串分割成一个列表。我们将在第七章中讨论列表。列表是一系列由逗号分隔并用方括号括起来的值:
>>> tasks = 'get gloves,get mask,give cat vitamins,call ambulance'
>>> tasks.split(',')
['get gloves', 'get mask', 'give cat vitamins', 'call ambulance']
在前面的例子中,字符串称为tasks,字符串函数称为split(),带有单一的分隔符参数','。如果不指定分隔符,split()将使用任何连续的空白字符——换行符、空格和制表符:
>>> tasks.split()
['get', 'gloves,get', 'mask,give', 'cat', 'vitamins,call', 'ambulance']
在不带参数调用split时,你仍然需要括号——这是 Python 知道你在调用函数的方式。
使用 join()合并
不太意外的是,join()函数是split()的反向操作:它将字符串列表合并成一个单独的字符串。看起来有点反向,因为你首先指定将所有东西粘合在一起的字符串,然后是要粘合的字符串列表:string .join( list )。所以,要使用换行符将列表lines连接起来,你会说'\n'.join(lines)。在下面的示例中,让我们用逗号和空格将列表中的一些名字连接起来:
>>> crypto_list = ['Yeti', 'Bigfoot', 'Loch Ness Monster']
>>> crypto_string = ', '.join(crypto_list)
>>> print('Found and signing book deals:', crypto_string)
Found and signing book deals: Yeti, Bigfoot, Loch Ness Monster
使用 replace()替换
你用replace()进行简单的子字符串替换。给它旧的子字符串、新的子字符串,以及要替换的旧子字符串的实例数量。它返回更改后的字符串,但不修改原始字符串。如果省略这个最后的计数参数,它会替换所有实例。在这个例子中,只有一个字符串('duck')在返回的字符串中被匹配并替换:
>>> setup = "a duck goes into a bar..."
>>> setup.replace('duck', 'marmoset')
'a marmoset goes into a bar...'
>>> setup
'a duck goes into a bar...'
更改多达 100 个:
>>> setup.replace('a ', 'a famous ', 100)
'a famous duck goes into a famous bar...'
当你知道确切的子字符串要更改时,replace() 是一个很好的选择。但要小心。在第二个例子中,如果我们替换为单个字符字符串'a'而不是两个字符字符串'a '(a后跟一个空格),我们也会改变其他单词中间的a:
>>> setup.replace('a', 'a famous', 100)
'a famous duck goes into a famous ba famousr...'
有时,你想确保子字符串是一个完整的单词,或者是一个单词的开头等。在这些情况下,你需要正则表达式,在第十二章中详细描述。
用 strip() 去除
从字符串中去除前导或尾随的“填充”字符,尤其是空格,这是非常常见的。这里显示的strip()函数假设你想要去除空白字符(' ', '\t', '\n'),如果你不给它们参数的话。strip()会去除两端,lstrip()只从左边,rstrip()只从右边。假设字符串变量world包含字符串"earth"浮动在空格中:
>>> world = " earth "
>>> world.strip()
'earth'
>>> world.strip(' ')
'earth'
>>> world.lstrip()
'earth '
>>> world.rstrip()
' earth'
如果字符不在那里,什么也不会发生:
>>> world.strip('!')
' earth '
除了没有参数(意味着空白字符)或单个字符外,你还可以告诉strip()去除多字符字符串中的任何字符:
>>> blurt = "What the...!!?"
>>> blurt.strip('.?!')
'What the'
附录 E 显示了一些对于strip()有用的字符组的定义:
>>> import string
>>> string.whitespace
' \t\n\r\x0b\x0c'
>>> string.punctuation
'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
>>> blurt = "What the...!!?"
>>> blurt.strip(string.punctuation)
'What the'
>>> prospector = "What in tarnation ...??!!"
>>> prospector.strip(string.whitespace + string.punctuation)
'What in tarnation'
搜索和选择
Python 有一组庞大的字符串函数。让我们探讨它们中最常见的一些如何工作。我们的测试对象是以下字符串,其中包含了玛格丽特·卡文迪许,纽卡斯尔公爵夫人的不朽诗作“液体是什么?”的文字:
>>> poem = '''All that doth flow we cannot liquid name
... Or else would fire and water be the same;
... But that is liquid which is moist and wet
... Fire that property can never get.
... Then 'tis not cold that doth the fire put out
... But 'tis the wet that makes it die, no doubt.'''
鼓舞人心!
首先,获取前 13 个字符(偏移量 0 到 12):
>>> poem[:13]
'All that doth'
这首诗有多少个字符?(空格和换行符都包括在计数中。)
>>> len(poem)
250
它以All开头吗?
>>> poem.startswith('All')
True
它以That's all, folks!结尾吗?
>>> poem.endswith('That\'s all, folks!')
False
Python 有两个方法(find()和index())用于找到子字符串的偏移量,并且有两个版本(从开始或结尾)。如果找到子字符串,它们的工作方式相同。如果找不到,find()返回-1,而index()引发异常。
让我们找到诗中单词the的第一次出现的偏移量:
>>> word = 'the'
>>> poem.find(word)
73
>>> poem.index(word)
73
最后一个the的偏移量:
>>> word = 'the'
>>> poem.rfind(word)
214
>>> poem.rindex(word)
214
但如果子字符串不在其中:
>>> word = "duck"
>>> poem.find(word)
-1
>>> poem.rfind(word)
-1
>>> poem.index(word)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: substring not found
>>> poem.rfind(word)
-1
>>> poem.rindex(word)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: substring not found
三个字母序列the出现了多少次?
>>> word = 'the'
>>> poem.count(word)
3
诗中的所有字符都是字母或数字吗?
>>> poem.isalnum()
False
不,有一些标点字符。
案例
在这一部分,我们将看一些内置字符串函数的更多用法。我们的测试字符串再次是以下内容:
>>> setup = 'a duck goes into a bar...'
从两端去除.序列:
>>> setup.strip('.')
'a duck goes into a bar'
注意
由于字符串是不可变的,这些示例中没有一个实际上更改了setup字符串。每个示例只是取setup的值,对其进行处理,并将结果作为新字符串返回。
将第一个单词大写:
>>> setup.capitalize()
'A duck goes into a bar...'
将所有单词大写:
>>> setup.title()
'A Duck Goes Into A Bar...'
将所有字符转换为大写:
>>> setup.upper()
'A DUCK GOES INTO A BAR...'
将所有字符转换为小写:
>>> setup.lower()
'a duck goes into a bar...'
交换大写和小写:
>>> setup.swapcase()
'A DUCK GOES INTO A BAR...'
对齐
现在,让我们使用一些布局对齐函数。字符串在指定的总空间内(这里为30)居中对齐。
在 30 个空格内居中对齐字符串:
>>> setup.center(30)
' a duck goes into a bar... '
左对齐:
>>> setup.ljust(30)
'a duck goes into a bar... '
右对齐:
>>> setup.rjust(30)
' a duck goes into a bar...'
接下来,我们看一下更多如何对齐字符串的方法。
格式化
您已经看到可以使用+来连接字符串。让我们看看如何使用各种格式将数据值插值到字符串中。您可以用此方法生成需要外观精确的报告、表格和其他输出。
除了上一节中的函数外,Python 还有三种格式化字符串的方法:
-
旧风格(支持 Python 2 和 3)
-
新风格(Python 2.6 及更高版本)
-
f-strings(Python 3.6 及更高版本)
旧风格:%
旧式字符串格式化的形式为*format_string* % data。格式字符串中包含插值序列。表 5-1 说明了最简单的序列是一个%后跟一个指示要格式化的数据类型的字母。
表 5-1. 转换类型
%s | 字符串 |
|---|---|
%d | 十进制整数 |
%x | 十六进制整数 |
%o | 八进制整数 |
%f | 十进制浮点数 |
%e | 指数浮点数 |
%g | 十进制或指数浮点数 |
%% | 一个字面量% |
您可以使用%s来表示任何数据类型,Python 会将其格式化为无额外空格的字符串。
以下是一些简单的示例。首先,一个整数:
>>> '%s' % 42
'42'
>>> '%d' % 42
'42'
>>> '%x' % 42
'2a'
>>> '%o' % 42
'52'
一个浮点数:
>>> '%s' % 7.03
'7.03'
>>> '%f' % 7.03
'7.030000'
>>> '%e' % 7.03
'7.030000e+00'
>>> '%g' % 7.03
'7.03'
一个整数和一个字面量%:
>>> '%d%%' % 100
'100%'
让我们尝试一些字符串和整数插值:
>>> actor = 'Richard Gere'
>>> cat = 'Chester'
>>> weight = 28
>>> "My wife's favorite actor is %s" % actor
"My wife's favorite actor is Richard Gere"
>>> "Our cat %s weighs %s pounds" % (cat, weight)
'Our cat Chester weighs 28 pounds'
字符串中的%s表示插入一个字符串。字符串中%的数量需要与跟随字符串之后的数据项的数量匹配。单个数据项,如actor,直接放在最后一个%之后。多个数据必须分组成元组(详细信息见第七章; 由括号界定,逗号分隔),如(cat, weight)。
尽管weight是一个整数,但字符串中的%s将其转换为字符串。
您可以在格式字符串的%和类型说明符之间添加其他值来指定最小宽度、最大宽度、对齐和字符填充。这本质上是一种小语言,比接下来的两个部分的语言更有限。让我们快速看看这些值:
-
初始
'%'字符。 -
可选的对齐字符:没有或
'+'表示右对齐,'-'表示左对齐。 -
可选的最小宽度字段宽度。
-
可选的
'.'字符用于分隔最小宽度和最大字符数。 -
可选的maxchars(如果转换类型为
s)指定要从数据值中打印多少个字符。如果转换类型为f,则指定精度(小数点后要打印多少位数)。 -
早期表格中的转换类型字符。
这很令人困惑,所以这里有一些字符串的示例:
>>> thing = 'woodchuck'
>>> '%s' % thing
'woodchuck'
>>> '%12s' % thing
' woodchuck'
>>> '%+12s' % thing
' woodchuck'
>>> '%-12s' % thing
'woodchuck '
>>> '%.3s' % thing
'woo'
>>> '%12.3s' % thing
' woo'
>>> '%-12.3s' % thing
'woo '
再来一次,和一个带有%f变体的浮点数:
>>> thing = 98.6
>>> '%f' % thing
'98.600000'
>>> '%12f' % thing
' 98.600000'
>>> '%+12f' % thing
' +98.600000'
>>> '%-12f' % thing
'98.600000 '
>>> '%.3f' % thing
'98.600'
>>> '%12.3f' % thing
' 98.600'
>>> '%-12.3f' % thing
'98.600 '
和一个整数与%d:
>>> thing = 9876
>>> '%d' % thing
'9876'
>>> '%12d' % thing
' 9876'
>>> '%+12d' % thing
' +9876'
>>> '%-12d' % thing
'9876 '
>>> '%.3d' % thing
'9876'
>>> '%12.3d' % thing
' 9876'
>>> '%-12.3d' % thing
'9876 '
对于整数,%+12d只是强制打印符号,并且带有.3的格式字符串对其无效,就像对浮点数一样。
新风格:{} 和 format()
仍然支持旧风格格式化。在 Python 2 中,将冻结在版本 2.7 上,将永远支持。对于 Python 3,请使用本节中描述的“新风格”格式化。如果你使用的是 Python 3.6 或更新版本,f-strings(“最新风格:f-strings”)更加推荐。
“新风格”格式化的形式为*format_string*.format(*data*)。
格式字符串与前一节不完全相同。这里演示了最简单的用法:
>>> thing = 'woodchuck'
>>> '{}'.format(thing)
'woodchuck'
format()函数的参数需要按照格式字符串中的{}占位符的顺序:
>>> thing = 'woodchuck'
>>> place = 'lake'
>>> 'The {} is in the {}.'.format(thing, place)
'The woodchuck is in the lake.'
使用新风格格式,你还可以像这样按位置指定参数:
>>> 'The {1} is in the {0}.'.format(place, thing)
'The woodchuck is in the lake.'
值0指的是第一个参数place,1指的是thing。
format()的参数也可以是命名参数
>>> 'The {thing} is in the {place}'.format(thing='duck', place='bathtub')
'The duck is in the bathtub'
或者是一个字典:
>>> d = {'thing': 'duck', 'place': 'bathtub'}
在以下示例中,{0}是format()的第一个参数(字典d):
>>> 'The {0[thing]} is in the {0[place]}.'.format(d)
'The duck is in the bathtub.'
这些示例都使用默认格式打印它们的参数。新风格格式化与旧风格的格式字符串定义略有不同(示例如下):
-
初始冒号(
':')。 -
可选的填充字符(默认为
' ')以填充值字符串,如果比minwidth短。 -
可选的对齐字符。这次,左对齐是默认的。
'<'也表示左对齐,'>'表示右对齐,'^'表示居中。 -
数字的可选符号。没有意味着仅为负数添加减号(
'-')。' '表示负数前添加减号,正数前添加空格(' ')。 -
可选的minwidth。一个可选的句点(
'.')用于分隔minwidth和maxchars。 -
可选的maxchars。
-
转换类型。
>>> thing = 'wraith'
>>> place = 'window'
>>> 'The {} is at the {}'.format(thing, place)
'The wraith is at the window'
>>> 'The {:10s} is at the {:10s}'.format(thing, place)
'The wraith is at the window '
>>> 'The {:<10s} is at the {:<10s}'.format(thing, place)
'The wraith is at the window '
>>> 'The {:¹⁰s} is at the {:¹⁰s}'.format(thing, place)
'The wraith is at the window '
>>> 'The {:>10s} is at the {:>10s}'.format(thing, place)
'The wraith is at the window'
>>> 'The {:!¹⁰s} is at the {:!¹⁰s}'.format(thing, place)
'The !!wraith!! is at the !!window!!'
最新的风格:f-strings
f-strings出现在 Python 3.6 中,现在是推荐的字符串格式化方式。
制作 f-string:
-
直接在初始引号之前输入字母
f或F。 -
在大括号(
{})中包含变量名或表达式,以将它们的值放入字符串中。
这就像前一节的“新风格”格式化,但没有format()函数,并且格式字符串中没有空括号({})或位置参数({1})。
>>> thing = 'wereduck'
>>> place = 'werepond'
>>> f'The {thing} is in the {place}'
'The wereduck is in the werepond'
正如我之前提到的,大括号内也允许表达式:
>>> f'The {thing.capitalize()} is in the {place.rjust(20)}'
'The Wereduck is in the werepond'
这意味着在前一节的format()中可以做的事情,在主字符串的{}内部现在也可以做到。这看起来更容易阅读。
f-strings 使用与新式格式化相同的格式化语言(宽度、填充、对齐),在':'之后。
>>> f'The {thing:>20} is in the {place:.²⁰}'
'The wereduck is in the ......werepond......'
从 Python 3.8 开始,f-strings 增加了一个新的快捷方式,当你想打印变量名及其值时非常有帮助。在调试时非常方便。窍门是在 f-string 的{}括号内的变量名后面加上一个单独的=:
>>> f'{thing =}, {place =}'
thing = 'wereduck', place = 'werepond'
名称实际上可以是一个表达式,并且会按字面意思打印出来:
>>> f'{thing[-4:] =}, {place.title() =}'
thing[-4:] = 'duck', place.title() = 'Werepond'
最后,=后面可以跟着一个:和格式化参数,如宽度和对齐方式:
>>> f'{thing = :>4.4}'
thing = 'were'
更多字符串事项
Python 有比我展示的更多字符串函数。一些将出现在稍后的章节中(尤其是第十二章),但你可以在标准文档链接找到所有细节。
即将到来
你会在杂货店找到 Froot Loops,但 Python 循环在下一章的第一个柜台上。
待办事项
5.1 将以m开头的单词大写:
>>> song = """When an eel grabs your arm,
... And it causes great harm,
... That's - a moray!"""
5.2 打印每个列表问题及其正确匹配的答案,格式为:
Q: 问题
A: 答案
>>> questions = [
... "We don't serve strings around here. Are you a string?",
... "What is said on Father's Day in the forest?",
... "What makes the sound 'Sis! Boom! Bah!'?"
... ]
>>> answers = [
... "An exploding sheep.",
... "No, I'm a frayed knot.",
... "'Pop!' goes the weasel."
... ]
5.3 通过旧式格式编写以下诗歌。将字符串'roast beef'、'ham'、'head'和'clam'替换为此字符串中的内容:
My kitty cat likes %s,
My kitty cat likes %s,
My kitty cat fell on his %s
And now thinks he's a %s.
5.4 使用新式格式化编写一封表单信。将以下字符串保存为letter(你将在下一个练习中使用它):
Dear {salutation} {name},
Thank you for your letter. We are sorry that our {product}
{verbed} in your {room}. Please note that it should never
be used in a {room}, especially near any {animals}.
Send us your receipt and {amount} for shipping and handling.
We will send you another {product} that, in our tests,
is {percent}% less likely to have {verbed}.
Thank you for your support.
Sincerely,
{spokesman}
{job_title}
5.5 为字符串变量'salutation'、'name'、'product'、'verbed'(过去时动词)、'room'、'animals'、'percent'、'spokesman'和'job_title'分配值。使用letter.format()打印letter。
5.6 在公众投票之后为事物命名,出现了一个模式:英国潜艇(Boaty McBoatface)、澳大利亚赛马(Horsey McHorseface)和瑞典火车(Trainy McTrainface)。使用%格式化来打印国家集市上的获奖名字,以及鸭子、葫芦和 spitz 的奖品。
5.7 使用format()格式化方法做同样的事情。
5.8 再来一次,使用f strings。
第六章:使用 while 和 for 循环
对于所有的事情,我们辛劳工作,我们辛劳工作,我们所有的努力都被忽视……
罗伯特·彭斯,《为了那些,为了那些》
使用 if、elif 和 else 进行测试时,从上到下执行。有时,我们需要执行多次操作。我们需要一个循环,而 Python 给了我们两个选择:while 和 for。
使用 while 重复
Python 中最简单的循环机制是 while。使用交互式解释器,尝试这个例子,这是一个简单的循环,打印从 1 到 5 的数字:
>>> count = 1
>>> while count <= 5:
... print(count)
... count += 1
...
1
2
3
4
5
>>>
我们首先将值 1 赋给了 count。while 循环将 count 的值与 5 进行比较,如果 count 小于或等于 5,则继续。在循环内部,我们打印了 count 的值,然后使用语句 count += 1 将其值增加了一。Python 返回循环顶部,再次将 count 与 5 进行比较。此时 count 的值为 2,因此再次执行 while 循环的内容,并将 count 增加到 3。
这将持续到在循环底部将 count 从 5 增加到 6 为止。在下一次回到顶部时,count <= 5 现在为 False,while 循环结束。Python 继续执行下一行。
使用 break 取消
如果您想循环直到某些事情发生,但不确定什么时候会发生,可以使用带有 break 语句的无限循环。这次,让我们通过 Python 的 input() 函数从键盘读取一行输入,然后将其打印为首字母大写。当键入仅包含字母 q 的行时,我们跳出循环:
>>> while True:
... stuff = input("String to capitalize [type q to quit]: ")
... if stuff == "q":
... break
... print(stuff.capitalize())
...
String to capitalize [type q to quit]: test
Test
String to capitalize [type q to quit]: hey, it works
Hey, it works
String to capitalize [type q to quit]: q
>>>
使用 continue 跳过
有时,您不想中断循环,而只是想因某种原因跳到下一个迭代。这是一个牵强的例子:让我们读取一个整数,如果它是奇数,则打印它的平方,并在它是偶数时跳过。我们甚至添加了一些注释。同样,我们使用 q 来停止循环。
>>> while True:
... value = input("Integer, please [q to quit]: ")
... if value == 'q': # quit
... break
... number = int(value)
... if number % 2 == 0: # an even number
... continue
... print(number, "squared is", number*number)
...
Integer, please [q to quit]: 1
1 squared is 1
Integer, please [q to quit]: 2
Integer, please [q to quit]: 3
3 squared is 9
Integer, please [q to quit]: 4
Integer, please [q to quit]: 5
5 squared is 25
Integer, please [q to quit]: q
>>>
使用 break 检查与 else 一起使用
如果 while 循环正常结束(没有调用 break),控制将传递给可选的 else。当您已编写了一个 while 循环来检查某些内容,并在找到时立即中断时,您会使用它。如果 while 循环完成但未找到对象,则会运行 else:
>>> numbers = [1, 3, 5]
>>> position = 0
>>> while position < len(numbers):
... number = numbers[position]
... if number % 2 == 0:
... print('Found even number', number)
... break
... position += 1
... else: # break not called
... print('No even number found')
...
No even number found
注意
对于else的这种用法可能看起来不直观。将其视为中断检查器。
使用 for 和 in 进行迭代
Python 经常使用迭代器,有很好的理由。它们使您能够遍历数据结构,而无需知道其大小或实现方式。您甚至可以迭代即时创建的数据,允许处理否则无法一次性放入计算机内存中的数据流。
要展示迭代,我们需要一个可以迭代的对象。你已经在第五章看到了字符串,但还没有详细了解其他可迭代对象,比如列表和元组(见第七章)或字典(见第八章)。这里我将展示两种遍历字符串的方法,并在它们各自的章节中展示其他类型的迭代。
在 Python 中,通过以下方式逐步遍历字符串是合法的:
>>> word = 'thud'
>>> offset = 0
>>> while offset < len(word):
... print(word[offset])
... offset += 1
...
t
h
u
d
但有一个更好、更符合 Python 风格的方法:
>>> for letter in word:
... print(letter)
...
t
h
u
d
字符串迭代每次产生一个字符。
通过break取消
for循环中的break会跳出循环,就像在while循环中一样:
>>> word = 'thud'
>>> for letter in word:
... if letter == 'u':
... break
... print(letter)
...
t
h
使用continue跳过
在for循环中插入continue会跳到下一个迭代,就像在while循环中一样。
检查break与else的用法
类似于while,for也有一个可选的else语句,用于检查for是否正常完成。如果没有调用break,则会执行else语句。
当你希望确认前一个for循环是否完全执行而不是因为break而提前停止时,这是非常有用的:
>>> word = 'thud'
>>> for letter in word:
... if letter == 'x':
... print("Eek! An 'x'!")
... break
... print(letter)
... else:
... print("No 'x' in there.")
...
t
h
u
d
No 'x' in there.
注意
与while一样,使用for和else可能看起来不直观。如果你把for看作在寻找某些东西,那么当你没有找到时,else会被调用。如果想要在没有else的情况下达到相同的效果,可以使用某个变量来指示在for循环中是否找到了想要的内容。
使用range()生成数字序列
range()函数返回在指定范围内的一系列数字。无需首先创建和存储大数据结构(如列表或元组),就可以创建大范围,避免占用计算机所有内存并导致程序崩溃。
使用range()与使用切片类似:range( start, stop, step )。如果省略*start,范围将从0开始。与切片一样,创建的最后一个值将恰好在stop之前。step*的默认值是1,但可以使用-1向后遍历。
类似于zip(),range()返回一个可迭代对象,因此你需要用for ... in逐个遍历其值,或者将该对象转换为像列表这样的序列。让我们创建一个范围为0, 1, 2的示例:
>>> for x in range(0,3):
... print(x)
...
0
1
2
>>> list( range(0, 3) )
[0, 1, 2]
下面是如何从2到0生成一个范围:
>>> for x in range(2, -1, -1):
... print(x)
...
2
1
0
>>> list( range(2, -1, -1) )
[2, 1, 0]
以下片段使用步长为2来获取从0到10的偶数:
>>> list( range(0, 11, 2) )
[0, 2, 4, 6, 8, 10]
其他迭代器
第十四章展示了如何迭代文件。在第十章,你可以看到如何启用对自定义对象的迭代。此外,第十一章讨论了itertools——一个带有许多有用快捷方式的标准 Python 模块。
即将到来
将各个数据链入列表和元组中。
待完成的事情
6.1 使用for循环打印列表[3, 2, 1, 0]的值。
将值7赋给变量guess_me,并将值1赋给变量number。编写一个while循环,比较number与guess_me。如果number小于guess me,则打印'too low'。如果number等于guess_me,则打印'found it!',然后退出循环。如果number大于guess_me,则打印'oops',然后退出循环。在循环结束时增加number。
将值5赋给变量guess_me。使用for循环迭代名为number的变量在range(10)上。如果number小于guess_me,则打印'too low'。如果它等于guess_me,则打印'found it!',然后退出 for 循环。如果number大于guess_me,则打印'oops',然后退出循环。
第七章:元组和列表
人类与低等灵长类动物的区别在于他对列表的热爱。
H. Allen Smith
在前几章中,我们从 Python 的一些基本数据类型开始:布尔值,整数,浮点数和字符串。如果你把它们看作是原子,那么本章中的数据结构就像分子一样。也就是说,我们将这些基本类型以更复杂的方式组合在一起。你将每天都用到它们。编程的很大一部分就是将数据切割和粘贴成特定形式,而这些就是你的金刚钻和胶枪。
大多数计算机语言可以表示按其整数位置索引的项目序列:第一个,第二个,依此类推直到最后一个。你已经见过 Python 的字符串,它们是字符序列。
Python 还有另外两种序列结构:元组和列表。它们包含零个或多个元素。与字符串不同,元素可以是不同的类型。事实上,每个元素都可以是任何Python 对象。这使你可以创建像你喜欢的那样深度和复杂的结构。
为什么 Python 同时包含列表和元组?元组是不可变的;当你将元素(仅一次)分配给元组时,它们就成为了固定的部分,不能更改。列表是可变的,这意味着你可以兴致勃勃地插入和删除元素。我将展示每种的许多例子,并着重于列表。
元组
让我们先把一件事搞清楚。你可能会听到两种不同的tuple发音。哪个是正确的?如果你猜错了,是否会被认为是 Python 的冒牌货?别担心。Python 的创造者 Guido van Rossum 在Twitter 上说过:
我在周一/三/五会念作 too-pull,周二/四/六会念作 tub-pull。周日我不谈它们。 :)
使用逗号和()创建
创建元组的语法有点不一致,如下面的例子所示。让我们从使用()创建一个空元组开始:
>>> empty_tuple = ()
>>> empty_tuple
()
要创建一个或多个元素的元组,请在每个元素后面都跟一个逗号。这适用于单元素元组:
>>> one_marx = 'Groucho',
>>> one_marx
('Groucho',)
你可以将它们括在括号中,仍然得到相同的元组:
>>> one_marx = ('Groucho',)
>>> one_marx
('Groucho',)
这里有一个小陷阱:如果括号中只有一个东西而省略了逗号,你将得不到一个元组,而只是那个东西(在这个例子中是字符串'Groucho'):
>>> one_marx = ('Groucho')
>>> one_marx
'Groucho'
>>> type(one_marx)
<class 'str'>
如果有多个元素,请除了最后一个元素外,每个元素后面都跟一个逗号:
>>> marx_tuple = 'Groucho', 'Chico', 'Harpo'
>>> marx_tuple
('Groucho', 'Chico', 'Harpo')
Python 在回显元组时包括括号。当你定义一个元组时通常不需要它们,但使用括号会更安全,并且有助于使元组更可见:
>>> marx_tuple = ('Groucho', 'Chico', 'Harpo')
>>> marx_tuple
('Groucho', 'Chico', 'Harpo')
在某些情况下,如果逗号可能具有其他用途,则确实需要括号。例如,在这个例子中,你可以只用一个尾随逗号创建并分配一个单元素元组,但你不能将其作为函数的参数传递。
>>> one_marx = 'Groucho',
>>> type(one_marx)
<class 'tuple'>
>>> type('Groucho',)
<class 'str'>
>>> type(('Groucho',))
<class 'tuple'>
元组让你一次性赋值多个变量:
>>> marx_tuple = ('Groucho', 'Chico', 'Harpo')
>>> a, b, c = marx_tuple
>>> a
'Groucho'
>>> b
'Chico'
>>> c
'Harpo'
有时被称为元组解包。
你可以使用元组在一条语句中交换值,而不使用临时变量:
>>> password = 'swordfish'
>>> icecream = 'tuttifrutti'
>>> password, icecream = icecream, password
>>> password
'tuttifrutti'
>>> icecream
'swordfish'
>>>
使用 tuple()创建
tuple()转换函数从其他内容制作元组:
>>> marx_list = ['Groucho', 'Chico', 'Harpo']
>>> tuple(marx_list)
('Groucho', 'Chico', 'Harpo')
使用+组合元组
这类似于组合字符串:
>>> ('Groucho',) + ('Chico', 'Harpo')
('Groucho', 'Chico', 'Harpo')
使用复制项
这就像重复使用+一样:
>>> ('yada',) * 3
('yada', 'yada', 'yada')
比较元组
这与列表比较类似:
>>> a = (7, 2)
>>> b = (7, 2, 9)
>>> a == b
False
>>> a <= b
True
>>> a < b
True
使用 for 和 in 进行迭代
元组迭代类似于其他类型的迭代:
>>> words = ('fresh','out', 'of', 'ideas')
>>> for word in words:
... print(word)
...
fresh
out
of
ideas
修改元组
你不能!与字符串一样,元组是不可变的,因此您不能更改现有元组。就像您之前看到的那样,您可以连接(组合)元组以制作新元组,就像您可以连接字符串一样:
>>> t1 = ('Fee', 'Fie', 'Foe')
>>> t2 = ('Flop,')
>>> t1 + t2
('Fee', 'Fie', 'Foe', 'Flop')
这意味着您可以看起来修改元组,就像这样:
>>> t1 = ('Fee', 'Fie', 'Foe')
>>> t2 = ('Flop,')
>>> t1 += t2
>>> t1
('Fee', 'Fie', 'Foe', 'Flop')
但它不是相同的t1。Python 从由t1和t2指向的原始元组制作了一个新元组,并将名称t1指向了这个新元组。您可以使用id()查看变量名称何时指向新值:
>>> t1 = ('Fee', 'Fie', 'Foe')
>>> t2 = ('Flop',)
>>> id(t1)
4365405712
>>> t1 += t2
>>> id(t1)
4364770744
列表
列表适合按其顺序跟踪事物,特别是当顺序和内容可能会变化时。与字符串不同,列表是可变的。您可以就地更改列表,添加新元素,并删除或替换现有元素。相同的值可以在列表中出现多次。
使用[]创建
列表由零个或多个元素组成,用逗号分隔,并用方括号括起来:
>>> empty_list = [ ]
>>> weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
>>> big_birds = ['emu', 'ostrich', 'cassowary']
>>> first_names = ['Graham', 'John', 'Terry', 'Terry', 'Michael']
>>> leap_years = [2000, 2004, 2008]
>>> randomness = 'Punxsatawney", {"groundhog": "Phil"}, "Feb. 2"}
first_names列表显示值不需要是唯一的。
注意
如果您只想跟踪唯一值并且不关心顺序,则 Python set可能比列表更好。在前面的示例中,big_birds可以是一个集合。我们在[第八章中探讨了集合。
使用 list()创建或转换
您还可以使用list()函数创建一个空列表:
>>> another_empty_list = list()
>>> another_empty_list
[]
Python 的list()函数还将其他可迭代数据类型(如元组、字符串、集合和字典)转换为列表。以下示例将字符串转换为一个字符的字符串列表:
>>> list('cat')
['c', 'a', 't']
该示例将元组转换为列表:
>>> a_tuple = ('ready', 'fire', 'aim')
>>> list(a_tuple)
['ready', 'fire', 'aim']
使用 split()从字符串创建
正如我之前在“使用 split()拆分”中提到的,使用split()通过某个分隔符将字符串切割为列表:
>>> talk_like_a_pirate_day = '9/19/2019'
>>> talk_like_a_pirate_day.split('/')
['9', '19', '2019']
如果您的原始字符串中有多个连续的分隔符字符串怎么办?好吧,你会得到一个空字符串作为列表项:
>>> splitme = 'a/b//c/d///e'
>>> splitme.split('/')
['a', 'b', '', 'c', 'd', '', '', 'e']
如果您使用两个字符的分隔符字符串//,则会得到这个结果:
>>> splitme = 'a/b//c/d///e'
>>> splitme.split('//')
>>>
['a/b', 'c/d', '/e']
通过[ 偏移量 ]获取项
与字符串类似,您可以通过指定其偏移量从列表中提取单个值:
>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> marxes[0]
'Groucho'
>>> marxes[1]
'Chico'
>>> marxes[2]
'Harpo'
同样,与字符串类似,负索引从末尾向后计数:
>>> marxes[-1]
'Harpo'
>>> marxes[-2]
'Chico'
>>> marxes[-3]
'Groucho'
>>>
注意
偏移量必须是此列表的有效偏移量,即您先前分配了一个值的位置。如果指定了开始之前或结束之后的偏移量,您将收到一个异常(错误)。这是如果我们尝试获取第六个马克思兄弟(偏移量5从0开始计数),或倒数第五个会发生的情况:
>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> marxes[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>> marxes[-5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
使用切片获取项目
您可以通过使用切片提取列表的子序列:
>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> marxes[0:2]
['Groucho', 'Chico']
列表的切片也是一个列表。
与字符串类似,切片可以步进除了一之外的其他值。下一个示例从开始处开始,每次向右移动 2 个位置:
>>> marxes[::2]
['Groucho', 'Harpo']
在这里,我们从末尾开始,左移 2 个位置:
>>> marxes[::-2]
['Harpo', 'Groucho']
最后,反转列表的窍门:
>>> marxes[::-1]
['Harpo', 'Chico', 'Groucho']
这些切片都没有改变marxes列表本身,因为我们没有把它们赋给marxes。要就地反转列表,请使用*列表*.reverse():
>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> marxes.reverse()
>>> marxes
['Harpo', 'Chico', 'Groucho']
注意
reverse()函数改变了列表但不返回其值。
正如你在字符串中看到的,切片可以指定一个无效的索引,但不会导致异常。它会“捕捉”到最接近的有效索引或者返回空:
>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> marxes[4:]
[]
>>> marxes[-6:]
['Groucho', 'Chico', 'Harpo']
>>> marxes[-6:-2]
['Groucho']
>>> marxes[-6:-4]
[]
通过 append()在末尾添加项目
添加项目到列表的传统方法是一个接一个地用append()将它们添加到末尾。在前面的例子中,我们忘记了 Zeppo,但这没关系,因为列表是可变的,所以我们现在可以添加他:
>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> marxes.append('Zeppo')
>>> marxes
['Groucho', 'Chico', 'Harpo', 'Zeppo']
通过 insert()在偏移处添加项目
append()函数只在列表末尾添加项目。当你想在列表的任何偏移之前添加项目时,请使用insert()。偏移0在开头插入。超出列表末尾的偏移会像append()一样在末尾插入,所以你不需要担心 Python 抛出异常:
>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> marxes.insert(2, 'Gummo')
>>> marxes
['Groucho', 'Chico', 'Gummo', 'Harpo']
>>> marxes.insert(10, 'Zeppo')
>>> marxes
['Groucho', 'Chico', 'Gummo', 'Harpo', 'Zeppo']
用*复制所有项目
在第五章中,你看到你可以用*来复制字符串的字符。对列表也同样适用:
>>> ["blah"] * 3
['blah', 'blah', 'blah']
通过 extend()或者+合并列表
你可以通过使用extend()将一个列表合并到另一个列表中。假设一个好心的人给了我们一个名为others的新马克思斯列表,并且我们想要将它们合并到主要的marxes列表中:
>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Zeppo']
>>> others = ['Gummo', 'Karl']
>>> marxes.extend(others)
>>> marxes
['Groucho', 'Chico', 'Harpo', 'Zeppo', 'Gummo', 'Karl']
或者,你可以使用+或者+=:
>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Zeppo']
>>> others = ['Gummo', 'Karl']
>>> marxes += others
>>> marxes
['Groucho', 'Chico', 'Harpo', 'Zeppo', 'Gummo', 'Karl']
如果我们使用了append(),others会被添加为单个列表项,而不是合并其项目:
>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Zeppo']
>>> others = ['Gummo', 'Karl']
>>> marxes.append(others)
>>> marxes
['Groucho', 'Chico', 'Harpo', 'Zeppo', ['Gummo', 'Karl']]
这再次证明了列表可以包含不同类型的元素。在这种情况下,四个字符串和一个包含两个字符串的列表。
通过[offset]更改项目
就像你可以通过偏移获取列表项的值一样,你也可以修改它:
>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> marxes[2] = 'Wanda'
>>> marxes
['Groucho', 'Chico', 'Wanda']
再次,列表偏移需要是这个列表的有效偏移之一。
你不能用这种方式改变字符串中的字符,因为字符串是不可变的。列表是可变的。你可以改变列表包含的项目数量以及项目本身。
使用切片更改项目
前一节展示了如何使用切片获取子列表。你也可以使用切片为子列表赋值:
>>> numbers = [1, 2, 3, 4]
>>> numbers[1:3] = [8, 9]
>>> numbers
[1, 8, 9, 4]
您分配给列表的右侧对象甚至不需要与左侧切片具有相同数量的元素:
>>> numbers = [1, 2, 3, 4]
>>> numbers[1:3] = [7, 8, 9]
>>> numbers
[1, 7, 8, 9, 4]
>>> numbers = [1, 2, 3, 4]
>>> numbers[1:3] = []
>>> numbers
[1, 4]
实际上,右边的东西甚至不需要是一个列表。任何 Python可迭代对象都可以,分离其项目并将其分配给列表元素:
>>> numbers = [1, 2, 3, 4]
>>> numbers[1:3] = (98, 99, 100)
>>> numbers
[1, 98, 99, 100, 4]
>>> numbers = [1, 2, 3, 4]
>>> numbers[1:3] = 'wat?'
>>> numbers
[1, 'w', 'a', 't', '?', 4]
通过 del 按偏移删除项目
我们的事实核查员刚刚告诉我们,Gummo 确实是马克思兄弟之一,但卡尔不是,并且早先插入他的人非常无礼。让我们来修复一下:
>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Gummo', 'Karl']
>>> marxes[-1]
'Karl'
>>> del marxes[-1]
>>> marxes
['Groucho', 'Chico', 'Harpo', 'Gummo']
当你按列表中的位置删除一个项目时,随后的项目会向后移动以填补删除项目的空间,并且列表的长度会减少一个。如果我们从marxes列表的最后一个版本中删除了'Chico',我们会得到这样的结果:
>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Gummo']
>>> del marxes[1]
>>> marxes
['Groucho', 'Harpo', 'Gummo']
注意
del 是一个 Python 语句,不是列表的方法 —— 你不会说 marxes[-1].del()。这与赋值 (=) 的相反:它会将一个名称从 Python 对象中分离出来,如果该名称是对该对象的最后一个引用,则可以释放该对象的内存。
使用remove()按值删除项目
如果不确定或不关心项目在列表中的位置,请使用remove()按值删除它。再见,Groucho:
>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> marxes.remove('Groucho')
>>> marxes
['Chico', 'Harpo']
如果列表中有相同值的重复项目,remove()仅删除找到的第一个。
使用偏移获取项目并使用pop()删除它
你可以通过使用pop()从列表中获取项目并同时删除它。如果使用偏移调用pop(),它将返回该偏移量处的项目;如果没有参数,则使用-1。因此,pop(0)返回列表的头部(起始处),而pop()或pop(-1)返回尾部(结束处),如下所示:
>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Zeppo']
>>> marxes.pop()
'Zeppo'
>>> marxes
['Groucho', 'Chico', 'Harpo']
>>> marxes.pop(1)
'Chico'
>>> marxes
['Groucho', 'Harpo']
注意
现在是计算机术语时间!别担心,这些不会出现在期末考试中。如果你使用append()在末尾添加新项目,并使用pop()从同一端移除它们,你就实现了一种称为 LIFO(后进先出)队列的数据结构。这更常被称为 栈。pop(0)将创建一个 FIFO(先进先出)队列。当你希望按到达顺序收集数据并首先使用最旧的数据(FIFO),或者首先使用最新的数据(LIFO)时,这些非常有用。
使用clear()删除所有项目
Python 3.3 引入了清空列表所有元素的方法:
>>> work_quotes = ['Working hard?', 'Quick question!', 'Number one priorities!']
>>> work_quotes
['Working hard?', 'Quick question!', 'Number one priorities!']
>>> work_quotes.clear()
>>> work_quotes
[]
使用index()按值查找项目的偏移量
如果想知道列表中项目按其值的偏移量,使用index():
>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Zeppo']
>>> marxes.index('Chico')
1
如果该值在列表中出现多次,只返回第一个的偏移量:
>>> simpsons = ['Lisa', 'Bart', 'Marge', 'Homer', 'Bart']
>>> simpsons.index('Bart')
1
使用in测试值是否存在
在列表中检查值是否存在的 Python 方式是使用in:
>>> marxes = ['Groucho', 'Chico', 'Harpo', 'Zeppo']
>>> 'Groucho' in marxes
True
>>> 'Bob' in marxes
False
同一个值可能在列表中出现多次。只要至少出现一次,in 就会返回 True:
>>> words = ['a', 'deer', 'a' 'female', 'deer']
>>> 'deer' in words
True
注意
如果经常检查列表中某个值的存在性,并且不关心项目的顺序,Python set 是存储和查找唯一值的更合适方式。我们在第八章中讨论了集合。
使用count()计算值的出现次数
要统计列表中特定值出现的次数,请使用count():
>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> marxes.count('Harpo')
1
>>> marxes.count('Bob')
0
>>> snl_skit = ['cheeseburger', 'cheeseburger', 'cheeseburger']
>>> snl_skit.count('cheeseburger')
3
使用join()将列表转换为字符串
“使用 join()组合” 更详细地讨论了join(),这里是另一个示例:
>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> ', '.join(marxes)
'Groucho, Chico, Harpo'
你可能会认为这似乎有点反向。join()是一个字符串方法,而不是一个列表方法。你不能说marxes.join(', '),即使它看起来更直观。join()的参数是一个字符串或任何可迭代的字符串序列(包括列表),其输出是一个字符串。如果join()只是一个列表方法,你不能将其与其他可迭代对象如元组或字符串一起使用。如果你确实希望它能处理任何可迭代类型,你需要为每种类型编写特殊的代码来处理实际的连接。记住——join() 与 split()是相反的,如下所示:
>>> friends = ['Harry', 'Hermione', 'Ron']
>>> separator = ' * '
>>> joined = separator.join(friends)
>>> joined
'Harry * Hermione * Ron'
>>> separated = joined.split(separator)
>>> separated
['Harry', 'Hermione', 'Ron']
>>> separated == friends
True
使用sort()或sorted()重新排序项目
通常需要按值而不是偏移量对列表中的项目进行排序。Python 提供了两个函数:
-
列表方法
sort()会原地对列表进行排序。 -
通用函数
sorted()返回列表的已排序副本。
如果列表中的项是数字,则默认按升序数字顺序排序。如果它们是字符串,则按字母顺序排序:
>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> sorted_marxes = sorted(marxes)
>>> sorted_marxes
['Chico', 'Groucho', 'Harpo']
sorted_marxes是一个新列表,创建它并未改变原始列表:
>>> marxes
['Groucho', 'Chico', 'Harpo']
但在marxes列表上调用列表函数sort()确实会改变marxes:
>>> marxes.sort()
>>> marxes
['Chico', 'Groucho', 'Harpo']
如果列表的元素都是相同类型的(例如在marxes中的字符串),sort()将正常工作。有时甚至可以混合类型——例如整数和浮点数——因为 Python 在表达式中会自动转换它们:
>>> numbers = [2, 1, 4.0, 3]
>>> numbers.sort()
>>> numbers
[1, 2, 3, 4.0]
默认排序顺序是升序,但可以添加参数reverse=True将其设置为降序:
>>> numbers = [2, 1, 4.0, 3]
>>> numbers.sort(reverse=True)
>>> numbers
[4.0, 3, 2, 1]
使用len()获取长度
len()返回列表中的项数:
>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> len(marxes)
3
使用=分配
当你将一个列表分配给多个变量时,在一个地方改变列表也会在另一个地方改变,如下所示:
>>> a = [1, 2, 3]
>>> a
[1, 2, 3]
>>> b = a
>>> b
[1, 2, 3]
>>> a[0] = 'surprise'
>>> a
['surprise', 2, 3]
那么现在b里面是什么?它还是[1, 2, 3],还是['surprise', 2, 3]?让我们看看:
>>> b
['surprise', 2, 3]
记住第二章中的盒子(对象)和带有注释的字符串(变量名)类比?b只是引用与a相同的列表对象(两个名称字符串引导到同一个对象盒子)。无论我们使用名称a还是b改变列表内容,都会反映在两者上:
>>> b
['surprise', 2, 3]
>>> b[0] = 'I hate surprises'
>>> b
['I hate surprises', 2, 3]
>>> a
['I hate surprises', 2, 3]
使用copy()、list()或切片复制
你可以通过以下任一方法将列表的值复制到独立的新列表中:
-
列表
copy()方法 -
list()转换函数 -
列表切片
[:]
我们的原始列表将再次是a。我们用列表copy()函数制作b,用list()转换函数制作c,用列表切片制作d:
>>> a = [1, 2, 3]
>>> b = a.copy()
>>> c = list(a)
>>> d = a[:]
同样,b、c和d是a的副本:它们是具有自己值且与原始列表对象[1, 2, 3]没有连接的新对象。改变a不会影响副本b、c和d:
>>> a[0] = 'integer lists are boring'
>>> a
['integer lists are boring', 2, 3]
>>> b
[1, 2, 3]
>>> c
[1, 2, 3]
>>> d
[1, 2, 3]
使用deepcopy()复制所有内容
如果列表的值全部是不可变的,copy() 函数可以很好地工作。正如之前所见,可变值(如列表、元组或字典)是引用。对原始对象或副本的更改将反映在两者中。
让我们使用前面的例子,但将列表 a 中的最后一个元素更改为列表 [8, 9] 而不是整数 3:
>>> a = [1, 2, [8, 9]]
>>> b = a.copy()
>>> c = list(a)
>>> d = a[:]
>>> a
[1, 2, [8, 9]]
>>> b
[1, 2, [8, 9]]
>>> c
[1, 2, [8, 9]]
>>> d
[1, 2, [8, 9]]
目前为止一切顺利。现在更改 a 中的子列表中的一个元素:
>>> a[2][1] = 10
>>> a
[1, 2, [8, 10]]
>>> b
[1, 2, [8, 10]]
>>> c
[1, 2, [8, 10]]
>>> d
[1, 2, [8, 10]]
现在,a[2] 的值是一个列表,它的元素可以被改变。我们使用的所有列表复制方法都是 浅复制(不是价值判断,而是深度判断)。
要修复这个问题,我们需要使用 deepcopy() 函数:
>>> import copy
>>> a = [1, 2, [8, 9]]
>>> b = copy.deepcopy(a)
>>> a
[1, 2, [8, 9]]
>>> b
[1, 2, [8, 9]]
>>> a[2][1] = 10
>>> a
[1, 2, [8, 10]]
>>> b
[1, 2, [8, 9]]
deepcopy() 可以处理深度嵌套的列表、字典和其他对象。
你将在第九章更多地了解 import。
比较列表
你可以直接使用比较运算符如 ==、< 等来比较列表。这些运算符遍历两个列表,比较相同偏移量的元素。如果列表 a 比列表 b 短,并且所有元素都相等,则 a 小于 b:
>>> a = [7, 2]
>>> b = [7, 2, 9]
>>> a == b
False
>>> a <= b
True
>>> a < b
True
使用 for 和 in 迭代
在第六章中,你看到了如何使用 for 迭代字符串,但更常见的是迭代列表:
>>> cheeses = ['brie', 'gjetost', 'havarti']
>>> for cheese in cheeses:
... print(cheese)
...
brie
gjetost
havarti
与以前一样,break 结束 for 循环,continue 跳到下一个迭代:
>>> cheeses = ['brie', 'gjetost', 'havarti']
>>> for cheese in cheeses:
... if cheese.startswith('g'):
... print("I won't eat anything that starts with 'g'")
... break
... else:
... print(cheese)
...
brie
I won't eat anything that starts with 'g'
如果 for 循环完成而没有 break,你仍然可以使用可选的 else:
>>> cheeses = ['brie', 'gjetost', 'havarti']
>>> for cheese in cheeses:
... if cheese.startswith('x'):
... print("I won't eat anything that starts with 'x'")
... break
... else:
... print(cheese)
... else:
... print("Didn't find anything that started with 'x'")
...
brie
gjetost
havarti
Didn't find anything that started with 'x'
如果初始的 for 从未运行,则控制也转到 else:
>>> cheeses = []
>>> for cheese in cheeses:
... print('This shop has some lovely', cheese)
... break
... else: # no break means no cheese
... print('This is not much of a cheese shop, is it?')
...
This is not much of a cheese shop, is it?
因为在这个例子中 cheeses 列表为空,所以 for cheese in cheeses 从未完成过单个循环,它的 break 语句从未执行过。
使用 zip() 迭代多个序列
还有一个很好的迭代技巧:通过使用 zip() 函数并行迭代多个序列:
>>> days = ['Monday', 'Tuesday', 'Wednesday']
>>> fruits = ['banana', 'orange', 'peach']
>>> drinks = ['coffee', 'tea', 'beer']
>>> desserts = ['tiramisu', 'ice cream', 'pie', 'pudding']
>>> for day, fruit, drink, dessert in zip(days, fruits, drinks, desserts):
... print(day, ": drink", drink, "- eat", fruit, "- enjoy", dessert)
...
Monday : drink coffee - eat banana - enjoy tiramisu
Tuesday : drink tea - eat orange - enjoy ice cream
Wednesday : drink beer - eat peach - enjoy pie
zip() 在最短的序列结束时停止。 其中一个列表(desserts)比其他列表长,因此除非我们扩展其他列表,否则没有人会得到任何布丁。
第八章 展示了 dict() 函数如何从包含两个元素的序列(如元组、列表或字符串)创建字典。你可以使用 zip() 遍历多个序列,并从相同偏移量的项目创建元组。让我们创建两个对应的英文和法文单词的元组:
>>> english = 'Monday', 'Tuesday', 'Wednesday'
>>> french = 'Lundi', 'Mardi', 'Mercredi'
现在,使用 zip() 将这些元组配对。zip() 返回的值本身不是元组或列表,而是一个可迭代的值,可以转换为元组:
>>> list( zip(english, french) )
[('Monday', 'Lundi'), ('Tuesday', 'Mardi'), ('Wednesday', 'Mercredi')]
将 zip() 的结果直接提供给 dict(),完成:一个微小的英法词典!
>>> dict( zip(english, french) )
{'Monday': 'Lundi', 'Tuesday': 'Mardi', 'Wednesday': 'Mercredi'}
利用列表推导式创建列表
你已经了解如何使用方括号或 list() 函数创建列表。这里,我们将看看如何使用 列表推导式 创建列表,它包含了你刚刚看到的 for/in 迭代。
你可以像这样逐个项地构建从 1 到 5 的整数列表:
>>> number_list = []
>>> number_list.append(1)
>>> number_list.append(2)
>>> number_list.append(3)
>>> number_list.append(4)
>>> number_list.append(5)
>>> number_list
[1, 2, 3, 4, 5]
或者,你也可以使用迭代器和 range() 函数:
>>> number_list = []
>>> for number in range(1, 6):
... number_list.append(number)
...
>>> number_list
[1, 2, 3, 4, 5]
或者,你可以直接将 range() 的输出转换为列表:
>>> number_list = list(range(1, 6))
>>> number_list
[1, 2, 3, 4, 5]
所有这些方法都是有效的 Python 代码,并且会产生相同的结果。然而,更 Pythonic(而且通常更快)的构建列表的方式是使用列表推导式。列表推导式的最简单形式看起来像这样:
[*`expression`* for *`item`* in *`iterable`*]
下面是一个列表推导式如何构建整数列表:
>>> number_list = [number for number in range(1,6)]
>>> number_list
[1, 2, 3, 4, 5]
在第一行,你需要第一个number变量为列表生成值:也就是说,将循环的结果放入number_list中。第二个number是循环的一部分。为了显示第一个number是一个表达式,请尝试这个变体:
>>> number_list = [number-1 for number in range(1,6)]
>>> number_list
[0, 1, 2, 3, 4]
列表推导式将循环移到方括号内部。这个推导式示例并不比之前的示例更简单,但你可以做更多。列表推导式可以包含条件表达式,看起来像这样:
[*`expression`* for *`item`*
in *`iterable`* if *`condition`*]
让我们创建一个新的推导式,构建一个仅包含1到5之间奇数的列表(记住number % 2对于奇数为True,对于偶数为False):
>>> a_list = [number for number in range(1,6) if number % 2 == 1]
>>> a_list
[1, 3, 5]
现在,推导式比其传统的对应部分更紧凑了一点:
>>> a_list = []
>>> for number in range(1,6):
... if number % 2 == 1:
... a_list.append(number)
...
>>> a_list
[1, 3, 5]
最后,就像可以有嵌套循环一样,对应的推导式中还可以有超过一个for ...子句集。为了展示这一点,让我们首先尝试一个简单的嵌套循环并打印结果:
>>> rows = range(1,4)
>>> cols = range(1,3)
>>> for row in rows:
... for col in cols:
... print(row, col)
...
1 1
1 2
2 1
2 2
3 1
3 2
现在,让我们使用一个推导式,并将其分配给变量cells,使其成为一个(row, col)元组的列表:
>>> rows = range(1,4)
>>> cols = range(1,3)
>>> cells = [(row, col) for row in rows for col in cols]
>>> for cell in cells:
... print(cell)
...
(1, 1)
(1, 2)
(2, 1)
(2, 2)
(3, 1)
(3, 2)
顺便说一句,你也可以使用元组解包从每个元组中获取row和col值,当你迭代cells列表时:
>>> for row, col in cells:
... print(row, col)
...
1 1
1 2
2 1
2 2
3 1
3 2
列表推导式中的for row ...和for col ...片段也可以有它们自己的if测试。
列表的列表
列表可以包含不同类型的元素,包括其他列表,如下所示:
>>> small_birds = ['hummingbird', 'finch']
>>> extinct_birds = ['dodo', 'passenger pigeon', 'Norwegian Blue']
>>> carol_birds = [3, 'French hens', 2, 'turtledoves']
>>> all_birds = [small_birds, extinct_birds, 'macaw', carol_birds]
那么,作为列表的列表的all_birds看起来是什么样子?
>>> all_birds
[['hummingbird', 'finch'], ['dodo', 'passenger pigeon', 'Norwegian Blue'], 'macaw',
[3, 'French hens', 2, 'turtledoves']]
让我们看看其中的第一个项目:
>>> all_birds[0]
['hummingbird', 'finch']
第一项是一个列表:实际上,它是我们创建all_birds时指定的small_birds的第一项。你应该能够猜到第二项是什么:
>>> all_birds[1]
['dodo', 'passenger pigeon', 'Norwegian Blue']
它是我们指定的第二项,extinct_birds。如果我们想要extinct_birds的第一项,我们可以通过指定两个索引从all_birds中提取它:
>>> all_birds[1][0]
'dodo'
[1]指的是all_birds中第二个项目的列表,而[0]指的是该内部列表中的第一个项目。
元组与列表
你经常可以在列表的地方使用元组,但它们的功能要少得多——没有append()、insert()等方法——因为它们创建后无法修改。为什么不在任何地方都使用列表呢?
-
元组使用更少的空间。
-
你不会因为错误而破坏元组项。
-
你可以使用元组作为字典的键(参见第八章)。
-
命名元组(参见“命名元组”)可以作为对象的简单替代。
我不会在这里详细讨论元组。在日常编程中,你将更多地使用列表和字典。
没有元组推导式
可变类型(列表、字典和集合)有理解式。不可变类型如字符串和元组需要使用其各自章节中列出的其他方法创建。
您可能认为将列表推导的方括号更改为圆括号将创建元组推导。它似乎确实有效,因为如果您键入以下内容,将不会出现异常:
>>> number_thing = (number for number in range(1, 6))
括号中的东西完全不同:生成器推导,它返回一个 生成器对象:
>>> type(number_thing)
<class 'generator'>
我将在“生成器”一章中详细讨论生成器。生成器是向迭代器提供数据的一种方式。
即将到来
它们如此出色,它们有自己的章节:字典 和 集合。
要做的事情
使用列表和元组与数字(第三章)和字符串(第五章)来表示具有丰富多样性的现实世界元素。
7.1 创建一个名为years_list的列表,从您的出生年份开始,直到您五岁生日的年份。例如,如果您出生于 1980 年,则列表将是years_list = [1980, 1981, 1982, 1983, 1984, 1985]。如果您不到五岁正在阅读本书,那我也不知道该怎么办。
7.2 years_list中哪一年是你三岁生日的那一年?记住,你的第一年是 0 岁。
7.3 years_list中哪一年你最大?
7.4 使用这三个字符串作为元素创建名为things的列表:"mozzarella"、"cinderella"、"salmonella"。
7.5 将things中指向人的元素大写,然后打印列表。它改变了列表中的元素吗?
7.6 将things中“cheesy”的元素全部大写,然后打印列表。
7.7 删除things中的"disease"元素,收集您的诺贝尔奖,并打印列表。
7.8 创建一个名为surprise的列表,其中包含元素"Groucho"、"Chico"和"Harpo"。
7.9 将surprise列表的最后一个元素转为小写,反转它,然后大写化。
7.10 使用列表推导创建一个名为even的列表,其中包含range(10)中的偶数。
7.11 让我们创建一个跳绳打油诗生成器。您将打印一系列两行打油诗。从以下程序片段开始:
start1 = ["fee", "fie", "foe"]
rhymes = [
("flop", "get a mop"),
("fope", "turn the rope"),
("fa", "get your ma"),
("fudge", "call the judge"),
("fat", "pet the cat"),
("fog", "walk the dog"),
("fun", "say we're done"),
]
start2 = "Someone better"
对于rhymes中的每个元组(first,second):
对于第一行:
-
打印
start1中的每个字符串,大写化,并跟一个感叹号和一个空格。 -
打印
first,并将其大写化,然后跟一个感叹号。
对于第二行:
-
打印
start2和一个空格。 -
打印
second和一个句点。