前言
上一篇讲语法对比的时候,列表和数组的基本操作一笔带过了。
这篇我们深入聊聊 Python 列表——尤其是两个前端不常见但非常强大的特性:切片操作 和 列表推导式。
学完这两个,你会理解为什么很多人说"Python 的列表比 JS 数组好用"。
一、列表的增删改查(对比 JS 数组)
先把基本操作过一遍,熟悉的直接跳过。
# 创建列表
fruits = ["苹果", "香蕉", "橙子", "葡萄"]
# 访问元素(索引从 0 开始)
print(fruits[0]) # 苹果
print(fruits[-1]) # 葡萄(负数索引!从末尾倒数)
print(fruits[-2]) # 橙子
# 修改元素
fruits[1] = "芒果"
print(fruits) # ['苹果', '芒果', '橙子', '葡萄']
# 添加元素
fruits.append("草莓") # 末尾添加(类比 push)
fruits.insert(1, "西瓜") # 指定位置插入
# 删除元素
fruits.remove("芒果") # 按值删除
fruits.pop() # 删除末尾(类比 pop)
fruits.pop(0) # 删除指定索引
# 查询
print(len(fruits)) # 长度(注意:len() 是函数)
print("苹果" in fruits) # True/False,比 includes() 更自然
print(fruits.index("橙子")) # 返回索引
负数索引是 Python 特有的,JS 没有这个:
arr = [10, 20, 30, 40, 50]
print(arr[-1]) # 50,最后一个
print(arr[-2]) # 40,倒数第二个
二、切片操作:Python 最优雅的特性之一
切片(Slice)是 Python 列表的独有功能,JS 里的 slice() 方法只是它的简化版。
基本语法:list[start:end:step]
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 基本切片 [start:end](不包含 end)
print(nums[2:5]) # [2, 3, 4]
# 省略 start(从头开始)
print(nums[:4]) # [0, 1, 2, 3]
# 省略 end(到末尾)
print(nums[6:]) # [6, 7, 8, 9]
# 负数切片
print(nums[-3:]) # [7, 8, 9],最后三个
print(nums[:-2]) # [0, 1, 2, 3, 4, 5, 6, 7],去掉最后两个
# 带步长 [start:end:step]
print(nums[::2]) # [0, 2, 4, 6, 8],每隔一个取一个
print(nums[1::2]) # [1, 3, 5, 7, 9],奇数索引
print(nums[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],反转列表!
最后那个 [::-1] 是经典用法:一行代码反转列表。 JS 里要用 .reverse()(而且会修改原数组),Python 的切片不修改原列表,返回新列表。
对比 JS 的 slice():
// JS
const nums = [0, 1, 2, 3, 4, 5];
nums.slice(2, 5); // [2, 3, 4]
nums.slice(-3); // [3, 4, 5]
// JS 没有步长参数
# Python
nums = [0, 1, 2, 3, 4, 5]
nums[2:5] # [2, 3, 4]
nums[-3:] # [3, 4, 5]
nums[::2] # [0, 2, 4],JS 做不到
三、列表推导式:优雅到令人上瘾
列表推导式(List Comprehension)是 Python 最受欢迎的特性之一,用一行代码代替 map 和 filter。
基本格式:[表达式 for 变量 in 可迭代对象]
从 JS 的 map 迁移:
// JS map
const nums = [1, 2, 3, 4, 5];
const doubled = nums.map(n => n * 2);
// [2, 4, 6, 8, 10]
# Python 列表推导式
nums = [1, 2, 3, 4, 5]
doubled = [n * 2 for n in nums]
# [2, 4, 6, 8, 10]
从 JS 的 filter 迁移:
// JS filter
const evens = nums.filter(n => n % 2 === 0);
// [2, 4]
# Python 列表推导式(带条件)
evens = [n for n in nums if n % 2 == 0]
# [2, 4]
map + filter 组合:
// JS:先过滤偶数,再乘以 2
const result = nums.filter(n => n % 2 === 0).map(n => n * 2);
// [4, 8]
# Python:一行搞定
result = [n * 2 for n in nums if n % 2 == 0]
# [4, 8]
更多实用例子:
# 生成平方数列表
squares = [x**2 for x in range(1, 6)]
# [1, 4, 9, 16, 25]
# 字符串列表转大写
words = ["hello", "world", "python"]
upper = [w.upper() for w in words]
# ['HELLO', 'WORLD', 'PYTHON']
# 处理字典列表(JSON 数据很常见)
users = [
{"name": "Alice", "age": 25},
{"name": "Bob", "age": 17},
{"name": "Charlie", "age": 30}
]
# 取出所有成年用户的名字
adult_names = [u["name"] for u in users if u["age"] >= 18]
# ['Alice', 'Charlie']
最后那个例子在处理接口返回的 JSON 数据时非常实用!
四、浅拷贝 vs 深拷贝:和 JS 一样的坑
这个坑 JS 也有,Python 也有,面试也常考。
# 直接赋值:两个变量指向同一个列表
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4],a 也被修改了!
# 浅拷贝:复制一层
a = [1, 2, [3, 4]]
b = a.copy() # 或者 b = a[:]
b[0] = 99
print(a) # [1, 2, [3, 4]],第一层没被改
b[2].append(5) # 修改嵌套列表
print(a) # [1, 2, [3, 4, 5]],嵌套层被改了!
浅拷贝只复制第一层,嵌套的对象还是共享引用。
# 深拷贝:完全独立的副本
import copy
a = [1, 2, [3, 4]]
b = copy.deepcopy(a)
b[2].append(5)
print(a) # [1, 2, [3, 4]],完全不受影响
对比 JS 的做法:
// JS 浅拷贝
const a = [1, 2, [3, 4]];
const b = [...a]; // 或 a.slice()
// JS 深拷贝(常用方法)
const c = JSON.parse(JSON.stringify(a)); // 简单数据可用
const d = structuredClone(a); // 现代浏览器推荐
规律: 数据结构只有一层,用 .copy() 或 [:];有嵌套结构,用 copy.deepcopy()。
小结
- 负数索引 arr[-1] 是 Python 特有的,获取末尾元素很方便
- 切片 arr[start:end:step] 功能强大,[::-1] 一行反转列表
- 列表推导式 [x for x in arr if 条件] 是 map + filter 的优雅替代
- 拷贝陷阱:直接赋值是引用,一层用 .copy(),有嵌套用 copy.deepcopy()
下篇预告: 第 05 篇,Python 字典深度解析。字典就是 Python 的对象/JSON,前端最有亲切感——但里面也有很多 JS 没有的好用功能等你来发现。