跳转到内容

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

元组赋值

[编辑 | 编辑源代码]

交换两个变量的值通常很有用。对于传统的赋值,您必须使用临时变量。例如,要交换 ab

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

maxmin 是内置函数,用于查找序列中的最大和最小元素。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 都会从列表中选择下一个元组并将元素分配给 letternumber。此循环的输出为

0 a
1 b
2 c

如果您将 zipfor 和元组赋值组合起来,您将得到一个有用的习语,用于同时遍历两个(或多个)序列。例如,has_match 接受两个序列 t1t2,如果存在一个索引 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}

dictzip 组合起来可以提供一种简洁的方式来创建字典

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

同样。

在字典中使用元组作为键是很常见的(主要是因为不能使用列表)。例如,电话簿可以将姓氏和名字的组合映射到电话号码。假设我们已经定义了lastfirstnumber,我们可以写成

directory[last,first] = number

方括号中的表达式是一个元组。我们可以使用元组赋值来遍历这个字典。

for last, first in directory:
    print first, last, directory[last,first]

这个循环遍历了directory中的键,这些键都是元组。它将每个元组的元素赋值给lastfirst,然后打印名字和相应的电话号码。

在状态图中,可以用两种方法表示元组。更详细的方法是显示索引和元素,就像它们出现在列表中一样。例如,元组('Cleese', 'John')将显示为

<IMG SRC="book020.png">

但在更大的图中,你可能希望省略细节。例如,电话簿的图可能显示为

<IMG SRC="book021.png">

这里使用 Python 语法作为图形速记来显示元组。

图中的电话号码是 BBC 的投诉热线,请不要拨打。

比较元组

[edit | edit source]

比较运算符适用于元组和其他序列;Python 从比较每个序列的第一个元素开始。如果它们相等,则继续比较下一个元素,依此类推,直到找到不同的元素。后面的元素不再考虑(即使它们很大)。

>>> (0, 1, 2) &lt; (0, 3, 4)
True
>>> (0, 1, 2000000) &lt; (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语句,创建元组比创建列表在语法上更简单。在其他情况下,你可能更喜欢列表。
  • 如果你想使用序列作为字典键,则必须使用不可变类型,例如元组或字符串。
  • 如果你将序列作为参数传递给函数,则使用元组可以减少由于别名导致的意外行为的可能性。

由于元组是不可变的,因此它们不提供像sortreverse这样的方法,这些方法会修改现有的列表。但是 Python 提供了内置函数sortedreversed,它们接受任何序列作为参数,并返回一个包含相同元素的按不同顺序排列的新列表。

调试

[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
华夏公益教科书