Skip to content

02-元组

本章代码基于 Python 3.11+ 编写

元组是不可变的序列,适合存储不应被修改的数据,如配置信息、坐标点等。


本章全面讲解 Python 元组的创建、访问、不可变性和高级用法。


概念铺垫

为什么需要元组?一个不可变数据的需求场景

问题场景: 你在开发一个配置管理系统,需要存储固定的配置项(如数据库连接信息)。

问题:

  • 用列表存储配置,可能被意外修改
  • 如何确保某些数据不被改变?

使用元组的解决方案:

python
# 数据库配置(不可变)
db_config: tuple[str, str, int] = ("localhost", "mydb", 3306)

# 元组无法修改,保护配置安全
# db_config[0] = "newhost"  # TypeError: 不能修改

# 但可以创建新元组
new_config: tuple[str, str, int] = ("newhost", db_config[1], db_config[2])

这就是元组的价值:保护重要数据不被意外修改


元组解决了什么问题?

元组的本质是:存储不应改变的数据序列

就像你的身份证号码,一旦确定就不会改变。元组存储的数据也是"只读"的。

元组的优势:

  1. 数据安全:防止意外修改
  2. 性能更好:比列表更快、更省内存
  3. 可哈希:可以作为字典的键
  4. 语义明确:表明数据不应改变

L1 理解层:会用

元组的最简用法(3分钟上手)

元组的核心操作:创建、访问、解包。

python
# 创建元组
point: tuple[int, int] = (10, 20)

# 访问元素
print(point[0])  # 输出:10

# 解包
x, y = point
print(f"x={x}, y={y}")

这就是元组的基本用法。接下来我们详细学习元组的特性。


第一部分:元组基础

什么是元组

元组(Tuple) 与列表类似,但是不可变(创建后不能修改)。

┌─────────────────────────────────────────────────────────────┐
│                    元组的特点                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ✅ 有序:元素有固定的顺序                                 │
│   ✅ 不可变:创建后不能修改、添加、删除元素                  │
│   ✅ 可重复:允许有相同的元素                               │
│   ✅ 异构:可以存储不同类型的元素                           │
│   ✅ 可哈希:可作为字典的键(元素也必须可哈希)              │
│                                                             │
│   表示方法:使用圆括号 ()                                    │
│   例如:(1, 2, 3)                                           │
│                                                             │
│   底层实现:静态数组                                         │
│   • 比列表更节省内存                                        │
│   • 创建后大小固定                                          │
│   • 访问速度比列表略快                                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

列表 vs 元组

┌─────────────────────────────────────────────────────────────┐
│                  列表 vs 元组 对比                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   特性            列表 (list)        元组 (tuple)           │
│   ─────────────────────────────────────────────             │
│   符号            []                 ()                     │
│   可变性          可变               不可变                 │
│   方法数量        多(11 个)        少(2 个)             │
│   内存占用        较大               较小                   │
│   创建速度        较慢               较快                   │
│   作为字典键      ❌ 不可以          ✅ 可以                │
│   哈希支持        ❌ 不可哈希        ✅ 可哈希              │
│                                                             │
│   使用场景:                                                 │
│   ─────────────────────────────────────────────             │
│   列表:                                                     │
│   • 需要修改的数据集合                                       │
│   • 数据量可能变化                                           │
│   • 一般用途的序列                                           │
│                                                             │
│   元组:                                                     │
│   • 固定不变的数据(坐标、RGB 颜色)                         │
│   • 字典的键                                                 │
│   • 函数多返回值                                             │
│   • 数据库记录                                               │
│   • 配置信息                                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

创建元组

python
# 方式 1:使用圆括号
colors = ("red", "green", "blue")
numbers = (1, 2, 3, 4, 5)
mixed = (1, "hello", 3.14, True)

# 方式 2:不使用括号(元组打包)
point = 10, 20
print(type(point))  # <class 'tuple'>

# 方式 3:创建空元组
empty = ()
empty2 = tuple()

# 方式 4:创建单元素元组(必须有逗号!)
single = (1,)       # ✅ 正确:元组
print(type(single)) # <class 'tuple'>

not_tuple = (1)     # ❌ 错误:这是整数
print(type(not_tuple))  # <class 'int'>

# 方式 5:从其他可迭代对象创建
from_list = tuple([1, 2, 3])
from_string = tuple("hello")  # ('h', 'e', 'l', 'l', 'o')
from_range = tuple(range(3))  # (0, 1, 2)

# 方式 6:元组推导式?不,是生成器!
gen = (x ** 2 for x in range(5))  # 这是生成器表达式
tuple_gen = tuple(x ** 2 for x in range(5))  # (0, 1, 4, 9, 16)

单元素元组陷阱:

┌─────────────────────────────────────────────────────────────┐
│                  单元素元组陷阱                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ❌ 错误写法:                                              │
│   t = (1)      # 这是整数 1,不是元组                       │
│   t = ("hi")   # 这是字符串 "hi",不是元组                  │
│                                                             │
│   ✅ 正确写法:                                              │
│   t = (1,)     # 元组 (1,)                                  │
│   t = ("hi",)  # 元组 ('hi',)                               │
│   t = 1,       # 元组 (1,)(不推荐,可读性差)              │
│                                                             │
│   判断方法:                                                 │
│   type((1))    # <class 'int'>                              │
│   type((1,))   # <class 'tuple'>                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

访问元组

python
colors = ("red", "green", "blue", "yellow")

# 索引访问
print(colors[0])   # red(第一个)
print(colors[-1])  # yellow(最后一个)
print(colors[2])   # blue

# 切片
print(colors[0:2])   # ('red', 'green')
print(colors[::2])   # ('red', 'blue')
print(colors[::-1])  # ('yellow', 'blue', 'green', 'red')

# 统计
print(len(colors))            # 4
print(colors.count("red"))    # 1
print(colors.index("blue"))   # 2

# 成员检查
print("green" in colors)      # True
print("black" in colors)      # False

# 遍历
for color in colors:
    print(color)

for i, color in enumerate(colors):
    print(f"{i}: {color}")

第二部分:元组的不可变性

理解不可变性

python
colors = ("red", "green", "blue")

# ❌ 不能修改元素
# colors[0] = "yellow"  # TypeError: 'tuple' object does not support item assignment

# ❌ 不能添加元素
# colors.append("yellow")  # AttributeError

# ❌ 不能删除元素
# del colors[0]  # TypeError

# ✅ 可以重新赋值整个变量
colors = ("yellow", "green", "blue")  # 创建新元组,变量指向新元组

# ✅ 可以拼接创建新元组
new_colors = colors + ("purple",)
print(new_colors)  # ('yellow', 'green', 'blue', 'purple')

# ✅ 可以切片创建新元组
subset = colors[0:2]
print(subset)  # ('yellow', 'green')

不可变的"陷阱"

python
# 元组本身不可变,但元素可能是可变对象
person = ("Alice", 25, ["Python", "JavaScript"])

# ❌ 不能修改元组本身
# person[0] = "Bob"  # TypeError

# ✅ 但可以修改可变元素(如列表)
person[2].append("Go")
print(person)  # ('Alice', 25, ['Python', 'JavaScript', 'Go'])

# 这意味着:包含可变元素的元组不可哈希
# hash(person)  # TypeError: unhashable type: 'list'

# 真正不可变的元组:所有元素都不可变
safe_tuple = ("Alice", 25, ("Python", "JavaScript"))
hash(safe_tuple)  # ✅ 可以计算哈希值

图示:

┌─────────────────────────────────────────────────────────────┐
│                  元组不可变性图示                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   元组存储的是引用:                                         │
│                                                             │
│   person = ("Alice", 25, ["Python", "JS"])                  │
│                                                             │
│   person ──→ ┌─────────────────────────┐                    │
│              │ 引用1 ──→ "Alice"        │                    │
│              │ 引用2 ──→ 25             │                    │
│              │ 引用3 ──→ ["Python", "JS"] ← 可变对象        │
│              └─────────────────────────┘                    │
│                                                             │
│   • 元组本身不可变:不能改变引用1、2、3                      │
│   • 但引用3指向的列表可以修改                                │
│                                                             │
│   结论:                                                     │
│   • 元组不可变 = 元组存储的引用不可变                        │
│   • 引用指向的对象可能可变                                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

第三部分:元组解包

基本解包

python
# 基本解包
person = ("Alice", 25, "Beijing")
name, age, city = person

print(name)  # Alice
print(age)   # 25
print(city)  # Beijing

# 解包时变量数量必须匹配
# a, b = (1, 2, 3)  # ValueError: too many values to unpack

# 交换变量值(经典用法)
a, b = 10, 20
a, b = b, a
print(a, b)  # 20 10

# 不需要临时变量!
# 传统方式需要:temp = a; a = b; b = temp

扩展解包

python
# 使用 * 收集剩余元素
numbers = (1, 2, 3, 4, 5)

first, *rest = numbers
print(first)   # 1
print(rest)    # [2, 3, 4, 5](注意:是列表)

*beginning, last = numbers
print(beginning)  # [1, 2, 3, 4]
print(last)       # 5

first, *middle, last = numbers
print(first)   # 1
print(middle)  # [2, 3, 4]
print(last)    # 5

# 忽略某些值
a, _, c = (1, 2, 3)  # _ 表示忽略
print(a, c)  # 1 3

# 忽略多个值
a, *_, b = (1, 2, 3, 4, 5)
print(a, b)  # 1 5

嵌套解包

python
# 解包嵌套元组
person = ("Alice", (25, "Beijing"))
name, (age, city) = person

print(name)  # Alice
print(age)   # 25
print(city)  # Beijing

# 解包多层嵌套
data = (1, (2, (3, 4)))
a, (b, (c, d)) = data
print(a, b, c, d)  # 1 2 3 4

# 实际应用:遍历带索引的序列
for i, (x, y) in enumerate([(1, 2), (3, 4), (5, 6)]):
    print(f"索引 {i}: x={x}, y={y}")

第四部分:命名元组

基本用法

概念说明

namedtuplecollections 模块提供的工厂函数,创建具有命名字段的元组子类。

┌─────────────────────────────────────────────────────────────┐
│                    命名元组                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   优点:                                                     │
│   • 可以通过名称访问字段(更易读)                           │
│   • 仍然可以通过索引访问                                    │
│   • 不可变,内存高效                                        │
│   • 可以作为字典键                                          │
│                                                             │
│   适用场景:                                                 │
│   • 替代简单的类(只有数据,没有方法)                      │
│   • 数据库记录                                              │
│   • 坐标点、颜色等                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

代码示例:

python
from collections import namedtuple

# 创建命名元组类
Point = namedtuple('Point', ['x', 'y'])
Color = namedtuple('Color', 'red green blue')
Person = namedtuple('Person', 'name, age, city')

# 创建实例
p = Point(3, 4)
c = Color(255, 128, 0)
person = Person("Alice", 25, "Beijing")

# 通过名称访问
print(p.x, p.y)        # 3 4
print(c.red)           # 255
print(person.name)     # Alice

# 通过索引访问
print(p[0], p[1])      # 3 4

# 解包
x, y = p
print(x, y)            # 3 4

# 不可变
# p.x = 10  # AttributeError

# 获取字段名
print(Point._fields)   # ('x', 'y')

# 转换为字典
print(p._asdict())     # {'x': 3, 'y': 4}

# 替换字段(返回新实例)
p2 = p._replace(x=10)
print(p2)              # Point(x=10, y=4)
print(p)               # Point(x=3, y=4)(原实例不变)

命名元组 vs 普通类

python
from collections import namedtuple

# 方式 1:命名元组
Point = namedtuple('Point', ['x', 'y'])
p1 = Point(3, 4)
print(p1.x)  # 3

# 方式 2:普通类
class PointClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p2 = PointClass(3, 4)
print(p2.x)  # 3

# 方式 3:数据类(Python 3.7+)
from dataclasses import dataclass

@dataclass
class PointData:
    x: int
    y: int

p3 = PointData(3, 4)
print(p3.x)  # 3

对比:

特性命名元组普通类数据类
不可变可选
内存占用最小较大中等
可添加方法
默认值
类型提示
可哈希需实现可选

第五部分:元组作为字典键

使用场景

python
# 元组可以作为字典的键(因为不可变)
locations = {
    (0, 0): "原点",
    (1, 0): "东",
    (0, 1): "北",
    (-1, 0): "西",
    (0, -1): "南"
}

print(locations[(0, 0)])  # 原点
print(locations[(1, 0)])  # 东

# 网格游戏中的应用
grid = {}
grid[(2, 3)] = "宝藏"
grid[(5, 1)] = "怪物"

# 检查位置
pos = (2, 3)
if pos in grid:
    print(f"这里有一个{grid[pos]}")

# 列表不能作为键
# d = {[1, 2]: "value"}  # TypeError: unhashable type: 'list'

# 包含可变元素的元组也不能作为键
# t = (1, [2, 3])
# d = {t: "value"}  # TypeError

第六部分:函数与元组

多返回值

python
# 函数返回多个值(实际返回元组)
def get_stats(numbers):
    return min(numbers), max(numbers), sum(numbers), len(numbers)

data = [3, 1, 4, 1, 5, 9, 2, 6]

# 方式 1:接收元组
stats = get_stats(data)
print(stats)  # (1, 9, 31, 8)

# 方式 2:解包接收
minimum, maximum, total, count = get_stats(data)
print(f"最小值:{minimum}, 最大值:{maximum}")

# 方式 3:部分解包
minimum, maximum, *_ = get_stats(data)
print(f"范围:{minimum} - {maximum}")

可变参数

python
# *args 接收任意数量的位置参数(打包为元组)
def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3))      # 6
print(sum_all(1, 2, 3, 4, 5))  # 15

# 解包元组作为参数
numbers = (1, 2, 3, 4, 5)
print(sum_all(*numbers))  # 15(解包后传入)

# 实际应用
def print_info(name, age, city):
    print(f"{name}, {age}岁, 来自{city}")

person = ("Alice", 25, "Beijing")
print_info(*person)  # 解包传入

第七部分:元组性能

内存和速度优势

python
import sys

# 内存对比
lst = [1, 2, 3, 4, 5]
tpl = (1, 2, 3, 4, 5)

print(sys.getsizeof(lst))  # 约 104 字节
print(sys.getsizeof(tpl))  # 约 80 字节(更小)

# 创建速度对比
import timeit

print(timeit.timeit('[1, 2, 3, 4, 5]', number=1000000))
# 约 0.05 秒

print(timeit.timeit('(1, 2, 3, 4, 5)', number=1000000))
# 约 0.01 秒(更快)

# 原因:
# • 元组是静态结构,创建时一次性分配内存
# • 列表是动态结构,需要预留扩展空间

L2 实践层:用好

最佳实践

元组 vs 列表选择指南

场景推荐原因
固定不变的数据元组语义明确,防止意外修改
坐标、颜色、尺寸元组自然语义,不可变
函数多返回值元组解包方便,不可变
字典键元组可哈希
配置信息元组防止修改,可作为键
需要修改的数据列表可变性
数据量可能变化列表动态增删
一般用途序列列表灵活性
数据库记录(只读)命名元组字段访问 + 不可变

反模式:不要这样做

python
# ❌ 反模式 1:单元素元组忘记逗号
single = (1)      # 这是整数,不是元组!
type(single)      # <class 'int'>

# ✅ 正确做法
single = (1,)     # 元组
type(single)      # <class 'tuple'>
python
# ❌ 反模式 2:存储可变元素后用作字典键
t = (1, [2, 3])   # 包含列表
d = {t: "value"}  # TypeError: unhashable type: 'list'

# ✅ 正确做法:确保所有元素不可变
t = (1, (2, 3))   # 嵌套元组
d = {t: "value"}  # OK
python
# ❌ 反模式 3:频繁拼接创建新元组
result = ()
for i in range(1000):
    result = result + (i,)  # O(n²),每次创建新元组

# ✅ 正确做法:先收集再转换
items = []
for i in range(1000):
    items.append(i)
result = tuple(items)  # O(n)
python
# ❌ 反模式 4:用元组存储需要修改的数据
scores = (85, 90, 78)  # 无法更新成绩
# scores[0] = 92  # TypeError

# ✅ 正确做法:用列表
scores = [85, 90, 78]  # 可修改
scores[0] = 92
python
# ❌ 反模式 5:过度使用命名元组(需要方法时)
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
# p.distance()  # 无法添加方法

# ✅ 正确做法:用 dataclass 或普通类
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int
    
    def distance(self) -> float:
        return (self.x ** 2 + self.y ** 2) ** 0.5

适用场景

场景是否推荐元组原因
坐标点 (x, y)✅ 推荐自然不可变语义
RGB 颜色✅ 推荐固定三值
函数返回多值✅ 推荐解包方便
字典复合键✅ 推荐可哈希
配置项✅ 推荐防止意外修改
数据库记录命名元组字段访问
需增删元素❌ 用列表元组不可变
需修改元素❌ 用列表元组不可变

命名元组最佳实践

python
from collections import namedtuple

# ✅ 正确:字段名使用合法标识符
Point = namedtuple('Point', ['x', 'y'])

# ✅ 正确:命名元组替代简单字典(更省内存)
# ❌ 反模式:用 dict 存固定结构的数据
config = {"host": "localhost", "port": 8080}
# ✅ 推荐:用命名元组
Config = namedtuple('Config', ['host', 'port'])
config = Config(host="localhost", port=8080)

# ❌ 反模式:超出 5-6 个字段仍用 namedtuple
# 字段过多 → 可读性下降 → 换 dataclass

解包反模式

python
# ❌ 反模式:解包时丢弃大量值但不使用 _
data = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
first, rest1, rest2, rest3, rest4, rest5, rest6, rest7, rest8, last = data

# ✅ 正确:使用 * 收集
first, *_, last = data  # _ 约定表示"忽略"

# ❌ 反模式:变量数量与元组长度不匹配
name, age = ("Alice",)  # ValueError
# ✅ 正确:不确定长度时用 * 或先检查长度

不可变设计原则

python
# ✅ 正确:确保元组内所有元素都可哈希
safe_key = ("user", 123)           # 可作为 dict 键
# ❌ 反模式:包含可变元素的元组作为键
# bad_key = ("user", [1, 2, 3])    # TypeError: unhashable

# ✅ 正确:需要"修改"时创建新元组
config = ("localhost", 3306)
new_config = ("newhost", config[1])  # 不修改原元组

# ❌ 反模式:用元组存储频繁变化的数据
# scores = (85, 90, 78)  # 每次更新都要创建新元组

L3 专家层:深入

底层原理:静态数组

Python 如何实现元组

元组在 Python 中使用静态数组实现:

元组内部结构:
┌─────────────────────────────────────────────────────────────┐
│                    静态数组原理                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   内存布局:                                                 │
│   ┌────────────────────────────────────────────┐           │
│   │ [0]  [1]  [2]  [3]  [4]                    │           │
│   │ 引用 引用 引用 引用 引用                    │           │
│   └────────────────────────────────────────────┘           │
│   固定大小,创建后不变                                        │
│                                                             │
│   与列表的区别:                                              │
│   ─────────────────────────────────────────────             │
│   • 列表:预留容量,可能扩容                                  │
│   • 元组:固定容量,永不扩容                                  │
│   • 列表:存储长度和容量                                      │
│   • 元组:只存储长度                                          │
│                                                             │
│   内存优化:                                                 │
│   ─────────────────────────────────────────────             │
│   • 元组大小固定 → 无预留空间                                 │
│   • Python 缓存小元组 → 重用相同元组                         │
│   • 单元素元组特殊处理                                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

元组缓存机制

python
import sys

# 小元组可能被缓存(相同内容共享内存)
a = (1, 2)
b = (1, 2)
print(a is b)  # True(可能缓存)

# 大元组不缓存
a = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
b = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
print(a is b)  # False(不缓存)

# 空元组:唯一的单例
empty1 = ()
empty2 = tuple()
print(empty1 is empty2)  # True

性能考量

python
import sys
import timeit

# 内存对比
lst = [1, 2, 3, 4, 5]
tpl = (1, 2, 3, 4, 5)
print(sys.getsizeof(lst))  # ~104 bytes
print(sys.getsizeof(tpl))  # ~80 bytes(少约 23%)

# 创建速度对比
print(timeit.timeit('[1, 2, 3, 4, 5]', number=1000000))
# ~0.05s(列表需要分配预留空间)

print(timeit.timeit('(1, 2, 3, 4, 5)', number=1000000))
# ~0.01s(元组直接分配)
操作元组列表说明
创建~5x快基准元组无预留空间逻辑
内存-23%基准无容量字段
索引访问O(1)O(1)相同
哈希O(n)不可哈希元组可哈希
拼接O(n)O(n)相同(都创建新对象)

设计动机

设计选择原因影响
不可变安全、语义明确可哈希、线程安全
静态数组无预留空间内存高效
缓存小元组减少分配相同元组共享内存
只有2个方法简单无修改方法

知识关联

元组知识关联图:
                    ┌─────────────────┐
                    │   不可变序列    │
                    └─────────────────┘

           ┌───────────────┼───────────────┐
           │               │               │
           ▼               ▼               ▼
    ┌───────────┐   ┌───────────┐   ┌───────────┐
    │   元组    │   │   字符串  │   │  frozenset│
    │  tuple    │   │   str     │   │           │
    │ 有序      │   │ 有序      │   │ 无序      │
    └───────────┘   └───────────┘   └───────────┘

           │ 增强可读性

    ┌───────────┐
    │ 命名元组  │
    │ namedtuple│
    │ 字段访问  │
    └───────────┘

           │ 现代替代

    ┌───────────┐
    │ 数据类    │
    │ dataclass │
    │ 类型提示  │
    │ 可选不可变│
    └───────────┘

对比:
    列表 (list) ──→ 可变序列 ──→ 同一类型,可修改
     元组 (tuple) ──→ 不可变序列 ──→ 安全、可哈希

字节码验证:元组解包底层实现

python
import dis

# 元组解包 — UNPACK_SEQUENCE 专用指令
def tuple_unpack():
    x, y, z = (1, 2, 3)

dis.dis(tuple_unpack)
# 输出:
#   LOAD_CONST   ((1, 2, 3))  加载常量元组
#   UNPACK_SEQUENCE  3        解包为 3 个变量
#   STORE_FAST   (x, y, z)    存储到局部变量

# 变量交换 — ROT_TWO 指令优化
def swap():
    a, b = 10, 20
    a, b = b, a  # 不需要临时变量!

dis.dis(swap)
# 输出:
#   LOAD_FAST    b
#   LOAD_FAST    a
#   ROT_TWO                  ← 旋转栈顶两个值
#   STORE_FAST   a
#   STORE_FAST   b
# 结论:a, b = b, a 在字节码层面直接交换,无临时变量开销

内存对比:元组 vs 列表

python
import sys

lst = [1, 2, 3, 4, 5]
tpl = (1, 2, 3, 4, 5)

print(f"列表 sizeof: {sys.getsizeof(lst)} bytes")  # ~104 bytes
print(f"元组 sizeof: {sys.getsizeof(tpl)} bytes")  # ~80 bytes(少 23%)

# 原因:列表存储 ob_size + allocated(容量字段),元组只存储 ob_size
# 元组的 PyTupleObject 比 PyListObject 少一个 Py_ssize_t 字段

# 空元组是单例
a = ()
b = ()
print(a is b)  # True — 所有空元组共享同一对象

# 小元组可能被缓存(Python 内部优化)
a = (1, 2)
b = (1, 2)
print(a is b)  # 可能 True(取决于 Python 实现和上下文)

元组结构体对比(C 层面)

PyListObject 结构:              PyTupleObject 结构:
┌──────────────────────┐        ┌──────────────────────┐
│ ob_refcnt            │        │ ob_refcnt            │
│ ob_type              │        │ ob_type              │
│ ob_size    (len)     │        │ ob_size    (len)     │
│ ob_item    (数组指针)│        │ ob_item    (数组指针)│
│ allocated  (容量)    │        │ (没有 allocated)     │
└──────────────────────┘        └──────────────────────┘
                          少了 1 个 Py_ssize_t ≈ 8 bytes

本章小结

┌─────────────────────────────────────────────────────────────┐
│                      元组 知识要点                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   创建:                                                     │
│   ✓ (1, 2, 3)、tuple()、1, 2, 3                             │
│   ✓ 单元素:(1,) ← 逗号不能省略                             │
│                                                             │
│   特点:                                                     │
│   ✓ 不可变:创建后不能修改                                  │
│   ✓ 可哈希:可作为字典键(元素也必须可哈希)                │
│   ✓ 内存高效:比列表更小更快                                │
│                                                             │
│   方法:                                                     │
│   ✓ count、index(只有 2 个方法)                           │
│                                                             │
│   解包:                                                     │
│   ✓ 基本解包:a, b, c = (1, 2, 3)                           │
│   ✓ 扩展解包:first, *rest = (1, 2, 3, 4)                   │
│   ✓ 嵌套解包:name, (age, city) = person                    │
│                                                             │
│   命名元组:                                                 │
│   ✓ namedtuple('Name', 'field1 field2')                     │
│   ✓ 通过名称访问:p.x, p.y                                  │
│   ✓ _fields、_asdict()、_replace()                          │
│                                                             │
│   使用场景:                                                 │
│   ✓ 固定数据(坐标、颜色)                                  │
│   ✓ 字典键                                                  │
│   ✓ 函数多返回值                                            │
│   ✓ 配置信息                                                │
│                                                             │
│   实际应用:                                                 │
│   ✓ 配置管理、坐标系统、多返回值                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

从简单到复杂:元组的渐进应用

层级1:创建和访问

python
# 基础元组
colors: tuple[str, str, str] = ("red", "green", "blue")
print(colors[0])  # red

层级2:解包

python
# 元组解包
rgb: tuple[int, int, int] = (255, 128, 0)
r, g, b = rgb
print(f"R:{r}, G:{g}, B:{b}")

层级3:作为字典键

python
# 坐标作为键
distances: dict[tuple[int, int], float] = {
    (0, 0): 0.0,
    (1, 0): 1.0,
    (1, 1): 1.414
}
print(distances[(1, 1)])

层级4:命名元组

python
from collections import namedtuple

# 定义命名元组
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
print(f"Point({p.x}, {p.y})")

层级5:函数返回多个值

python
# 多返回值
def get_stats(numbers: list[int]) -> tuple[int, int, float]:
    return min(numbers), max(numbers), sum(numbers) / len(numbers)

minimum, maximum, average = get_stats([1, 2, 3, 4, 5])

综合应用:配置管理系统

这个示例综合运用元组的多个知识点:

python
# 配置管理系统(Python 3.11+)
from collections import namedtuple
from typing import Any

# 使用命名元组定义配置结构
DatabaseConfig = namedtuple('DatabaseConfig', ['host', 'port', 'database', 'user'])
CacheConfig = namedtuple('CacheConfig', ['host', 'port', 'ttl'])

class ConfigManager:
    """配置管理器"""
    
    def __init__(self) -> None:
        # 使用元组存储不可变配置
        self._db_config: DatabaseConfig = DatabaseConfig(
            host="localhost",
            port=3306,
            database="myapp",
            user="admin"
        )
        
        self._cache_config: CacheConfig = CacheConfig(
            host="localhost",
            port=6379,
            ttl=3600
        )
        
        # 环境映射(使用元组作为键)
        self._env_servers: dict[tuple[str, str], str] = {
            ("production", "db"): "db.prod.example.com",
            ("production", "cache"): "cache.prod.example.com",
            ("development", "db"): "localhost",
            ("development", "cache"): "localhost",
        }
    
    def get_db_config(self) -> DatabaseConfig:
        """获取数据库配置"""
        return self._db_config
    
    def get_server(self, env: str, service: str) -> str:
        """获取服务器地址"""
        key: tuple[str, str] = (env, service)
        return self._env_servers.get(key, "unknown")

# 使用示例
config = ConfigManager()
db = config.get_db_config()
print(f"数据库:{db.host}:{db.port}/{db.database}")

server = config.get_server("production", "db")
print(f"生产数据库服务器:{server}")

这个示例展示了:

  • 元组存储不可变配置
  • 命名元组提供字段访问
  • 元组作为字典键
  • 函数返回元组
  • 类型提示的现代语法