Python基础:列表与元组详解

Python列表与元组全面解析:相似而不同的序列类型

在Python的数据类型家族中,列表(list)和元组(tuple)是两种最基础、最常用的序列类型。虽然它们看起来相似,但在特性、用途和性能上却有着重要的区别。本文将带你深入探索这两种数据类型的方方面面,从基础用法到高级技巧,帮助你在实际编程中更加得心应手地使用它们。

基本概念与特性对比

列表和元组都是序列类型,可以存储任意类型的Python对象,但它们有一个根本区别:

python 复制代码
# 列表是可变的(mutable)
my_list = [1, 2, 3]
my_list[0] = 100  # 完全合法

# 元组是不可变的(immutable)
my_tuple = (1, 2, 3)
# my_tuple[0] = 100  # TypeError: 'tuple' object does not support item assignment

下面是两者的主要特性对比:

特性 列表(List) 元组(Tuple)
可变性 可变 不可变
语法 方括号 [1, 2, 3] 圆括号 (1, 2, 3)
大小 通常消耗更多内存 内存占用较小
速度 操作相对较慢 操作相对较快
主要用途 存储可能需要修改的数据集合 存储不可变数据,作为字典键,函数参数/返回值

创建与初始化

列表的创建方式

python 复制代码
# 空列表
empty_list1 = []
empty_list2 = list()

# 包含元素的列表
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", True, 3.14]

# 列表推导式(强大而简洁)
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]

# 使用range转换
range_list = list(range(5))  # [0, 1, 2, 3, 4]

# 从其他序列转换
tuple_to_list = list((1, 2, 3))  # [1, 2, 3]
string_to_list = list("Python")  # ['P', 'y', 't', 'h', 'o', 'n']

元组的创建方式

python 复制代码
# 空元组
empty_tuple1 = ()
empty_tuple2 = tuple()

# 包含元素的元组
numbers = (1, 2, 3, 4, 5)
mixed = (1, "hello", True, 3.14)

# 单元素元组(注意逗号不可少!)
singleton = (42,)  # 注意逗号!没有逗号(42)只是一个带括号的表达式
another_singleton = 42,  # 不带括号也可以,但要有逗号

# 元组推导式(实际上是生成器表达式)
# Python没有真正的元组推导式,但可以将生成器表达式转换为元组
tuple_from_gen = tuple(x**2 for x in range(5))  # (0, 1, 4, 9, 16)

# 从其他序列转换
list_to_tuple = tuple([1, 2, 3])  # (1, 2, 3)
string_to_tuple = tuple("Python")  # ('P', 'y', 't', 'h', 'o', 'n')

💡 提示:对于元组,括号在大多数情况下是可选的,主要的标识符是逗号。但为了代码清晰度,通常建议使用括号。

访问元素

列表和元组的元素访问方式基本相同:

索引访问

python 复制代码
fruits = ["apple", "banana", "cherry", "date", "elderberry"]
fruit_tuple = ("apple", "banana", "cherry", "date", "elderberry")

# 正向索引(从0开始)
print(fruits[0])       # apple
print(fruit_tuple[0])  # apple

# 负向索引(从-1开始)
print(fruits[-1])      # elderberry
print(fruit_tuple[-1]) # elderberry

切片操作

切片语法:sequence[start:stop:step]

python 复制代码
# 基本切片
print(fruits[1:3])         # ['banana', 'cherry']
print(fruit_tuple[1:3])    # ('banana', 'cherry')

# 省略起始索引(默认为0)
print(fruits[:3])          # ['apple', 'banana', 'cherry']

# 省略结束索引(默认为序列长度)
print(fruits[2:])          # ['cherry', 'date', 'elderberry']

# 使用步长
print(fruits[::2])         # ['apple', 'cherry', 'elderberry']

# 负步长(反向)
print(fruits[::-1])        # ['elderberry', 'date', 'cherry', 'banana', 'apple']

# 复杂切片
print(fruits[3:0:-1])      # ['date', 'cherry', 'banana']

解包(Unpacking)

这是Python的强大特性,允许将序列中的元素分配给多个变量:

python 复制代码
# 基本解包
a, b, c = [1, 2, 3]
x, y, z = (4, 5, 6)
print(a, b, c)  # 1 2 3
print(x, y, z)  # 4 5 6

# 使用*运算符收集剩余元素
first, *rest = [1, 2, 3, 4, 5]
print(first)  # 1
print(rest)   # [2, 3, 4, 5]

# 提取开头和结尾,中间元素收集到一个变量
head, *middle, tail = [1, 2, 3, 4, 5]
print(head)    # 1
print(middle)  # [2, 3, 4]
print(tail)    # 5

⚠️ 注意:解包时变量数量必须与序列长度匹配,除非使用*运算符。

修改操作

列表的修改操作

作为可变序列,列表提供了丰富的修改操作:

python 复制代码
# 修改单个元素
fruits = ["apple", "banana", "cherry"]
fruits[0] = "apricot"
print(fruits)  # ['apricot', 'banana', 'cherry']

# 通过切片修改多个元素
numbers = [1, 2, 3, 4, 5]
numbers[1:4] = [20, 30, 40]
print(numbers)  # [1, 20, 30, 40, 5]

# 插入元素
numbers.insert(2, 25)
print(numbers)  # [1, 20, 25, 30, 40, 5]

# 添加元素到末尾
numbers.append(6)
print(numbers)  # [1, 20, 25, 30, 40, 5, 6]

# 扩展列表
numbers.extend([7, 8, 9])
print(numbers)  # [1, 20, 25, 30, 40, 5, 6, 7, 8, 9]

# 删除元素
del numbers[0]
print(numbers)  # [20, 25, 30, 40, 5, 6, 7, 8, 9]

# 通过值删除
numbers.remove(30)
print(numbers)  # [20, 25, 40, 5, 6, 7, 8, 9]

# 弹出元素(默认最后一个)
last = numbers.pop()
print(last)     # 9
print(numbers)  # [20, 25, 40, 5, 6, 7, 8]

# 弹出指定位置的元素
third = numbers.pop(2)
print(third)    # 40
print(numbers)  # [20, 25, 5, 6, 7, 8]

# 清空列表
numbers.clear()
print(numbers)  # []

元组的"修改"

由于元组是不可变的,我们不能直接修改其元素。但可以通过创建新元组实现"修改"效果:

python 复制代码
# 不能这样做
coords = (10, 20, 30)
# coords[0] = 100  # TypeError!

# 需要创建新元组
coords = (100,) + coords[1:]
print(coords)  # (100, 20, 30)

# 或者转换为列表,修改后再转回元组
coords_list = list(coords)
coords_list[1] = 200
coords = tuple(coords_list)
print(coords)  # (100, 200, 30)

💡 重要提示:虽然元组本身不可变,但如果元组包含可变对象(如列表),这些对象的内容是可以修改的:

python 复制代码
weird_tuple = (1, 2, [3, 4])
# weird_tuple[0] = 10  # 错误!
weird_tuple[2][0] = 30  # 完全合法!
print(weird_tuple)  # (1, 2, [30, 4])

这种行为有时会导致混淆,因为元组的"不可变性"只针对元组结构本身,而非其中包含的对象。

列表与元组的常用方法

列表方法

列表提供了丰富的内置方法:

python 复制代码
numbers = [3, 1, 4, 1, 5, 9, 2]

# 排序(原地修改)
numbers.sort()
print(numbers)  # [1, 1, 2, 3, 4, 5, 9]

# 反转(原地修改)
numbers.reverse()
print(numbers)  # [9, 5, 4, 3, 2, 1, 1]

# 计数
print(numbers.count(1))  # 2

# 查找索引(首次出现)
print(numbers.index(5))  # 1

# 复制列表
numbers_copy = numbers.copy()  # 等同于numbers[:]

元组方法

相比之下,元组只有两个方法:

python 复制代码
coords = (10, 20, 30, 20, 40)

# 计数
print(coords.count(20))  # 2

# 查找索引(首次出现)
print(coords.index(30))  # 2

通用序列操作

这些操作适用于列表和元组:

python 复制代码
seq1 = [1, 2, 3]
seq2 = (4, 5, 6)

# 长度
print(len(seq1))  # 3
print(len(seq2))  # 3

# 最大值/最小值
print(max(seq1))  # 3
print(min(seq2))  # 4

# 包含检查
print(2 in seq1)  # True
print(10 in seq2)  # False

# 拼接
print(seq1 + [4, 5])  # [1, 2, 3, 4, 5]
print(seq2 + (7, 8))  # (4, 5, 6, 7, 8)

# 重复
print(seq1 * 3)  # [1, 2, 3, 1, 2, 3, 1, 2, 3]
print(seq2 * 2)  # (4, 5, 6, 4, 5, 6)

列表与元组的性能比较

内存使用

元组通常比同等内容的列表占用更少的内存:

python 复制代码
import sys

list_ex = [1, 2, 3, 4, 5]
tuple_ex = (1, 2, 3, 4, 5)

print(f"列表内存占用: {sys.getsizeof(list_ex)} 字节")
print(f"元组内存占用: {sys.getsizeof(tuple_ex)} 字节")
# 在Python 3.9上,列表占用104字节,元组占用80字节

创建时间

元组创建通常比列表更快:

python 复制代码
import timeit

# 创建100万次空列表和空元组的时间比较
list_time = timeit.timeit("[]", number=1000000)
tuple_time = timeit.timeit("()", number=1000000)

print(f"创建100万个列表耗时: {list_time:.6f}秒")
print(f"创建100万个元组耗时: {tuple_time:.6f}秒")

💡 性能提示:当处理大量数据且不需要修改时,优先使用元组可以提高性能并减少内存占用。

高级操作技巧

列表推导式与生成器表达式

python 复制代码
# 列表推导式 - 创建新列表
squares = [x**2 for x in range(10)]
print(squares)  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# 带条件的列表推导式
odd_squares = [x**2 for x in range(10) if x % 2 == 1]
print(odd_squares)  # [1, 9, 25, 49, 81]

# 嵌套列表推导式
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [num for row in matrix for num in row]
print(flattened)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# 生成器表达式 - 生成元组
tuple_gen = tuple(x**2 for x in range(5))
print(tuple_gen)  # (0, 1, 4, 9, 16)

嵌套列表与元组

python 复制代码
# 创建矩阵
matrix_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
matrix_tuple = ((1, 2, 3), (4, 5, 6), (7, 8, 9))

# 访问元素
print(matrix_list[1][2])  # 6
print(matrix_tuple[1][2]) # 6

# 列表可以修改内部元素
matrix_list[0][1] = 20
print(matrix_list)  # [[1, 20, 3], [4, 5, 6], [7, 8, 9]]

# 元组不能修改自身元素,但如果元素是列表,列表内容可以修改
nested = ([1, 2], [3, 4])
nested[0][1] = 20
print(nested)  # ([1, 20], [3, 4])

排序技巧

python 复制代码
# 使用sorted()函数(不修改原序列,返回新列表)
numbers = [3, 1, 4, 1, 5, 9, 2]
num_tuple = (3, 1, 4, 1, 5, 9, 2)

sorted_numbers = sorted(numbers)
sorted_tuple = sorted(num_tuple)  # 返回列表,不是元组
print(sorted_numbers)  # [1, 1, 2, 3, 4, 5, 9]
print(sorted_tuple)    # [1, 1, 2, 3, 4, 5, 9]

# 反向排序
print(sorted(numbers, reverse=True))  # [9, 5, 4, 3, 2, 1, 1]

# 自定义排序
students = [
    ("Alice", 25, "A"),
    ("Bob", 20, "B"),
    ("Charlie", 22, "A")
]

# 按年龄排序
by_age = sorted(students, key=lambda x: x[1])
print(by_age)  # [('Bob', 20, 'B'), ('Charlie', 22, 'A'), ('Alice', 25, 'A')]

# 先按成绩排序,再按年龄排序(多重排序)
by_grade_then_age = sorted(students, key=lambda x: (x[2], x[1]))
print(by_grade_then_age)  # [('Charlie', 22, 'A'), ('Alice', 25, 'A'), ('Bob', 20, 'B')]

使用场景与最佳实践

何时使用列表

  • 需要频繁修改集合内容时
  • 需要使用列表特有方法如append()extend()insert()
  • 处理可变大小的数据集合
  • 实现栈(用append()pop())或队列(用append()pop(0))
  • 进行原地排序或修改操作
python 复制代码
# 列表作为栈使用
stack = []
stack.append(1)  # 入栈
stack.append(2)
stack.append(3)
print(stack.pop())  # 出栈: 3
print(stack)  # [1, 2]

何时使用元组

  • 存储不应被修改的数据
  • 作为字典的键(列表不能用作字典键因为它是可变的)
  • 函数参数和返回值
  • 需要确保数据不被意外修改
  • 优化性能和内存使用
python 复制代码
# 元组作为字典键
locations = {
    (40.7128, -74.0060): "New York",
    (34.0522, -118.2437): "Los Angeles",
    (41.8781, -87.6298): "Chicago"
}
print(locations[(40.7128, -74.0060)])  # New York

# 函数返回多个值(实际上是返回元组)
def get_dimensions():
    return 1920, 1080  # 返回元组(1920, 1080)

width, height = get_dimensions()  # 解包
print(f"Width: {width}, Height: {height}")

命名元组

当需要有一定结构的元组时,可以使用命名元组:

python 复制代码
from collections import namedtuple

# 定义命名元组类型
Point = namedtuple('Point', ['x', 'y', 'z'])

# 创建实例
p1 = Point(1, 2, 3)
p2 = Point(x=4, y=5, z=6)

# 通过名称访问字段
print(p1.x, p1.y, p1.z)  # 1 2 3

# 通过索引访问(仍然是元组!)
print(p1[0], p1[1], p1[2])  # 1 2 3

# 解包
x, y, z = p2
print(x, y, z)  # 4 5 6

# 不可变性
# p1.x = 10  # AttributeError: can't set attribute

命名元组提供了更好的可读性和自我文档化的代码,同时保持了元组的不可变性。

常见陷阱与解决方案

列表的浅拷贝vs深拷贝

python 复制代码
import copy

# 原始列表(包含嵌套列表)
original = [1, 2, [3, 4]]

# 直接赋值(引用相同对象)
reference = original
reference[0] = 10
print(original)  # [10, 2, [3, 4]] - 原列表也被修改

# 浅拷贝(只复制第一层)
shallow = original.copy()  # 或 shallow = original[:] 或 shallow = list(original)
shallow[0] = 100  # 不影响original
shallow[2][0] = 30  # 影响original!
print(original)  # [10, 2, [30, 4]]

# 深拷贝(递归复制所有内容)
deep = copy.deepcopy(original)
deep[0] = 1000  # 不影响original
deep[2][0] = 300  # 也不影响original
print(original)  # [10, 2, [30, 4]]
print(deep)  # [1000, 2, [300, 4]]

列表乘法的陷阱

python 复制代码
# 看似创建了一个包含5个空列表的列表
wrong = [[]] * 5
wrong[0].append(10)
print(wrong)  # [[10], [10], [10], [10], [10]]!所有子列表都被修改了

# 正确方式:使用列表推导式
correct = [[] for _ in range(5)]
correct[0].append(10)
print(correct)  # [[10], [], [], [], []]

可变参数与元组拆包

python 复制代码
# *args收集位置参数为元组
def print_args(*args):
    print(f"类型: {type(args)}")
    print(f"内容: {args}")

print_args(1, 2, 3)  # 类型: <class 'tuple'>, 内容: (1, 2, 3)

# 将列表/元组拆包为参数
values = [10, 20, 30]
print_args(*values)  # 内容: (10, 20, 30)

元组的单元素陷阱

python 复制代码
# 这不是元组,只是带括号的表达式
not_tuple = (42)
print(type(not_tuple))  # <class 'int'>

# 正确的单元素元组需要逗号
single_tuple = (42,)
print(type(single_tuple))  # <class 'tuple'>

# 括号可以省略,逗号才是关键
also_tuple = 42,
print(type(also_tuple))  # <class 'tuple'>

总结

列表和元组是Python中最基础的序列类型,它们有许多共同点,但也有关键的区别:

  1. 列表可变的,适用于需要频繁修改的数据集合。它提供了丰富的方法来添加、删除和操作元素。

  2. 元组不可变的,适用于固定数据的表示,特别是当你需要确保数据不被修改或作为字典键时。

  3. 性能方面,元组通常比列表更快且占用内存更少,这使它们非常适合大量数据的处理。

  4. 使用列表的场景:需要经常添加/删除元素,需要排序或其他原地修改操作,实现栈或队列等数据结构。

  5. 使用元组的场景:函数返回值,字典键,确保数据不被意外修改,提高性能。

  6. 命名元组为元组添加了字段名,提高了代码可读性,非常适合表示记录或结构。

通过深入理解列表和元组的特性及其差异,你可以在编写Python程序时做出更明智的选择,创建出更高效、更清晰的代码。记住,选择正确的数据结构往往是解决问题的第一步!

无论你是Python新手还是经验丰富的开发者,掌握列表和元组的精髓都将帮助你成为更优秀的Python程序员。

相关推荐
不会写代码的码农农2 分钟前
【2025年26期免费获取股票数据API接口】实例演示五种主流语言获取股票行情api接口之沪深A股涨停股池数据获取实例演示及接口API说明文档
java·开发语言·python·股票api·股票数据接口·股票数据
Seven9714 分钟前
【设计模式】遍历集合的艺术:深入探索迭代器模式的无限可能
java·后端·设计模式
BIT_Legend17 分钟前
Torch 模型 model => .onnx => .trt 及利用 TensorTR 在 C++ 下的模型部署教程
c++·人工智能·python·深度学习
小杨40419 分钟前
springboot框架项目应用实践五(websocket实践)
spring boot·后端·websocket
浪九天20 分钟前
Java直通车系列28【Spring Boot】(数据访问Spring Data JPA)
java·开发语言·spring boot·后端·spring
蹦蹦跳跳真可爱58935 分钟前
Python----计算机视觉处理(Opencv:自适应二值化,取均值,加权求和(高斯定理))
人工智能·python·opencv·计算机视觉
dreadp1 小时前
使用 OpenSSL 和 Python 实现 AES-256-CBC 加密与解密(安全密钥管理)
python·安全·网络安全·密码学·openssl
bobz9651 小时前
IKEv1 和 IKEv2 发展历史和演进背景
后端
IT北辰1 小时前
《用 python、MySQL 和 Chart.js 打造炫酷数据看板》实战案例笔记
python
大鹏dapeng1 小时前
Gone v2 goner/gin——试试用依赖注入的方式打开gin-gonic/gin
后端·go