你好,我是 hockor,今天我们继续 Python 的学习,主要是讲讲 Python 中的列表和元组。 作为对比,我们会同步看看 JS 的数组和 Python 的列表在一些语法层面以及 API 使用上的区别。
列表 vs 数组
我们先来看看最基础的增删改查和基础遍历
JS demo
jsx
// 创建和初始化
const numbers = [1, 2, 3, 4, 5];
const fruits = new Array('apple', 'banana', 'cherry');
// 添加元素
numbers.push(6); // 末尾添加: [1,2,3,4,5,6]
numbers.unshift(0); // 开头添加: [0,1,2,3,4,5,6]
numbers.splice(3, 0, 2.5); // 中间插入: [0,1,2,2.5,3,4,5,6]
// 删除元素
let last = numbers.pop(); // 删除末尾: 6
let first = numbers.shift(); // 删除开头: 0
numbers.splice(2, 1); // 删除索引2: [1,2,3,4,5]
// 查找和检查
let index = numbers.indexOf(3); // 查找索引: 2
let exists = numbers.includes(4); // 检查存在: true
// 遍历和转换
numbers.forEach(n => console.log(n));
let doubled = numbers.map(n => n * 2);
let evens = numbers.filter(n => n % 2 === 0);
let sum = numbers.reduce((acc, n) => acc + n, 0);
Python demo
python
# 创建和初始化
numbers = [1, 2, 3, 4, 5]
fruits = list(['apple', 'banana', 'cherry'])
# 添加元素
numbers.append(6) # 末尾添加: [1,2,3,4,5,6]
numbers.insert(0, 0) # 开头添加: [0,1,2,3,4,5,6]
numbers.insert(3, 2.5) # 中间插入: [0,1,2,2.5,3,4,5,6]
# 删除元素
last = numbers.pop() # 删除末尾: 6
first = numbers.pop(0) # 删除开头: 0
numbers.pop(2) # 删除索引2: [1,2,3,4,5]
# 查找和检查
index = numbers.index(3) # 查找索引: 2
exists = 4 in numbers # 检查存在: True
# 遍历和转换
for n in numbers:
print(n)
doubled = list(map(lambda n: n * 2, numbers))
evens = list(filter(lambda n: n % 2 == 0, numbers))
sum_val = sum(numbers)
从上述基础操作对比可以看出,虽然两种语言的语法相似,但在具体的方法命名和返回值处理上存在差异。JavaScript 更倾向于提供链式调用的能力,而 Python 则更注重代码的可读性和明确性。
从使用便利性来讲,我觉得 Python 的设计会更胜一筹。
高级特性对比
现代 JavaScript 引入的 ES6+ 特性为数组操作带来了更多便利,而 Python 的列表推导式和生成器表达式则提供了独特的优势,我们来看一些具体的例子
ES6 相关特性
js
// ES6+ 高级特性
const arr = [1, 2, 3, 4, 5];
// 解构赋值
const [first, second, ...rest] = arr;
const [a, , c] = arr; // 跳过元素
// 扩展运算符
const newArr = [...arr, 6, 7];
const combined = [...arr1, ...arr2];
// 数组复制
const shallow = [...arr];
const copy = Array.from(arr);
// 函数式编程
const result = arr
.filter(x => x > 2)
.map(x => x * 2)
.reduce((acc, x) => acc + x, 0);
// 现代方法
const found = arr.find(x => x > 3);
const foundIndex = arr.findIndex(x => x > 3);
const hasLarge = arr.some(x => x > 10);
const allPositive = arr.every(x => x > 0);
// 平整数组
const nested = [[1, 2], [3, 4]];
const flattened = nested.flat();
const mapped = nested.flatMap(x => x.map(y => y * 2));
Python 高级特性
python
# Python 高级特性
arr = [1, 2, 3, 4, 5]
# 解构赋值(解包)
first, second, *rest = arr
a, _, c, *_ = arr # 跳过元素
# 列表拼接
new_arr = [*arr, 6, 7]
combined = arr1 + arr2
# 列表复制
shallow = arr.copy()
copy = list(arr)
# 函数式编程链式调用(使用生成器)
from functools import reduce
result = reduce(
lambda acc, x: acc + x,
map(lambda x: x * 2,
filter(lambda x: x > 2, arr)), 0)
# 高级操作
found = next((x for x in arr if x > 3), None)
found_index = next((i for i, x in enumerate(arr) if x > 3), -1)
has_large = any(x > 10 for x in arr)
all_positive = all(x > 0 for x in arr)
# 列表推导式
squared = [x**2 for x in arr]
evens = [x for x in arr if x % 2 == 0]
nested_comp = [y for sublist in nested for y in sublist]
这些高级特性展现了两种语言不同的编程范式倾向。JavaScript 偏向函数式编程风格,提供了丰富的链式调用方法,而 Python 则通过列表推导式和生成器提供了更加简洁和内存友好的数据处理方式。
2. 元组
Python 的元组是不可变的序列,用小括号表示,JavaScript 中没有直接对应的数据类型
。
python
# Python 元组
coordinates = (10, 20)
# coordinates[0] = 30 # 这会报错,因为元组是不可变的
single_item_tuple = (1,) # 注意逗号
虽然不能修改元组的元素,但是元组变量是可以重新赋值的哈
用途
元组在Python中有以下几个主要用途:
- 作为函数的返回值:当函数需要返回多个值时,通常使用元组
python
def get_coordinates():
return (10, 20) # 返回一个元组
x, y = get_coordinates() # 元组解包
- 表示不应被修改的数据:比如坐标点、RGB颜色值等固定数据
python
point = (0, 0) # 二维坐标
color = (255, 128, 0) # RGB颜色值
- 作为字典的键:因为元组是不可变的,所以可以用作字典的键,而列表不行
python
locations = {(0, 0): 'origin', (1, 1): 'point A'}
print(locations[(0, 0)]) # 输出: 'origin'
- 数据的安全性:使用元组可以防止数据被意外修改
总的来说,当你需要一个不可变的序列时,应该使用元组而不是列表。这样可以保证数据的完整性,并且在某些情况下还能提高性能。
底层实现对比
JS
我们以 V8 为例,V8 将数组分为两种主要模式:
-
Fast Elements(连续内存模式):
-
用于同质化数据(如全为 SmI 小整数或全为 Double 浮点数)。
-
直接存储值(而非指针),类似 C 数组:
-
js
[1, 2, 3] → 内存连续存储:| 1 | 2 | 3 |
访问速度接近原生数组(O(1))。
-
Dictionary Elements(哈希表模式):
-
稀疏数组或混合类型时,退化为哈希表(键为索引的字符串),比如在[1,2,3].push('apple')。
-
访问速度较慢(O(1) 但常数因子大),但节省内存(仅存储有效元素)。
-
Python
指针数组结构
Python 列表存储的是 对象的引用(指针),而非对象本身。每个元素是一个指向 Python 对象(如 int、str、list 等)的指针(8字节,64位系统)。
内存布局示例:
c
// 类似 C 的结构(简化版)
typedef struct {
PyObject **ob_item; // 指针数组(存储元素的地址)
Py_ssize_t allocated; // 预分配的总容量
Py_ssize_t size; // 当前实际大小
} PyListObject;
列表 lst = [1, "a", [2]] 的内存布局:
c
ob_item → [0x1000(指向整数1), 0x2000(指向字符串"a"), 0x3000(指向子列表[2])]
allocated = 4 // 可能多分配的空间
size = 3
动态扩容机制
-
当列表满时
(size == allocated)
,会触发扩容:-
新容量公式:
new_allocated = (size >> 3) + (size < 9 ? 3 : 6) + size
(实际策略更复杂)。 -
重新分配更大的内存块,复制旧数据到新空间。
-
-
扩容因子约为 1.125x(比 C++ 的 std::vector 的 2x 更保守)。
异构数据支持
由于存储的是指针,列表天然支持混合类型:
python
[42, "hello", 3.14, [1, 2]] # 每个元素指向不同类型的对象
性能影响示例
1、遍历速度:
-
JavaScript 的 Fast Elements 数组遍历比 Python 列表更快(直接访问值,无需解引用)。
-
Python 的异构列表遍历需频繁检查类型,效率较低。
2、内存占用:
-
Python 的 [1, 2, 3] 需要存储 3 个 int 对象 + 3 个指针 + 列表元数据。
-
JavaScript 的 [1, 2, 3] 在 Fast Elements 模式下可能直接存储 3 个数值。
3、动态操作:
-
Python 的 list.append() 可能触发扩容和内存复制。
-
JavaScript 的 arr.push() 可能触发存储模式切换(Fast → Dictionary)。
ok,以上就是关于 Python 中列表和元组的相关内容,我们下一节字典/集合 再见~