我们已经看过了列表及其用法。现在,在你有了更多的背景知识后,我将更详细地介绍列表。首先,我们将看看获取列表中元素的更多方法,然后我们将讨论如何复制它们。

以下是使用索引访问列表单个元素的一些示例:

>>> 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() 函数总是返回最后一个索引加一。倒数第二项可以是 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_indexlast_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 定义):

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_bold(text):
    true = 1
    false = 0
    ## is_bold 表示我们是否当前正在查看
    ## 粗体文本部分。
    is_bold = false
    ## start_block 是未加粗文本或加粗文本段落的开始索引。
    start_block = 0
    for index in range(len(text)):
        ## 处理加粗文本的开始
        if text[index] == "<B>":
            if is_bold:
                print("错误:额外的加粗")
            ## print "未加粗:", text[start_block:index]
            is_bold = true
            start_block = index + 1
        ## 处理加粗文本的结束
        ## 记住切片中的最后一个数字是最后一个索引后的索引。
        if text[index] == "</B>":
            if not is_bold:
                print("错误:多余的闭合加粗标签")
            print("加粗 [", start_block, ":", index, "]", text[start_block:index])
            is_bold = false
            start_block = index + 1

get_bold(poem)

这个程序的作用是从文本中提取出加粗(<B>...</B>)的部分,并打印出来。如果有多余的加粗或闭合标签,会输出错误提示。

输出结果如下:

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 * 2b 赋值为 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 时,ab 都发生了变化,但 c 没有变化。这是因为使用切片操作符时,内部数组仍然是引用。然而,使用 deepcopy 时,c 被完全复制。

那么,我是否每次使用函数或 = 时都需要担心引用呢?好消息是,你只需要在使用字典和列表时担心引用。数字和字符串在赋值时会创建引用,但对数字和字符串的每次操作都创建一个新副本,因此你永远不会无意中修改它们。当你修改列表或字典时,才需要考虑引用问题。

到现在你可能会问,为什么要使用引用呢?基本的原因是速度。创建一个指向千元素列表的引用比复制所有元素要快得多。另一个原因是,它允许你拥有一个修改输入列表或字典的函数。只要记住,如果你遇到数据在不该修改时被修改的奇怪问题,考虑引用的使用。

Last modified: Saturday, 11 January 2025, 11:32 AM