用 Linkbot 学习 Python 3 / 列表进阶
我们已经了解了列表以及如何使用它们。现在你已经具备了一些基础知识,我将更详细地介绍列表。首先,我们将探索更多访问列表元素的方法,然后我们将讨论如何复制它们。
以下是一些使用索引访问列表单个元素的示例
>>> some_numbers = ['zero', 'one', 'two', 'three', 'four', 'five'] >>> some_numbers[0] 'zero' >>> some_numbers[4] 'four' >>> some_numbers[5] 'five'
所有这些示例对你来说应该很熟悉。如果你想获取列表中的第一个项目,只需查看索引 0。第二个项目是索引 1,以此类推。但是,如果你想获取列表中的最后一个项目怎么办?一种方法是使用 len()
函数,例如 some_numbers[len(some_numbers) - 1]
。这种方法有效,因为 len()
函数始终返回最后一个索引加 1。那么倒数第二个项目就是 some_numbers[len(some_numbers) - 2]
。但有一种更简单的方法。在 Python 中,最后一个项目始终是索引 -1。倒数第二个是索引 -2,以此类推。以下是一些更多示例
>>> some_numbers[len(some_numbers) - 1] 'five' >>> some_numbers[len(some_numbers) - 2] 'four' >>> some_numbers[-1] 'five' >>> some_numbers[-2] 'four' >>> some_numbers[-6] 'zero'
因此,列表中的任何项目都可以通过两种方式进行索引:从前面和从后面。
另一种访问列表部分的有用方法是使用切片。以下是一个示例,让你了解它们的使用场景
>>> things = [0, 'Fred', 2, 'S.P.A.M.', 'Stocking', 42, "Jack", "Jill"] >>> things[0] 0 >>> things[7] 'Jill' >>> things[0:8] [0, 'Fred', 2, 'S.P.A.M.', 'Stocking', 42, 'Jack', 'Jill'] >>> things[2:4] [2, 'S.P.A.M.'] >>> things[4:7] ['Stocking', 42, 'Jack'] >>> things[1:5] ['Fred', 2, 'S.P.A.M.', 'Stocking']
切片用于返回列表的一部分。切片操作符的形式为 things[first_index:last_index]
。切片在 first_index
之前和 last_index
之前切割列表,并返回中间的部分。你可以使用两种类型的索引
>>> things[-4:-2] ['Stocking', 42] >>> things[-4] 'Stocking' >>> things[-4:6] ['Stocking', 42]
切片另一个技巧是未指定索引。如果未指定第一个索引,则假定为列表的开头。如果未指定最后一个索引,则假定为整个剩余列表。以下是一些示例
>>> things[:2] [0, 'Fred'] >>> things[-2:] ['Jack', 'Jill'] >>> things[:3] [0, 'Fred', 2] >>> things[:-5] [0, 'Fred', 2]
这是一个(受 HTML 启发)的程序示例(如果你想的话,可以将诗歌定义复制粘贴进去)
poem = ["<B>", "Jack", "and", "Jill", "</B>", "went", "up", "the",
"hill", "to", "<B>", "fetch", "a", "pail", "of", "</B>",
"water.", "Jack", "fell", "<B>", "down", "and", "broke",
"</B>", "his", "crown", "and", "<B>", "Jill", "came",
"</B>", "tumbling", "after"]
def get_bolds(text):
true = 1
false = 0
## is_bold tells whether or not we are currently looking at
## a bold section of text.
is_bold = false
## start_block is the index of the start of either an unbolded
## segment of text or a bolded segment.
start_block = 0
for index in range(len(text)):
## Handle a starting of bold text
if text[index] == "<B>":
if is_bold:
print("Error: Extra Bold")
## print "Not Bold:", text[start_block:index]
is_bold = true
start_block = index + 1
## Handle end of bold text
## Remember that the last number in a slice is the index
## after the last index used.
if text[index] == "</B>":
if not is_bold:
print("Error: Extra Close Bold")
print("Bold [", start_block, ":", index, "]", text[start_block:index])
is_bold = false
start_block = index + 1
get_bolds(poem)
输出结果为
Bold [ 1 : 4 ] ['Jack', 'and', 'Jill'] Bold [ 11 : 15 ] ['fetch', 'a', 'pail', 'of'] Bold [ 20 : 23 ] ['down', 'and', 'broke'] Bold [ 28 : 30 ] ['Jill', 'came']
get_bold()
函数接受一个被分成单词和标记的列表。它查找的标记是 <B>
,它表示粗体文本的开始,以及 </B>
,它表示粗体文本的结束。get_bold()
函数遍历列表,搜索开始和结束标记。
列表的下一个特性是复制它们。如果你尝试像下面这样简单的方法
>>> a = [1, 2, 3] >>> b = a >>> print(b) [1, 2, 3] >>> b[1] = 10 >>> print(b) [1, 10, 3] >>> print(a) [1, 10, 3]
这可能看起来令人惊讶,因为对 b
的修改也导致了 a
的改变。发生的情况是语句 b = a
使 b
成为 a
的引用。这意味着 b
可以被认为是 a
的另一个名称。因此,对 b
的任何修改都会改变 a
。但是,有些赋值不会为一个列表创建两个名称
>>> a = [1, 2, 3] >>> b = a * 2 >>> print(a) [1, 2, 3] >>> print(b) [1, 2, 3, 1, 2, 3] >>> a[1] = 10 >>> print(a) [1, 10, 3] >>> print(b) [1, 2, 3, 1, 2, 3]
在这种情况下,b
不是 a
的引用,因为表达式 a * 2
创建了一个新的列表。然后语句 b = a * 2
使 b
成为 a * 2
的引用,而不是 a
的引用。所有赋值操作都会创建一个引用。当你将一个列表作为参数传递给一个函数时,你也会创建一个引用。大多数情况下,你无需担心创建引用而不是副本。但是,当你需要修改一个列表而不改变该列表的其他名称时,你必须确保你实际上已经创建了一个副本。
有多种方法可以创建列表的副本。最简单且大多数情况下有效的方法是使用切片操作符,因为它始终会创建一个新的列表,即使它是一个整个列表的切片
>>> a = [1, 2, 3] >>> b = a[:] >>> b[1] = 10 >>> print(a) [1, 2, 3] >>> print(b) [1, 10, 3]
获取切片 [:]
会创建一个列表的新副本。但是,它只复制外层列表。内部的任何子列表仍然是原始列表中子列表的引用。因此,当列表包含列表时,必须复制内层列表。你可以手动执行此操作,但 Python 已经包含一个模块来完成它。你可以使用 copy
模块的 deepcopy
函数
>>> import copy >>> a = [[1, 2, 3], [4, 5, 6]] >>> b = a[:] >>> c = copy.deepcopy(a) >>> b[0][1] = 10 >>> c[1][1] = 12 >>> print(a) [[1, 10, 3], [4, 5, 6]] >>> print(b) [[1, 10, 3], [4, 5, 6]] >>> print(c) [[1, 2, 3], [4, 12, 6]]
首先,请注意 a
是一个包含列表的列表。然后请注意,当运行 b[0][1] = 10
时,a
和 b
都发生了改变,但 c
没有。这是因为使用切片操作符时,内层数组仍然是引用。但是,使用 deepcopy
,c
被完全复制了。
那么,每次使用函数或 =
时,我都要担心引用吗?好消息是,你只需要在使用字典和列表时担心引用。数字和字符串在赋值时会创建引用,但对数字和字符串的任何修改操作都会创建一个新的副本,因此你永远不会意外地修改它们。当你修改列表或字典时,你确实需要考虑引用。
到目前为止,你可能想知道为什么使用引用?主要原因是速度。创建对包含一千个元素的列表的引用比复制所有元素快得多。另一个原因是它允许你创建一个函数来修改输入的列表或字典。只要记住引用,如果你遇到一些奇怪的数据改变问题,而它不应该改变。