1. 列表基础:Python中的瑞士军刀

列表(List)是Python中最基础也最强大的数据结构之一,就像编程世界里的瑞士军刀,几乎在任何场景下都能派上用场。我刚开始学Python时,导师就告诉我:"掌握了列表,就掌握了Python的半壁江山"。这句话在后来十年的编程生涯中不断得到验证。

列表的本质是一个有序的可变集合,用方括号 [] 表示,元素之间用逗号分隔。比如一个简单的购物清单可以这样表示:

shopping_list = ['牛奶', '鸡蛋', '面包', '苹果']

列表的强大之处在于它的灵活性:

  • 可以包含任意类型的对象(甚至混合类型)
  • 长度可以动态变化
  • 支持丰富的内置操作方法

在内存中,列表实际上存储的是对象的引用(指针),而不是对象本身。这意味着一个列表可以包含不同类型的对象,因为所有引用的大小都是相同的。这种设计使得列表操作非常高效。

注意:虽然列表可以存储不同类型的数据,但在实际开发中,我们通常会让一个列表只包含同类型数据,这样更符合业务逻辑且不易出错。

2. 列表操作:从基础到高阶

2.1 创建与访问

创建列表有多种方式,最直接的就是字面量表示法:

# 空列表
empty_list = []

# 数字列表
numbers = [1, 2, 3, 4, 5]

# 混合类型列表
mixed = [1, 'hello', 3.14, True]

访问列表元素使用索引(从0开始),Python还支持负索引(从-1开始表示最后一个元素):

fruits = ['apple', 'banana', 'cherry']
print(fruits[0])   # 'apple'
print(fruits[-1])  # 'cherry'

切片操作是Python列表的一大特色,可以方便地获取子列表:

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[2:5])   # [2, 3, 4]
print(numbers[:3])    # [0, 1, 2]
print(numbers[7:])    # [7, 8, 9]
print(numbers[::2])   # [0, 2, 4, 6, 8] (步长为2)

2.2 修改列表

列表是可变的(mutable),我们可以直接修改其中的元素:

fruits = ['apple', 'banana', 'cherry']
fruits[1] = 'blueberry'
print(fruits)  # ['apple', 'blueberry', 'cherry']

常用的修改方法:

  • append(x) :在末尾添加元素
  • insert(i, x) :在指定位置插入元素
  • extend(iterable) :扩展列表
  • remove(x) :删除第一个值为x的元素
  • pop([i]) :删除并返回指定位置的元素(默认最后一个)
  • clear() :清空列表
  • del 语句:删除列表或列表中的元素
numbers = [1, 2, 3]
numbers.append(4)       # [1, 2, 3, 4]
numbers.insert(1, 1.5)  # [1, 1.5, 2, 3, 4]
numbers.extend([5,6])   # [1, 1.5, 2, 3, 4, 5, 6]
numbers.remove(1.5)     # [1, 2, 3, 4, 5, 6]
last = numbers.pop()    # last=6, numbers=[1, 2, 3, 4, 5]
del numbers[0]          # [2, 3, 4, 5]
numbers.clear()         # []

2.3 列表排序与搜索

列表排序是常见操作,Python提供了两种主要方式:

numbers = [3, 1, 4, 1, 5, 9, 2]

# 方法1:sorted()函数(返回新列表)
sorted_numbers = sorted(numbers)  # [1, 1, 2, 3, 4, 5, 9]

# 方法2:list.sort()方法(原地修改)
numbers.sort()  # numbers变为[1, 1, 2, 3, 4, 5, 9]

两种方法的区别:

  • sorted() 返回新列表,原列表不变
  • sort() 直接修改原列表,返回None

对于自定义排序,可以使用 key 参数:

words = ['banana', 'pie', 'apple', 'Washington']
words.sort(key=len)  # 按长度排序:['pie', 'apple', 'banana', 'Washington']

搜索列表元素:

  • index(x) :返回第一个值为x的元素的索引
  • count(x) :返回x在列表中出现的次数
  • in 操作符:检查元素是否存在
numbers = [1, 2, 3, 2, 4]
print(numbers.index(2))    # 1
print(numbers.count(2))    # 2
print(3 in numbers)        # True

3. 列表推导式:优雅的构造方式

列表推导式(List Comprehension)是Python中非常优雅且高效的一种构造列表的方式。我第一次看到这种语法时,就被它的简洁性震撼了。

基本语法:

[expression for item in iterable if condition]

举例说明:

# 生成平方数列表
squares = [x**2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# 过滤偶数
evens = [x for x in range(20) if x % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

# 嵌套循环
pairs = [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
# [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

列表推导式不仅代码简洁,执行效率也比普通的for循环高。这是因为推导式在底层实现上做了优化。

提示:虽然列表推导式很强大,但也不要过度使用。当逻辑过于复杂时,使用普通的for循环可能更易读。

4. 列表的高级应用与性能考量

4.1 列表复制:浅拷贝与深拷贝

列表复制是一个容易踩坑的地方。直接赋值实际上只是创建了一个新的引用:

a = [1, 2, 3]
b = a
b[0] = 100
print(a)  # [100, 2, 3]  a也被修改了!

正确的复制方式:

# 浅拷贝(只复制一层)
a = [1, 2, 3]
b = a.copy()  # 或 b = a[:]
b[0] = 100
print(a)  # [1, 2, 3]  a不受影响

# 嵌套列表的情况
a = [1, [2, 3], 4]
b = a.copy()
b[1][0] = 100
print(a)  # [1, [100, 3], 4]  内层列表还是被共享了

# 深拷贝(完全独立)
import copy
a = [1, [2, 3], 4]
b = copy.deepcopy(a)
b[1][0] = 100
print(a)  # [1, [2, 3], 4]  完全不受影响

4.2 列表与迭代器

Python中有几种常见的与列表相关的迭代器:

  1. map() :对每个元素应用函数
numbers = [1, 2, 3]
squares = map(lambda x: x**2, numbers)
print(list(squares))  # [1, 4, 9]
  1. filter() :过滤元素
numbers = [1, 2, 3, 4, 5]
evens = filter(lambda x: x % 2 == 0, numbers)
print(list(evens))  # [2, 4]
  1. zip() :合并多个列表
names = ['Alice', 'Bob', 'Charlie']
scores = [85, 90, 78]
for name, score in zip(names, scores):
    print(f"{name}: {score}")

4.3 性能优化建议

在处理大规模数据时,列表性能很重要:

  1. 预分配空间 :当你知道列表最终大小时,可以预先分配空间
# 不好的做法:反复append
result = []
for i in range(10000):
    result.append(i)

# 更好的做法:预分配
result = [0] * 10000
for i in range(10000):
    result[i] = i
  1. 选择合适的数据结构
  • 如果需要频繁在头部插入/删除,考虑 collections.deque
  • 如果需要频繁成员检查,考虑 set
  • 如果数据固定不变,考虑 tuple
  1. 避免不必要的复制 :尽量使用切片或视图而不是创建新列表

  2. 利用生成器表达式 :对于只需要迭代一次的大数据集

# 列表推导式(立即计算)
sum([x**2 for x in range(1000000)])

# 生成器表达式(惰性计算)
sum(x**2 for x in range(1000000))  # 更节省内存

5. 实战案例:用列表解决实际问题

5.1 数据分析:计算移动平均

def moving_average(data, window_size):
    return [
        sum(data[i:i+window_size]) / window_size
        for i in range(len(data) - window_size + 1)
    ]

prices = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
print(moving_average(prices, 3))
# [11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0]

5.2 游戏开发:井字棋棋盘

# 初始化3x3棋盘
board = [[' ' for _ in range(3)] for _ in range(3)]

# 下棋
board[1][1] = 'X'  # 中心位置下X

# 打印棋盘
for row in board:
    print('|' + '|'.join(row) + '|')

5.3 文本处理:词频统计

def word_frequency(text):
    words = text.lower().split()
    freq = {}
    for word in words:
        freq[word] = freq.get(word, 0) + 1
    return sorted(freq.items(), key=lambda x: x[1], reverse=True)

text = "Python is great Python is simple Python is powerful"
print(word_frequency(text))
# [('python', 3), ('is', 3), ('great', 1), ('simple', 1), ('powerful', 1)]

5.4 算法实现:快速排序

def quicksort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[len(arr) // 2]
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return quicksort(left) + middle + quicksort(right)

print(quicksort([3,6,8,10,1,2,1]))
# [1, 1, 2, 3, 6, 8, 10]

6. 常见问题与调试技巧

6.1 IndexError:列表索引越界

这是最常见的列表相关错误:

fruits = ['apple', 'banana', 'cherry']
print(fruits[3])  # IndexError: list index out of range

解决方法:

  • 检查列表长度: len(fruits)
  • 使用安全访问模式:
index = 3
if index < len(fruits):
    print(fruits[index])
else:
    print("Index out of range")

6.2 修改列表的同时迭代

这是一个典型的陷阱:

numbers = [1, 2, 3, 4]
for num in numbers:
    if num % 2 == 0:
        numbers.remove(num)
print(numbers)  # [1, 3] 看起来没问题?

# 但这样呢?
numbers = [1, 2, 4, 3]
for num in numbers:
    if num % 2 == 0:
        numbers.remove(num)
print(numbers)  # [1, 4, 3]  4没有被删除!

正确做法是创建新列表或使用列表推导式:

numbers = [1, 2, 4, 3]
numbers = [num for num in numbers if num % 2 != 0]
print(numbers)  # [1, 3]

6.3 列表与None的混淆

有些列表方法返回None而不是新列表:

numbers = [3, 1, 2]
result = numbers.sort()
print(result)  # None
print(numbers)  # [1, 2, 3]

6.4 多维列表的初始化

初始化多维列表时容易犯的错误:

# 错误的初始化方式
matrix = [[0] * 3] * 3
matrix[0][0] = 1
print(matrix)  # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]  所有行都变了!

# 正确的初始化方式
matrix = [[0 for _ in range(3)] for _ in range(3)]
matrix[0][0] = 1
print(matrix)  # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]

7. 列表与其他数据结构的比较

7.1 列表 vs 元组

特性 列表(list) 元组(tuple)
可变性 可变 不可变
语法 [] ()
性能 稍慢 稍快
适用场景 需要修改的数据 固定数据

7.2 列表 vs 集合

特性 列表(list) 集合(set)
顺序 有序 无序
唯一性 允许重复 唯一
查找速度 O(n) O(1)
语法 [] {}

7.3 列表 vs 字典

特性 列表(list) 字典(dict)
索引 整数 任意键
顺序 有序 Python 3.7+有序
查找速度 O(n) O(1)
语法 [] {}

在实际项目中,我经常需要根据具体需求选择合适的数据结构。比如:

  • 需要维护顺序且可能修改 → 列表
  • 需要快速查找且元素唯一 → 集合
  • 需要键值对映射 → 字典
  • 数据固定不变 → 元组

8. Python列表的内部实现

了解列表的内部实现有助于写出更高效的代码。Python列表实际上是动态数组,其关键特性包括:

  1. 动态扩容 :当列表空间不足时,Python会分配更大的内存块(通常是当前大小的约1.125倍),然后将原有元素复制过去。

  2. 过度分配 :为了避免每次append都重新分配内存,Python会预先分配比实际需要更多的空间。

  3. 引用存储 :列表只存储对象的引用,而不是对象本身,这使得列表可以包含不同类型的对象。

这些实现细节解释了为什么:

  • append() 操作平均时间复杂度是O(1)
  • 列表的随机访问非常快(O(1))
  • 列表会占用比实际需要更多的内存

可以通过 sys.getsizeof() 查看列表实际占用的内存大小:

import sys
lst = []
for i in range(10):
    lst.append(i)
    print(f"长度: {len(lst)}, 大小: {sys.getsizeof(lst)}字节")

输出结果会显示,列表内存不是线性增长的,而是在某些点突然增加(扩容事件)。

9. 现代Python中的列表新特性

随着Python版本更新,列表相关功能也在不断增强:

9.1 海象运算符(Python 3.8+)

# 传统方式
numbers = [1, 2, 3, 4, 5]
half = len(numbers) // 2
first_half = numbers[:half]

# 使用海象运算符
numbers = [1, 2, 3, 4, 5]
first_half = numbers[:(half := len(numbers) // 2)]

9.2 类型注解(Python 3.9+)

from typing import List, Union

# 传统类型注解
def process_items(items: List[Union[int, str]]) -> List[str]:
    return [str(item) for item in items]

# Python 3.9+ 更简洁的写法
def process_items(items: list[int | str]) -> list[str]:
    return [str(item) for item in items]

9.3 模式匹配(Python 3.10+)

def handle_command(command):
    match command.split():
        case ["load", filename]:
            print(f"加载文件: {filename}")
        case ["save", filename]:
            print(f"保存文件: {filename}")
        case ["exit" | "quit"]:
            print("退出程序")
        case _:
            print("未知命令")

commands = ["load data.txt", "save output.txt", "exit"]
for cmd in commands:
    handle_command(cmd)

10. 性能测试与优化实战

让我们通过几个实际测试来看看如何优化列表操作:

10.1 拼接字符串

# 不好的做法:使用+=
result = ""
for s in ["a", "b", "c", "d"]:
    result += s  # 每次操作都创建新字符串

# 更好的做法:使用join
result = "".join(["a", "b", "c", "d"])

性能对比:

import timeit

def concat_plus():
    result = ""
    for s in ["a"] * 10000:
        result += s
    return result

def concat_join():
    return "".join(["a"] * 10000)

print(timeit.timeit(concat_plus, number=100))   # 约0.3秒
print(timeit.timeit(concat_join, number=100))   # 约0.02秒

10.2 过滤列表

# 方法1:列表推导式
result = [x for x in range(10000) if x % 2 == 0]

# 方法2:filter+lambda
result = list(filter(lambda x: x % 2 == 0, range(10000)))

# 方法3:预分配+循环
result = [0] * 5000
index = 0
for x in range(10000):
    if x % 2 == 0:
        result[index] = x
        index += 1

性能测试结果(时间越短越好):

列表推导式: 0.0012秒
filter+lambda: 0.0018秒
预分配+循环: 0.0010秒

10.3 列表与生成器

对于大数据集,生成器可以节省内存:

# 列表推导式(占用内存)
sum([x**2 for x in range(1000000)])

# 生成器表达式(节省内存)
sum(x**2 for x in range(1000000))

内存占用对比:

import sys
print(sys.getsizeof([x**2 for x in range(1000000)]))  # 约8448728字节
print(sys.getsizeof(x**2 for x in range(1000000)))    # 约128字节

在实际项目中,我通常会根据数据规模和处理需求选择合适的方案。对于中等规模数据(几千到几万条),列表推导式通常是最佳选择;对于超大规模数据,则考虑生成器或分块处理。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐