Python 编程思想/元组
元组是值的序列。这些值可以是任何类型,并且它们由整数索引,因此在这方面元组很像列表。重要的区别是元组是不可变的。
从语法上讲,元组是逗号分隔的值列表
>>> t = 'a', 'b', 'c', 'd', 'e'
虽然没有必要,但通常用括号括住元组
>>> t = ('a', 'b', 'c', 'd', 'e')
要创建一个只有一个元素的元组,您必须包含最后一个逗号
>>> t1 = ('a',)
>>> type(t1)
<type 'tuple'>
如果没有逗号,Python 会将 ('a')
视为括号中的字符串
>>> t2 = ('a')
>>> type(t2)
<type 'str'>
另一种创建元组的方法是使用内置函数 tuple
。没有参数,它将创建一个空元组
>>> t = tuple()
>>> print t
()
如果参数是序列(字符串、列表或元组),则结果是包含序列元素的元组
>>> t = tuple('lupins')
>>> print t
('l', 'u', 'p', 'i', 'n', 's')
因为 tuple
是内置函数的名称,所以您应该避免使用它作为变量名。
大多数列表运算符也适用于元组。方括号运算符对元素进行索引
>>> t = ('a', 'b', 'c', 'd', 'e')
>>> print t[0]
'a'
切片运算符选择一系列元素。
>>> print t[1:3]
('b', 'c')
但是,如果您尝试修改元组中的元素之一,则会出现错误
>>> t[0] = 'A'
TypeError: object doesn't support item assignment
您不能修改元组的元素,但您可以用另一个元组替换一个元组
>>> t = ('A',) + t[1:]
>>> print t
('A', 'b', 'c', 'd', 'e')
交换两个变量的值通常很有用。对于传统的赋值,您必须使用临时变量。例如,要交换 a
和 b
>>> temp = a
>>> a = b
>>> b = temp
此解决方案很繁琐;元组赋值更优雅
>>> a, b = b, a
左侧是变量元组;右侧是表达式元组。每个值都被分配给各自的变量。右侧的所有表达式在任何赋值之前都被计算。
左侧的变量数量和右侧的值数量必须相同
>>> a, b = 1, 2, 3
ValueError: too many values to unpack
更一般地说,右侧可以是任何类型的序列(字符串、列表或元组)。例如,要将电子邮件地址拆分为用户名和域名,您可以编写
>>> addr = '[email protected]'
>>> uname, domain = addr.split('@')
split
的返回值是包含两个元素的列表;第一个元素分配给 uname
,第二个分配给 domain
。
>>> print uname
monty
>>> print domain
python.org
严格来说,函数只能返回一个值,但如果该值是元组,则其效果与返回多个值相同。例如,如果您想将两个整数相除并计算商和余数,则计算 x/y
然后计算 x%y
很低效。最好同时计算两者。
内置函数 divmod
接受两个参数并返回包含两个值的元组,即商和余数。您可以将结果存储为元组
>>> t = divmod(7, 3)
>>> print t
(2, 1)
或者使用元组赋值单独存储元素
>>> quot, rem = divmod(7, 3)
>>> print quot
2
>>> print rem
1
这是一个返回元组的函数示例
def min_max(t):
return min(t), max(t)
max
和 min
是内置函数,用于查找序列中的最大和最小元素。min_max
计算两者并返回包含两个值的元组。
函数可以接受可变数量的参数。以 *
开头的参数名将参数收集到元组中。例如,printall
接受任意数量的参数并打印它们
def printall(*args):
print args
收集参数可以是任何你喜欢的名称,但 args
是约定俗成的。以下是函数的工作原理
>>> printall(1, 2.0, '3')
(1, 2.0, '3')
您可以将收集运算符与必需参数和位置参数组合起来
def pointless(required, optional=0, *args):
print required, optional, args
使用 1、2、3 和 4 个或更多参数运行此函数,并确保您了解它的作用。
收集的补充是散布。如果您有一系列值并且想将其作为多个参数传递给函数,您可以使用 *
运算符。例如,divmod
恰好接受两个参数;它不接受元组
>>> t = (7, 3)
>>> divmod(t)
TypeError: divmod expected 2 arguments, got 1
但是,如果您散布元组,它就可以工作
>>> divmod(*t)
(2, 1)
许多内置函数使用可变长度参数元组。例如,'max
' 和 'min
' 可以接受任意数量的参数
''>>> max(1,2,3)
3
''
但 'sum
' 则不行。
''>>> sum(1,2,3)
TypeError: sum expected at most 2 arguments, got 3
''
编写一个名为 'sumall
' 的函数,它接受任意数量的参数并返回它们的总和。
zip
是一个内置函数,它接受两个或多个序列并将它们“压缩”成一个包含元组的列表1,其中每个元组都包含来自每个序列的一个元素。
此示例压缩字符串和列表
>>> s = 'abc'
>>> t = [0, 1, 2]
>>> zip(s, t)
[('a', 0), ('b', 1), ('c', 2)]
结果是一个包含元组的列表,其中每个元组都包含字符串中的一个字符和列表中的相应元素。
如果序列的长度不同,则结果的长度与最短序列的长度相同。
>>> zip('Anne', 'Elk')
[('A', 'E'), ('n', 'l'), ('n', 'k')]
您可以在 for
循环中使用元组赋值来遍历元组列表
t = [('a', 0), ('b', 1), ('c', 2)]
for letter, number in t:
print number, letter
每次循环时,Python 都会从列表中选择下一个元组并将元素分配给 letter
和 number
。此循环的输出为
0 a
1 b
2 c
如果您将 zip
、for
和元组赋值组合起来,您将得到一个有用的习语,用于同时遍历两个(或多个)序列。例如,has_match
接受两个序列 t1
和 t2
,如果存在一个索引 i
使得 t1[i] == t2[i]
,则返回 True
def has_match(t1, t2):
for x, y in zip(t1, t2):
if x == y:
return True
return False
如果您需要遍历序列的元素及其索引,可以使用内置函数 enumerate
for index, element in enumerate('abc'):
print index, element
此循环的输出为
0 a
1 b
2 c
同样。
字典有一个名为 items
的方法,它返回一个元组列表,其中每个元组都是一个键值对2。
>>> d = {'a':0, 'b':1, 'c':2}
>>> t = d.items()
>>> print t
[('a', 0), ('c', 2), ('b', 1)]
正如您对字典的期望,这些项没有特定的顺序。
相反,您可以使用元组列表来初始化一个新的字典
>>> t = [('a', 0), ('c', 2), ('b', 1)]
>>> d = dict(t)
>>> print d
{'a': 0, 'c': 2, 'b': 1}
将 dict
与 zip
组合起来可以提供一种简洁的方式来创建字典
>>> d = dict(zip('abc', range(3)))
>>> print d
{'a': 0, 'c': 2, 'b': 1}
字典方法 update
也接受一个元组列表并将它们作为键值对添加到现有字典中。
将 items
、元组赋值和 for
组合起来,您可以得到遍历字典键和值的习语
for key, val in d.items():
print val, key
此循环的输出为
0 a
2 c
1 b
同样。
在字典中使用元组作为键是很常见的(主要是因为不能使用列表)。例如,电话簿可以将姓氏和名字的组合映射到电话号码。假设我们已经定义了last
、first
和number
,我们可以写成
directory[last,first] = number
方括号中的表达式是一个元组。我们可以使用元组赋值来遍历这个字典。
for last, first in directory:
print first, last, directory[last,first]
这个循环遍历了directory
中的键,这些键都是元组。它将每个元组的元素赋值给last
和first
,然后打印名字和相应的电话号码。
在状态图中,可以用两种方法表示元组。更详细的方法是显示索引和元素,就像它们出现在列表中一样。例如,元组('Cleese', 'John')
将显示为
但在更大的图中,你可能希望省略细节。例如,电话簿的图可能显示为
这里使用 Python 语法作为图形速记来显示元组。
图中的电话号码是 BBC 的投诉热线,请不要拨打。
比较元组
[edit | edit source]比较运算符适用于元组和其他序列;Python 从比较每个序列的第一个元素开始。如果它们相等,则继续比较下一个元素,依此类推,直到找到不同的元素。后面的元素不再考虑(即使它们很大)。
>>> (0, 1, 2) < (0, 3, 4)
True
>>> (0, 1, 2000000) < (0, 3, 4)
True
sort
函数的工作方式相同。它主要按第一个元素排序,但在出现相同的情况下,它按第二个元素排序,依此类推。
此特性适用于称为DSU的模式,用于
- 装饰
- 通过构建一个元组列表,其中一个或多个排序键位于序列元素之前,来对序列进行装饰。
- 排序
- 排序元组列表,以及
- 取消装饰
- 通过提取序列的排序元素来取消装饰。
例如,假设你有一个单词列表,你想按从长到短的顺序排序
def sort_by_length(words):
t = []
for word in words:
t.append((len(word), word))
t.sort(reverse=True)
res = []
for length, word in t:
res.append(word)
return res
第一个循环构建一个元组列表,其中每个元组都是一个单词,前面是它的长度。
sort
首先比较第一个元素长度,如果相等,则只考虑第二个元素。关键字参数reverse=True
告诉sort
按降序排序。
第二个循环遍历元组列表,并构建一个按长度降序排列的单词列表。
练习 2
[edit | edit source]在这个例子中,相同情况会通过比较单词来解决,因此具有相同长度的单词会按字母顺序排列。对于其他应用程序,你可能希望随机解决相同情况。修改此示例,以便具有相同长度的单词以随机顺序排列。提示:查看'random
'模块中的'random
'函数。
序列的序列
[edit | edit source]我重点介绍了元组列表,但这章中的几乎所有示例也适用于列表列表、元组元组和列表元组。为了避免枚举所有可能的组合,有时更容易讨论序列的序列。
在许多情况下,不同类型的序列(字符串、列表和元组)可以互换使用。那么你如何以及为什么选择其中之一呢?
首先,字符串比其他序列更受限制,因为元素必须是字符。它们也是不可变的。如果你需要更改字符串中的字符的能力(而不是创建一个新的字符串),你可能希望使用字符列表。
列表比元组更常见,主要是因为它们是可变的。但有一些情况你可能更喜欢元组
- 在某些情况下,例如
return
语句,创建元组比创建列表在语法上更简单。在其他情况下,你可能更喜欢列表。 - 如果你想使用序列作为字典键,则必须使用不可变类型,例如元组或字符串。
- 如果你将序列作为参数传递给函数,则使用元组可以减少由于别名导致的意外行为的可能性。
由于元组是不可变的,因此它们不提供像sort
和reverse
这样的方法,这些方法会修改现有的列表。但是 Python 提供了内置函数sorted
和reversed
,它们接受任何序列作为参数,并返回一个包含相同元素的按不同顺序排列的新列表。
调试
[edit | edit source]列表、字典和元组通常被称为数据结构;在本节中,我们开始看到复合数据结构,例如元组列表,以及包含元组作为键和列表作为值的字典。复合数据结构很有用,但它们容易出现我称为形状错误的错误;也就是说,由数据结构具有错误的类型、大小或组成导致的错误。例如,如果你期望一个包含一个整数的列表,而我给你一个普通的整数(不在列表中),它将不起作用。
为了帮助调试这些类型的错误,我编写了一个名为structshape
的模块,它提供了一个名为structshape
的函数,该函数接受任何类型的数据结构作为参数,并返回一个总结其形状的字符串。你可以在thinkpython.com/code/structshape.py
下载它
以下是简单列表的结果
>>> from structshape import structshape
>>> t = [1,2,3]
>>> print structshape(t)
list of 3 int
一个更复杂的程序可能会写“包含 3 个 ints 的列表”,但这更容易避免处理复数。以下是一个列表列表
>>> t2 = [[1,2], [3,4], [5,6]]
>>> print structshape(t2)
list of 3 list of 2 int
如果列表的元素类型不同,structshape
会按类型对它们进行分组,并按顺序排列
>>> t3 = [1, 2, 3, 4.0, '5', '6', [7], [8], 9]
>>> print structshape(t3)
list of (3 int, float, 2 str, 2 list of int, int)
以下是一个元组列表
>>> s = 'abc'
>>> lt = zip(t, s)
>>> print structshape(lt)
list of 3 tuple of (int, str)
以下是一个包含 3 个项的字典,它们将整数映射到字符串。
>>> d = dict(lt)
>>> print structshape(d)
dict of 3 int->str
如果你难以跟踪你的数据结构,structshape
可以提供帮助。
词汇表
[edit | edit source]- 元组
- 不可变的元素序列。
- 元组赋值
- 在右边使用序列,在左边使用元组变量进行的赋值。右侧被计算,然后其元素被赋值给左侧的变量。
- 收集
- 组装可变长度参数元组的操作。
- 分散
- 将序列视为参数列表的操作。
- DSU
- “装饰-排序-取消装饰”的缩写,这种模式涉及构建一个元组列表、排序和提取结果的一部分。
- 数据结构
- 相关值的集合,通常组织在列表、字典、元组等中。
- 形状(数据结构的形状)
- 对数据结构的类型、大小和组成的总结。
练习
[edit | edit source]练习 3
[edit | edit source]编写一个名为most_frequent
的函数,它接收一个字符串,并按频率递减的顺序打印字母。从几种不同的语言中找到文本样本,并查看不同语言之间的字母频率差异。将你的结果与'wikipedia.org/wiki/Letter_frequencies
'中的表格进行比较。
练习 4
[edit | edit source]更多 anagrams!
- 编写一个程序,从文件(见第 '9.1' 节)读取单词列表,并打印所有是 anagrams 的单词集。
以下是一个可能的输出示例
''['deltas', 'desalt', 'lasted', 'salted', 'slated', 'staled']
['retainers', 'ternaries']
['generating', 'greatening']
['resmelts', 'smelters', 'termless']
''
提示:你可能希望构建一个字典,将字母集映射到可以用这些字母拼写的单词列表。问题是,你如何用可以作为键的方式表示字母集?
- 修改前面的程序,以便它首先打印最大的 anagrams 集,然后打印第二大的集合,依此类推。
- 在 Scrabble 中,“bingo”是指当你玩了你牌架上的所有七个棋子,以及棋盘上的一个字母,形成一个八个字母的单词。哪组 8 个字母可以形成最多的可能的 bingo?提示:有七个。
- '如果你可以通过交换两个字母来将一个单词转换为另一个单词,那么这两个单词构成一个“metathesis 对”''3'';例如,“converse”和“conserve”。编写一个程序,找到字典中所有的 metathesis 对。提示:不要测试所有单词对,也不要测试所有可能的交换。'
'您可以从 '''thinkpython.com/code/anagram_sets.py
'''下载解决方案。'
这是另一个 Car Talk 谜题4:
最长的英语单词是什么,在您逐个删除字母时仍然是有效的英语单词?
现在,可以从两端或中间删除字母,但不能重新排列任何字母。每次删除一个字母后,都会得到另一个英语单词。如果您这样做,最终会得到一个字母,它也是英语单词——字典中找到的单词。我想知道最长的单词是什么,以及它有多少个字母?
我将给您一个简单的例子:Sprite。好的?从 sprite 开始,删除一个字母,从单词的内部删除一个字母,删除 r,剩下 spite,然后从末尾删除 e,剩下 spit,删除 s,剩下 pit,it,和 I。
编写一个程序来查找所有可以以这种方式缩减的单词,然后找到最长的单词。
这个练习比大多数练习更具挑战性,所以这里有一些建议
- 您可能需要编写一个函数,该函数接受一个单词并计算所有可以通过删除一个字母形成的单词。这些是单词的“子节点”。
- 递归地,如果一个单词的任何子节点是可缩减的,那么它就是可缩减的。作为基本情况,您可以考虑空字符串是可缩减的。
- 我提供的单词列表 '
words.txt
' 不包含单个字母的单词。因此,您可能需要添加“I”、“a”和空字符串。 - 为了提高程序的性能,您可能需要记忆已知可缩减的单词。
您可以在 'thinkpython.com/code/reducible.py
' 查看我的解决方案。
- 1
- 在 Python 3.0 中,
zip
返回元组的迭代器,但对于大多数目的而言,迭代器表现得像列表。 - 2
- 这种行为在 Python 3.0 中略有不同。
- 3
- 这个练习的灵感来自
puzzlers.org
上的一个例子。 - 4
-
www.cartalk.com/content/puzzler/transcripts/200651