MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python索引机制的全面解读

2021-10-215.2k 阅读

Python 索引机制基础概念

在 Python 中,索引(Indexing)是一种用于访问序列(如字符串、列表、元组等)中特定元素的强大工具。序列是一种有序的数据集合,其中的每个元素都有一个与之关联的位置编号,这个编号就是索引。通过索引,我们可以精准地定位并获取序列中的某个元素,或者对其进行修改(对于可变序列)。

正向索引

Python 中最常见的索引方式是正向索引。序列中的第一个元素的索引为 0,第二个元素的索引为 1,以此类推。例如,对于一个列表 my_list = [10, 20, 30, 40, 50]my_list[0] 会返回 10,my_list[2] 会返回 30。下面是具体的代码示例:

my_list = [10, 20, 30, 40, 50]
print(my_list[0])  
print(my_list[2])  

在字符串中同样适用正向索引。例如,对于字符串 my_str = "Hello, World!"my_str[0] 会返回字符 'H'my_str[7] 会返回字符 'W'。代码示例如下:

my_str = "Hello, World!"
print(my_str[0])  
print(my_str[7])  

负向索引

除了正向索引,Python 还支持负向索引。负向索引从序列的末尾开始计数,最后一个元素的索引为 -1,倒数第二个元素的索引为 -2,依此类推。继续以列表 my_list = [10, 20, 30, 40, 50] 为例,my_list[-1] 会返回 50,my_list[-3] 会返回 30。代码如下:

my_list = [10, 20, 30, 40, 50]
print(my_list[-1])  
print(my_list[-3])  

字符串也可以使用负向索引。对于 my_str = "Hello, World!"my_str[-1] 会返回字符 '!'my_str[-7] 会返回字符 'W'。代码示例:

my_str = "Hello, World!"
print(my_str[-1])  
print(my_str[-7])  

索引的边界

在使用索引时,需要注意索引的边界。对于一个长度为 n 的序列,正向索引的范围是从 0 到 n - 1,负向索引的范围是从 -1 到 -n。如果使用超出这个范围的索引,Python 会抛出 IndexError 异常。例如:

my_list = [10, 20, 30, 40, 50]
try:
    print(my_list[5])  
except IndexError as e:
    print(f"捕获到索引错误: {e}")
try:
    print(my_list[-6])  
except IndexError as e:
    print(f"捕获到索引错误: {e}")

上述代码中,尝试访问 my_list[5]my_list[-6] 都会导致 IndexError 异常,因为它们超出了合法的索引范围。

序列类型与索引

字符串索引

字符串是 Python 中最常用的序列类型之一。如前文所述,字符串可以通过正向和负向索引访问其中的字符。此外,字符串是不可变的,这意味着一旦创建,就不能直接修改其中的字符。例如,以下代码会报错:

my_str = "Hello"
try:
    my_str[1] = 'a'
except TypeError as e:
    print(f"捕获到类型错误: {e}")

如果需要修改字符串,可以通过切片(后面会详细介绍)和字符串拼接等方法来创建新的字符串。

列表索引

列表是一种可变的序列类型,这意味着可以通过索引修改列表中的元素。例如:

my_list = [10, 20, 30, 40, 50]
my_list[2] = 35
print(my_list)  

上述代码将列表 my_list 中索引为 2 的元素从 30 修改为 35。列表还支持嵌套,即列表中的元素可以是其他列表。对于嵌套列表,需要使用多层索引来访问内部列表中的元素。例如:

nested_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(nested_list[1][2])  

这里 nested_list[1] 访问到内部列表 [4, 5, 6],然后 [2] 再从这个内部列表中访问到元素 6。

元组索引

元组与列表类似,但元组是不可变的。元组使用小括号定义,同样可以通过正向和负向索引访问其中的元素。例如:

my_tuple = (10, 20, 30, 40, 50)
print(my_tuple[3])  
print(my_tuple[-2])  

由于元组不可变,尝试通过索引修改元组元素会导致错误:

my_tuple = (10, 20, 30, 40, 50)
try:
    my_tuple[2] = 35
except TypeError as e:
    print(f"捕获到类型错误: {e}")

切片(Slicing)

切片的基本概念

切片是索引机制的扩展,它允许我们从序列中获取一个子序列。切片通过指定起始索引、结束索引(不包含)和步长来定义。基本语法为 sequence[start:stop:step],其中 start 是起始索引(默认为 0),stop 是结束索引(不包含,默认为序列长度),step 是步长(默认为 1)。

简单切片示例

对于列表 my_list = [10, 20, 30, 40, 50]my_list[1:3] 会返回从索引 1 开始(包含)到索引 3 结束(不包含)的子列表 [20, 30]。代码示例:

my_list = [10, 20, 30, 40, 50]
print(my_list[1:3])  

字符串同样支持切片。对于 my_str = "Hello, World!"my_str[7:12] 会返回子字符串 "World"。代码如下:

my_str = "Hello, World!"
print(my_str[7:12])  

省略起始索引

如果省略起始索引,切片会从序列的开头开始。例如,my_list[:3] 会返回 [10, 20, 30],相当于 my_list[0:3]。代码:

my_list = [10, 20, 30, 40, 50]
print(my_list[:3])  

省略结束索引

省略结束索引时,切片会一直到序列的末尾。例如,my_list[2:] 会返回 [30, 40, 50],相当于 my_list[2:len(my_list)]。代码:

my_list = [10, 20, 30, 40, 50]
print(my_list[2:])  

省略起始和结束索引

当同时省略起始和结束索引时,如 my_list[:],会返回整个序列的副本。这在需要复制序列时非常有用。例如:

my_list = [10, 20, 30, 40, 50]
new_list = my_list[:]
print(new_list)  

步长不为 1 的切片

步长可以设置为非 1 的值,用于跳过某些元素。例如,my_list[::2] 会返回从开头开始,每隔一个元素的子列表 [10, 30, 50]。代码:

my_list = [10, 20, 30, 40, 50]
print(my_list[::2])  

如果步长为负数,则会反向切片。例如,my_list[::-1] 会返回列表的反转版本 [50, 40, 30, 20, 10]。代码:

my_list = [10, 20, 30, 40, 50]
print(my_list[::-1])  

切片与可变序列

对于可变序列(如列表),切片不仅可以用于获取子序列,还可以用于修改或删除部分元素。例如,通过切片赋值可以替换列表中的一段元素:

my_list = [10, 20, 30, 40, 50]
my_list[1:3] = [25, 35]
print(my_list)  

上述代码将列表 my_list 中索引 1 到 3(不包含)的元素替换为 [25, 35]。也可以通过切片删除元素,例如:

my_list = [10, 20, 30, 40, 50]
del my_list[1:3]
print(my_list)  

这里删除了索引 1 到 3(不包含)的元素。

多维序列的索引与切片

二维列表的索引与切片

二维列表(列表的列表)是一种常见的多维序列。例如,有一个二维列表 matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]。要访问其中的元素,可以使用两层索引。例如,matrix[1][2] 会返回 6。代码示例:

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(matrix[1][2])  

对于二维列表的切片,也遵循类似的规则。例如,要获取第二行,可以使用 matrix[1:2],这会返回 [[4, 5, 6]]。要获取前两行,可以使用 matrix[:2],返回 [[1, 2, 3], [4, 5, 6]]。代码:

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(matrix[1:2])  
print(matrix[:2])  

如果要获取部分列,可以结合内层的切片。例如,要获取第一列,可以使用 [row[0] for row in matrix],这会返回 [1, 4, 7]。代码:

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
first_column = [row[0] for row in matrix]
print(first_column)  

更高维度序列的索引与切片

对于更高维度的序列(如三维列表),原理类似,只是需要更多层的索引和切片操作。例如,有一个三维列表 cube = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]。要访问 cube[0][1][0] 会返回 3。代码示例:

cube = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
print(cube[0][1][0])  

切片操作同样可以应用到更高维度。例如,cube[:1] 会返回 [[[1, 2], [3, 4]]],即第一个二维子列表。代码:

cube = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
print(cube[:1])  

索引机制在迭代中的应用

同时获取索引和元素

在遍历序列时,有时我们不仅需要元素的值,还需要知道它的索引。Python 的 enumerate 函数可以帮助我们实现这一点。例如,对于列表 my_list = [10, 20, 30],可以这样使用 enumerate

my_list = [10, 20, 30]
for index, value in enumerate(my_list):
    print(f"索引 {index} 的值为 {value}")

上述代码会同时打印出列表元素的索引和值。

根据索引选择性迭代

有时我们可能只需要迭代序列中的部分元素,这可以通过索引来实现。例如,只迭代列表中索引为偶数的元素:

my_list = [10, 20, 30, 40, 50]
for index in range(0, len(my_list), 2):
    print(my_list[index])

这里通过 range 函数生成偶数索引,然后通过这些索引访问列表中的元素。

索引机制的高级应用

利用索引实现数据筛选

在处理数据时,索引机制可以用于筛选符合特定条件的数据。例如,有一个包含学生成绩的列表 scores = [85, 90, 78, 95, 88],要获取成绩大于 90 的学生的索引,可以这样做:

scores = [85, 90, 78, 95, 88]
high_score_indices = [index for index, score in enumerate(scores) if score > 90]
print(high_score_indices)  

上述代码使用列表推导式结合 enumerate 函数,生成成绩大于 90 的学生的索引列表。

索引与排序

在对序列进行排序后,索引可以帮助我们恢复原始数据的顺序。例如,有一个列表 data = [30, 10, 20],对其排序后:

data = [30, 10, 20]
sorted_data = sorted(data)
print(sorted_data)  

如果我们想知道排序后每个元素在原始列表中的位置,可以记录排序前的索引。例如:

data = [30, 10, 20]
indexed_data = list(enumerate(data))
sorted_indexed_data = sorted(indexed_data, key=lambda x: x[1])
original_indices = [index for index, value in sorted_indexed_data]
print(original_indices)  

这里 original_indices 记录了排序后每个元素在原始列表中的索引。

索引在数据结构算法中的应用

在一些数据结构算法中,索引起着关键作用。例如,在实现哈希表时,索引用于快速定位数据。虽然 Python 内置的字典(dict)是基于哈希表实现的,但我们可以通过简单的示例来理解索引在类似结构中的应用。假设我们要实现一个简单的哈希表来存储学生的成绩,键为学生名字,值为成绩。我们可以使用字符串的哈希值作为索引的一部分:

class SimpleHashTable:
    def __init__(self):
        self.table = [None] * 10

    def hash_function(self, key):
        return hash(key) % len(self.table)

    def insert(self, key, value):
        index = self.hash_function(key)
        while self.table[index] is not None:
            if self.table[index][0] == key:
                self.table[index] = (key, value)
                return
            index = (index + 1) % len(self.table)
        self.table[index] = (key, value)

    def get(self, key):
        index = self.hash_function(key)
        while self.table[index] is not None:
            if self.table[index][0] == key:
                return self.table[index][1]
            index = (index + 1) % len(self.table)
        return None

# 使用示例
hash_table = SimpleHashTable()
hash_table.insert("Alice", 85)
hash_table.insert("Bob", 90)
print(hash_table.get("Alice"))  

在上述代码中,hash_function 生成的哈希值作为索引,用于在哈希表中定位数据。通过这种方式,我们可以实现快速的数据查找和插入操作。

通过以上对 Python 索引机制的全面解读,从基础概念到高级应用,我们可以看到索引在 Python 编程中无处不在且至关重要。无论是简单的数据访问,还是复杂的数据结构和算法实现,深入理解索引机制都能帮助我们编写出更高效、更灵活的代码。