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

Python列表元素的添加方式

2021-02-254.5k 阅读

1. append() 方法

1.1 append() 方法概述

在Python的列表操作中,append() 方法是最为基础和常用的添加元素的方式。它的作用是在列表的末尾添加一个元素。这里的元素可以是Python中的任何数据类型,包括整数、字符串、列表、字典、元组等等。从本质上来说,append() 方法修改了列表对象本身,在原列表的内存空间中,将新元素添加到末尾,并且在添加后,列表的长度会增加1。

1.2 代码示例

# 创建一个空列表
my_list = []

# 使用append()方法添加整数
my_list.append(10)
print(my_list)

# 添加字符串
my_list.append('hello')
print(my_list)

# 添加列表
sub_list = [1, 2, 3]
my_list.append(sub_list)
print(my_list)

# 添加字典
my_dict = {'name': 'Alice', 'age': 25}
my_list.append(my_dict)
print(my_list)

在上述代码中,首先创建了一个空列表 my_list。然后通过 append() 方法依次添加了整数 10、字符串 'hello'、列表 sub_list 和字典 my_dict。每次添加后打印列表,可以看到列表的内容不断更新,新元素被添加到了列表末尾。

1.3 append() 方法的性能特点

从性能角度来看,append() 方法的时间复杂度为 $O(1)$ 平均情况下。这意味着无论列表当前有多长,添加一个元素的时间消耗基本是固定的。然而,在实际情况中,当列表需要扩容时(即原有的内存空间不足以容纳新元素),会涉及到内存的重新分配和数据的复制,此时时间复杂度可能会达到 $O(n)$,不过这种情况相对较少,并且Python的列表实现已经对这种情况进行了优化,会预先分配一定的额外空间,以减少频繁扩容带来的性能损耗。

2. extend() 方法

2.1 extend() 方法概述

extend() 方法用于将一个可迭代对象(如列表、元组、集合、字符串等)中的所有元素添加到另一个列表的末尾。与 append() 方法不同的是,append() 是将整个对象作为一个元素添加到列表,而 extend() 是将可迭代对象中的每个元素逐个添加到列表。从本质上讲,extend() 方法同样修改了原列表对象,它遍历传入的可迭代对象,将每个元素追加到原列表的内存空间末尾,列表长度的增加量等于可迭代对象中元素的个数。

2.2 代码示例

# 创建一个列表
original_list = [1, 2, 3]

# 使用extend()方法添加另一个列表
new_list = [4, 5, 6]
original_list.extend(new_list)
print(original_list)

# 添加元组
my_tuple = (7, 8, 9)
original_list.extend(my_tuple)
print(original_list)

# 添加集合
my_set = {10, 11, 12}
original_list.extend(my_set)
print(original_list)

# 添加字符串
my_string = 'abc'
original_list.extend(my_string)
print(original_list)

在这段代码中,首先定义了一个 original_list。然后分别使用 extend() 方法将列表 new_list、元组 my_tuple、集合 my_set 和字符串 my_string 中的元素添加到 original_list 中。可以看到,每个可迭代对象中的元素都被逐个添加到了列表末尾。

2.3 extend() 方法的性能特点

extend() 方法的时间复杂度取决于传入的可迭代对象的长度。如果传入的可迭代对象长度为 $n$,则 extend() 方法的时间复杂度为 $O(n)$。这是因为它需要遍历可迭代对象中的每个元素并添加到列表中。虽然每次添加单个元素的平均时间复杂度为 $O(1)$,但总的时间消耗与元素个数成正比。与多次使用 append() 方法添加可迭代对象中的元素相比,extend() 方法在性能上更优,因为它避免了多次触发列表扩容的操作。

3. insert() 方法

3.1 insert() 方法概述

insert() 方法允许在列表的指定位置插入一个元素。它接受两个参数,第一个参数是插入位置的索引,第二个参数是要插入的元素。通过指定索引,我们可以精确控制元素插入到列表中的位置。从本质上看,当使用 insert() 方法时,列表会从指定索引位置开始,将后续元素依次向后移动一个位置,为新元素腾出空间,然后将新元素插入到该位置,这会导致列表对象在内存中的布局发生改变,并且列表长度增加1。

3.2 代码示例

# 创建一个列表
nums = [1, 2, 4, 5]

# 在索引1处插入3
nums.insert(1, 3)
print(nums)

# 在列表开头插入0
nums.insert(0, 0)
print(nums)

# 在列表末尾插入6
nums.insert(len(nums), 6)
print(nums)

在上述代码中,首先定义了列表 nums。然后使用 insert() 方法在索引 1 处插入 3,接着在列表开头(索引 0)插入 0,最后在列表末尾(通过 len(nums) 获取末尾索引)插入 6。每次插入后打印列表,可观察到元素按照指定位置成功插入。

3.3 insert() 方法的性能特点

insert() 方法的时间复杂度为 $O(n)$,其中 $n$ 是列表的长度。这是因为在插入元素时,需要移动插入位置之后的所有元素。插入位置越靠前,需要移动的元素就越多,性能消耗也就越大。因此,在列表末尾插入元素(即 insert(len(lst), element))虽然也可以实现,但相比 append() 方法,由于需要移动元素,性能会更差,所以在末尾添加元素时应优先使用 append() 方法。

4. 列表拼接(+ 运算符)

4.1 列表拼接概述

通过 + 运算符可以将两个列表拼接成一个新的列表。它的本质是创建一个新的列表对象,该对象包含了两个原始列表中的所有元素,原有的两个列表对象并不会被修改。这种方式适用于需要快速创建一个包含多个列表元素的新列表的场景。

4.2 代码示例

# 创建两个列表
list1 = [1, 2, 3]
list2 = [4, 5, 6]

# 使用 + 运算符拼接列表
new_list = list1 + list2
print(new_list)

# 拼接多个列表
list3 = [7, 8, 9]
combined_list = list1 + list2 + list3
print(combined_list)

在这段代码中,首先定义了 list1list2 两个列表,通过 + 运算符将它们拼接成 new_list。然后又定义了 list3,并将 list1list2list3 拼接成 combined_list。可以看到,每次拼接都返回了一个新的列表。

4.3 列表拼接的性能特点

列表拼接(+ 运算符)的时间复杂度为 $O(m + n)$,其中 $m$ 和 $n$ 分别是两个要拼接的列表的长度。这是因为需要遍历两个列表的所有元素来创建新列表。与 extend() 方法相比,列表拼接会创建一个新的列表对象,而 extend() 方法是在原列表上进行修改,所以如果只是想在原列表基础上添加元素,extend() 方法的性能更好,因为它避免了创建新列表带来的额外开销。

5. 列表乘法(* 运算符)

5.1 列表乘法概述

使用 * 运算符可以将列表中的元素重复指定次数,生成一个新的列表。它的本质是通过重复原列表中的元素,创建一个新的列表对象,原列表本身不会被修改。这在需要快速创建一个包含重复元素的列表时非常有用。

5.2 代码示例

# 创建一个列表
single_list = [1, 2]

# 使用 * 运算符重复列表元素
repeated_list = single_list * 3
print(repeated_list)

# 重复字符串列表
string_list = ['a', 'b']
new_string_list = string_list * 2
print(new_string_list)

在上述代码中,首先定义了 single_list,通过 * 运算符将其元素重复 3 次生成 repeated_list。接着定义了 string_list,并将其元素重复 2 次生成 new_string_list。可以看到,新列表中包含了重复的元素。

5.3 列表乘法的性能特点

列表乘法(* 运算符)的时间复杂度与重复次数和原列表长度有关。如果原列表长度为 $n$,重复次数为 $k$,则时间复杂度为 $O(n * k)$。这是因为需要将原列表中的每个元素重复 $k$ 次来创建新列表。需要注意的是,当原列表中包含可变对象(如列表)时,重复后的新列表中的可变对象是同一个对象的引用,而不是独立的副本,这可能会导致一些意外的结果。

# 包含列表的列表
original = [[1]]
new = original * 3
print(new)
new[0].append(2)
print(new)

在这段代码中,original 是一个包含列表 [1] 的列表,通过 * 运算符重复 3 次得到 new。当修改 new[0] 中的列表时,会发现 new 中所有对应的列表都被修改了,因为它们是同一个列表对象的引用。

6. 列表推导式添加元素

6.1 列表推导式概述

列表推导式是一种简洁的创建列表的方式,也可以用于在创建列表的同时添加元素。它的本质是基于一个可迭代对象,通过指定的表达式和条件筛选,生成一个新的列表。列表推导式的语法结构为 [expression for item in iterable if condition],其中 expression 是对 item 进行操作后生成新列表元素的表达式,item 是从 iterable 中迭代取出的元素,if condition 是可选的筛选条件,只有满足条件的 item 才会经过 expression 处理后添加到新列表中。

6.2 代码示例

# 生成包含1到10的平方的列表
squares = [i ** 2 for i in range(1, 11)]
print(squares)

# 从现有列表中筛选出偶数并乘以2
nums = [1, 2, 3, 4, 5, 6]
new_nums = [num * 2 for num in nums if num % 2 == 0]
print(new_nums)

在第一段代码中,使用列表推导式遍历 range(1, 11),对每个数字求平方并生成新列表 squares。在第二段代码中,从 nums 列表中筛选出偶数,然后将其乘以 2 生成 new_nums 列表。

6.3 列表推导式的性能特点

列表推导式在性能上通常比使用 for 循环逐个添加元素要快,因为列表推导式是在底层由C语言实现的,执行效率更高。其时间复杂度取决于可迭代对象的长度和表达式、条件判断的复杂度。如果可迭代对象长度为 $n$,且表达式和条件判断的时间复杂度为 $O(1)$,则列表推导式的时间复杂度为 $O(n)$。不过,如果表达式或条件判断本身具有较高的时间复杂度,那么整体的时间复杂度也会相应增加。

7. 使用 itertools.chain() 添加元素

7.1 itertools.chain() 概述

itertools.chain() 是Python标准库 itertools 模块中的一个函数,它可以将多个可迭代对象连接起来,返回一个迭代器。通过将这个迭代器转换为列表,我们可以实现类似于 extend() 方法的功能,将多个可迭代对象的元素添加到一个列表中。其本质是通过迭代器协议,逐个遍历多个可迭代对象的元素,而不会在内存中一次性创建一个包含所有元素的大对象,直到需要时才生成和处理元素,这在处理大型可迭代对象时可以节省内存。

7.2 代码示例

import itertools

# 创建多个可迭代对象
list1 = [1, 2]
tuple1 = (3, 4)
set1 = {5, 6}

# 使用itertools.chain()连接可迭代对象并转换为列表
result = list(itertools.chain(list1, tuple1, set1))
print(result)

在上述代码中,首先导入了 itertools 模块。然后定义了 list1tuple1set1 三个可迭代对象。通过 itertools.chain() 函数将它们连接起来,并使用 list() 函数将返回的迭代器转换为列表 result

7.3 itertools.chain() 的性能特点

itertools.chain() 的时间复杂度取决于连接的可迭代对象中元素的总数。如果所有可迭代对象中的元素总数为 $n$,则其时间复杂度为 $O(n)$。由于它是通过迭代器逐个生成元素,而不是一次性创建所有元素的列表,所以在内存使用上更加高效,特别是在处理大量数据时。不过,与直接使用 extend() 方法相比,将 itertools.chain() 的结果转换为列表会带来一定的额外开销,所以在性能敏感且内存充足的情况下,extend() 方法可能更合适。

8. 从文件中读取数据添加到列表

8.1 从文件读取数据添加到列表概述

在实际应用中,经常需要从文件中读取数据并添加到列表中。Python提供了多种文件读取方式,如 open() 函数结合 readlines()read() 等方法。从本质上讲,文件读取操作会从磁盘中读取数据到内存中,然后通过相应的处理将数据添加到列表。不同的文件读取方式和数据格式处理会影响到数据添加的具体过程。

8.2 代码示例

# 假设文件data.txt内容为:
# 1
# 2
# 3
# 4

# 使用readlines()读取文件内容并添加到列表
try:
    with open('data.txt', 'r') as file:
        lines = file.readlines()
        num_list = [int(line.strip()) for line in lines]
    print(num_list)
except FileNotFoundError:
    print('文件未找到')


# 读取CSV文件内容并添加到列表
import csv

try:
    with open('data.csv', 'r') as csvfile:
        reader = csv.reader(csvfile)
        csv_list = list(reader)
    print(csv_list)
except FileNotFoundError:
    print('CSV文件未找到')

在第一段代码中,使用 open() 函数以只读模式打开 data.txt 文件,通过 readlines() 方法读取文件的每一行,然后使用列表推导式将每一行去除两端空白字符并转换为整数后添加到 num_list 中。在第二段代码中,使用 csv 模块的 reader() 函数读取 data.csv 文件内容,并将其转换为列表 csv_list

8.3 从文件读取数据添加到列表的性能特点

从文件读取数据添加到列表的性能受多种因素影响。文件的大小、读取方式以及数据处理的复杂度都会对性能产生作用。对于较小的文件,使用 readlines() 等简单方法通常可以快速完成读取和添加操作。然而,对于非常大的文件,一次性读取整个文件可能会导致内存不足的问题,此时可以考虑逐行读取(如使用迭代器的方式读取文件),这样虽然会增加一些处理的复杂度,但可以有效控制内存使用。从时间复杂度来看,如果文件中有 $n$ 行数据,且每行数据处理时间复杂度为 $O(1)$,则读取和添加操作的时间复杂度为 $O(n)$。对于CSV等结构化文件,csv 模块的处理方式在解析数据时也会带来一定的时间开销,具体取决于数据的复杂程度。

9. 使用 collections.deque 高效添加元素

9.1 collections.deque 概述

collections.deque(双端队列)是Python标准库 collections 模块中的一个数据结构,它提供了在两端高效添加和删除元素的功能。与普通列表相比,deque 在两端添加和删除元素的时间复杂度为 $O(1)$,而列表在开头插入元素的时间复杂度为 $O(n)$。deque 的本质是基于双向链表实现的,这使得它可以在两端快速地进行操作,非常适合需要频繁在两端添加或删除元素的场景。

9.2 代码示例

from collections import deque

# 创建一个deque
my_deque = deque([1, 2, 3])

# 在右端添加元素
my_deque.append(4)
print(list(my_deque))

# 在左端添加元素
my_deque.appendleft(0)
print(list(my_deque))

在上述代码中,首先从 collections 模块导入 deque。然后创建了一个包含 [1, 2, 3]deque 对象 my_deque。通过 append() 方法在右端添加元素 4,通过 appendleft() 方法在左端添加元素 0,每次操作后将 deque 对象转换为列表并打印。

9.3 collections.deque 的性能特点

deque 在两端添加和删除元素的平均时间复杂度为 $O(1)$,这使得它在需要频繁在两端操作的场景下性能远远优于列表。然而,deque 在随机访问元素时的性能较差,因为它不像列表那样支持直接通过索引快速访问元素,而是需要从一端开始遍历,时间复杂度为 $O(n)$。此外,deque 的内存开销相对列表会稍大一些,因为它需要额外的指针来维护双向链表结构。所以在选择使用 deque 还是列表时,需要根据具体的操作场景来决定,如果主要是在两端进行添加和删除操作,deque 是更好的选择;如果需要频繁随机访问元素,则列表更为合适。

10. 利用 numpy 数组添加元素后转换为列表

10.1 利用 numpy 数组添加元素概述

numpy 是Python中常用的数学计算库,numpy 数组提供了高效的数值计算功能。在某些情况下,可以先将数据添加到 numpy 数组中,然后再转换为Python列表。numpy 数组在内存布局上更加紧凑,对于数值计算和批量数据操作具有更高的效率。通过使用 numpy 数组的相关方法添加元素,再转换为列表,可以在一定程度上提高数据处理的速度,特别是对于大量数值数据的添加操作。

10.2 代码示例

import numpy as np

# 创建一个numpy数组
arr = np.array([1, 2, 3])

# 使用np.append()添加元素
new_arr = np.append(arr, [4, 5])
print(new_arr.tolist())

# 使用np.concatenate()添加多个数组
arr1 = np.array([1, 2])
arr2 = np.array([3, 4])
combined_arr = np.concatenate((arr1, arr2))
print(combined_arr.tolist())

在上述代码中,首先导入 numpy 库并创建了一个 numpy 数组 arr。然后使用 np.append() 方法向 arr 中添加了 [4, 5],并将结果转换为列表打印。接着又使用 np.concatenate() 方法将 arr1arr2 两个 numpy 数组合并,同样转换为列表打印。

10.3 利用 numpy 数组添加元素的性能特点

numpy 数组在添加元素时,np.append() 方法的时间复杂度较高,因为每次调用 np.append() 都会创建一个新的数组,其时间复杂度为 $O(n)$,其中 $n$ 是数组的总长度。相比之下,np.concatenate() 方法在合并多个数组时性能更好,其时间复杂度为 $O(m + n)$,其中 $m$ 和 $n$ 是要合并的数组的长度。当处理大量数值数据时,虽然 numpy 数组在添加元素后转换为列表的过程会带来一些额外开销,但由于 numpy 数组在内存管理和数值计算上的优势,整体性能可能会优于直接使用Python列表进行添加操作,特别是在需要进行复杂数值计算和批量数据处理的场景中。不过,如果数据量较小或者不需要进行数值计算,直接使用Python列表的添加方式可能更为简单和高效。