容器类型(一):列表与元组
如果你是从Java转向Python的开发者,看到list和tuple,第一反应很可能是:"这不就是ArrayList和不可变数组吗?" 没错,这个直觉非常准。但Python的列表和元组比Java的对应物更灵活、语法更简洁,甚至能让你写出"一行顶十行"的代码。
本文将带你深入理解Python中的这对"双面兄弟"------可变与不可变、动态与静态、丰富操作与严格约束,并通过与Java的对比,让你快速掌握它们的用法和适用场景。
一、list:Python 的"超级 ArrayList"
Java中的ArrayList是使用最广泛的动态数组,而Python的list就是它的"增强版"------不仅支持所有ArrayList的操作,还提供了切片、列表推导等杀手级特性。
1.1 创建与基本操作
python
# 创建列表
fruits = ["apple", "banana", "cherry"]
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True] # Python列表可混合类型
# 增
fruits.append("orange") # 末尾追加
fruits.insert(1, "grape") # 指定位置插入
fruits.extend(["melon", "kiwi"]) # 合并另一个列表
# 删
fruits.remove("banana") # 删除指定值(只删第一个)
popped = fruits.pop() # 弹出末尾元素
del fruits[0] # 删除索引位置元素
fruits.clear() # 清空列表
# 改
fruits[0] = "blueberry" # 直接赋值修改
# 查
print(fruits[2]) # 索引访问
print(fruits[-1]) # 倒数第一个
print(fruits.index("cherry")) # 查找索引
print("apple" in fruits) # 成员判断
与Java对比:
| 操作 | Java ArrayList | Python list |
|---|---|---|
| 追加 | add(e) |
append(e) |
| 插入 | add(i, e) |
insert(i, e) |
| 删除值 | remove(o) |
remove(o) |
| 删除索引 | remove(i) |
pop(i) 或 del lst[i] |
| 长度 | size() |
len(lst) |
| 判空 | isEmpty() |
not lst 或 len(lst)==0 |
Python胜在语法统一 :len()适用于所有容器,in操作符简洁直观。
1.2 切片:list 的"手术刀"
切片是Python最强大的特性之一,一次操作就能完成子数组的提取、复制、甚至原地替换。Java中需要循环或subList,而Python一行搞定。
python
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 获取子列表 [2, 3, 4]
sub = nums[2:5] # 起始索引:结束索引(不包含结束)
# 从开头到索引5(不含)
head = nums[:5] # [0,1,2,3,4]
# 从索引5到末尾
tail = nums[5:] # [5,6,7,8,9]
# 步长(每2个取1个)
every_second = nums[::2] # [0,2,4,6,8]
# 反转列表
reversed_nums = nums[::-1] # [9,8,7,...,0]
# 复制列表(浅拷贝)
copy = nums[:] # 等价于 nums.copy()
高级玩法:切片赋值
python
nums = [0, 1, 2, 3, 4]
nums[1:3] = [10, 20, 30] # 替换索引1~2为三个元素
print(nums) # [0, 10, 20, 30, 3, 4]
nums[2:4] = [] # 删除索引2~3的元素
print(nums) # [0, 10, 4]
这种原地修改的能力是Java ArrayList难以企及的。
1.3 列表推导式:声明式编程的典范
列表推导式(list comprehension)是Python独有的语法糖,它将循环和条件判断压缩进一个表达式,生成新列表。对于Java开发者来说,它相当于Stream API的简化版。
基础语法 :[表达式 for 变量 in 可迭代对象 if 条件]
python
# 生成平方数列表
squares = [x**2 for x in range(10)]
# Java: IntStream.range(0,10).map(x -> x*x).boxed().collect(Collectors.toList())
# 带条件的过滤
even_squares = [x**2 for x in range(10) if x % 2 == 0]
# 嵌套循环(生成坐标对)
coords = [(x, y) for x in range(3) for y in range(3)]
# [(0,0),(0,1),(0,2),(1,0),...]
# 条件表达式(三元)
results = [x if x > 0 else 0 for x in [-1, 2, -3, 4]]
与Java的Stream对比:
| 功能 | Java Stream | Python列表推导 |
|---|---|---|
| map | .map(f) |
[f(x) for x in list] |
| filter | .filter(p) |
[x for x in list if p(x)] |
| flatMap | .flatMap(f) |
[y for x in list for y in f(x)] |
列表推导式更紧凑、更易读(对Python程序员而言)。而且由于是立即执行,不需要终端操作。
1.4 其他常用方法
python
nums = [3, 1, 4, 1, 5, 9, 2]
nums.sort() # 原地排序
sorted_nums = sorted(nums) # 返回新列表,原列表不变
nums.reverse() # 原地反转
print(sum(nums)) # 求和
print(max(nums), min(nums))
print(nums.count(1)) # 统计出现次数
二、tuple:不可变的"定海神针"
元组(tuple)与列表几乎一模一样,除了一个关键区别:不可变。一旦创建,不能增、删、改。这让它天然具备哈希能力,可以作为字典的键,也是函数多返回值的不二之选。
2.1 创建与访问
python
# 创建元组
point = (10, 20)
single = (5,) # 只有一个元素时必须有逗号
empty = ()
# 访问(与列表相同)
print(point[0]) # 10
print(point[-1]) # 20
# 不可变性演示
point[0] = 30 # TypeError: 'tuple' object does not support item assignment
2.2 元组的不可变 ≠ 内容不可变
注意:如果元组内包含可变对象(如列表),该对象本身可以改变。元组只是"引用"不可变。
python
t = (1, 2, [3, 4])
t[2].append(5) # 合法!列表内容改变了
print(t) # (1, 2, [3, 4, 5])
# t[2] = [6,7] # 错误!不能更换引用
这一点和Java中final修饰的引用类型变量类似。
2.3 元组的常用操作
由于不可变,元组的方法很少:count(计数)、index(查找索引)。但许多列表的操作(如len、in、切片)同样适用于元组。
python
t = (1, 2, 3, 2, 2)
print(t.count(2)) # 3
print(t.index(3)) # 2
print(t[1:3]) # (2, 3) 切片返回新元组
2.4 元组的解包(Unpacking)------ Python的优雅之处
元组最实用的功能之一是解包,尤其适合函数返回多个值。
python
# 简单解包
a, b = (10, 20)
# 交换变量(无需临时变量)
x, y = y, x
# 函数返回多个值
def get_user():
return "Alice", 25
name, age = get_user()
print(name, age)
# 带星号收集剩余元素
first, *rest = (1, 2, 3, 4) # first=1, rest=[2,3,4]
Java中返回多值通常需要自定义类或Pair/Tuple库,而Python原生支持。
2.5 元组作为字典的键
由于元组是不可变的,它可以作为字典的键,而列表不行。
python
locations = {}
locations[(35.68, 139.76)] = "Tokyo" # 经纬度元组作为键
# locations[[1,2]] = "error" # 报错:unhashable type: 'list'
三、list vs tuple:如何选择?
| 场景 | 推荐 | 原因 |
|---|---|---|
| 元素数量会变化 | list |
可变,增删改方便 |
| 需要作为字典的键 | tuple |
不可变,可哈希 |
| 函数返回多个值 | tuple |
轻量、解包方便 |
| 存储同质数据(如所有整数) | list |
通常需要修改或扩展 |
| 配置常量(如颜色RGB值) | tuple |
语义上不应改变 |
| 性能要求高且数据固定 | tuple |
创建速度略快,内存占用更小 |
性能小贴士:在相同数据量下,元组的创建速度比列表快约10~30%,内存占用也更少。但如果数据频繁修改,列表更合适。
四、与Java的思维转换总结
| 概念 | Java | Python |
|---|---|---|
| 动态数组 | ArrayList<E> |
list(混合类型) |
| 不可变列表 | List.of(...) 返回不可变List |
tuple |
| 子数组/切片 | list.subList(from, to) |
lst[start:end] |
| 复制列表 | new ArrayList<>(list) |
lst[:] 或 lst.copy() |
| 列表推导 | Stream API + collector | [expr for x in list] |
| 多返回值 | 自定义类或Object[] |
(a, b) 元组+解包 |
| 交换变量 | 临时变量 | a, b = b, a |
Python的哲学:简洁优于复杂,直接优于间接。当你熟练使用切片和列表推导后,会发现很多原本需要循环的代码都可以被一行取代。
五、动手练习:巩固你的理解
尝试用Python实现以下任务,并思考如果用Java会怎么写:
- 给定一个列表
nums = [1,2,3,4,5,6],取出所有奇数,并计算它们的平方,生成新列表。 - 交换列表中的第一个和最后一个元素。
- 将两个列表
keys = ["name", "age"]和values = ["Alice", 25]合并成一个字典。 - 编写一个函数,接受任意多个参数,返回它们的和与平均值(用元组返回)。
参考解答:
python
# 1
nums = [1,2,3,4,5,6]
result = [x**2 for x in nums if x % 2 == 1] # [1, 9, 25]
# 2
nums[0], nums[-1] = nums[-1], nums[0]
# 3
d = dict(zip(keys, values)) # {'name': 'Alice', 'age': 25}
# 4
def stats(*args):
total = sum(args)
avg = total / len(args)
return total, avg
Python的list和tuple虽然简单,却是日常编程中使用频率最高的数据结构。理解它们的特性,尤其是切片和推导式,能让你写出更Pythonic的代码。下一篇文章我们将继续探讨Python的其他容器类型:dict和set,敬请期待!
当你开始习惯用一行列表推导代替四五行循环,用元组解包优雅地交换变量,你会感受到Python设计的精妙------让程序员专注于业务逻辑,而非繁琐的语法。