← 返回 Day 7 正文

Day 7 · 图解

Day 007 — 元组(Tuple)图解

本文件包含核心概念的 ASCII / Mermaid 图解


1. 元组 vs 列表:内存布局对比

┌─────────────────────────────────────────────────────────────┐
│                    列表对象 (list)                           │
│  ┌─────────────────────────────────┐                       │
│  │ PyObject_HEAD (16 bytes)        │                       │
│  │   ob_refcnt: 1                  │                       │
│  │   ob_type: &PyList_Type         │                       │
│  │ ob_size: 3                      │                       │
│  │ allocated: 4                    │ ← 预分配更多空间!    │
│  │ ob_item ─────┐                  │                       │
│  └──────────────┼──────────────────┘                       │
│                 ↓                                          │
│  ┌──────────────┴────────────────┐                         │
│  │ 指针数组 (连续内存, 32 bytes)  │                         │
│  │ [ptr0] [ptr1] [ptr2] [未使用] │← 多余的 1 个槽位       │
│  └───────┬──────┬──────┬─────────┘                         │
│          │      │      │                                   │
│          ↓      ↓      ↓                                   │
│         [1]    [2]    [3]       ← Python 整数对象          │
│                                                                
│  合计: 56 + (16~32) = 72~88 bytes                          │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                    元组对象 (tuple)                          │
│  ┌─────────────────────────────────┐                       │
│  │ PyObject_HEAD (16 bytes)        │                       │
│  │   ob_refcnt: 1                  │                       │
│  │   ob_type: &PyTuple_Type        │                       │
│  │ ob_size: 3                      │                       │
│  │ ┌─────────────────────────────┐ │                       │
│  │ │ ob_item[0] = ptr0           │ │  ← 指针直接嵌入      │
│  │ │ ob_item[1] = ptr1           │ │     结构体内部!      │
│  │ │ ob_item[2] = ptr2           │ │     没有额外分配!    │
│  │ └───────┬──────┬──────┬───────┘ │                       │
│  └─────────┼──────┼──────┼─────────┘                       │
│            │      │      │                                 │
│            ↓      ↓      ↓                                 │
│           [1]    [2]    [3]       ← Python 整数对象        │
│                                                                
│  合计: 40 bytes (一次性,无浪费)                           │
└─────────────────────────────────────────────────────────────┘

2. 元组拆包流程图

a, b, c = (1, 2, 3)

            (1, 2, 3)
               │
     ┌─────────┴─────────┐
     │ 获取迭代器         │
     │ for val in tuple:  │
     └─────────┬─────────┘
               │
     ┌─────────▼─────────┐
     │ 第一次 next() → 1  │ ←── a
     │ 第二次 next() → 2  │ ←── b
     │ 第三次 next() → 3  │ ←── c
     │ 第四次 next() → 抛 │ ←── StopIteration
     └───────────────────┘


带星号的拆包流程:

first, *middle, last = (1, 2, 3, 4, 5)

            (1, 2, 3, 4, 5)
                   │
     ┌─────────────▼─────────────┐
     │ Python 扫描星号位置       │
     │                           │
     │ 左边部分:first → t[0]    │
     │ 星号变量:*middle → t[1:4]│
     │ 右边部分:last → t[4]     │
     └─────────────┬─────────────┘
                   │
         ┌─────────▼─────────┐
         │ first = 1         │
         │ middle = [2, 3, 4]│ ←── 注意是列表!
         │ last = 5          │
         └───────────────────┘

3. namedtuple 继承关系图

classDiagram class tuple { __hash__() __getitem__(index) count(value) index(value) __len__() } class namedtuple_Point { +float x +float y _fields _asdict() _replace(**kwargs) _make(iterable) } class NamedTuple_Vector { +float x +float y +float z distance_to(other) length() _fields } tuple <|-- namedtuple_Point : 继承 tuple <|-- NamedTuple_Vector : 继承 %% namedtuple 在元组基础上增加了: %% - 字段名(属性访问) %% - __repr__(友好输出) %% - _fields(字段元组) %% - _asdict()(转字典) %% - _replace()(创建修改副本) %% - _make()(工厂方法)
classDiagram
    class tuple {
        __hash__()
        __getitem__(index)
        count(value)
        index(value)
        __len__()
    }
    
    class namedtuple_Point {
        +float x
        +float y
        _fields
        _asdict()
        _replace(**kwargs)
        _make(iterable)
    }
    
    class NamedTuple_Vector {
        +float x
        +float y
        +float z
        distance_to(other)
        length()
        _fields
    }
    
    tuple <|-- namedtuple_Point : 继承
    tuple <|-- NamedTuple_Vector : 继承
    
    %% namedtuple 在元组基础上增加了:
    %% - 字段名(属性访问)
    %% - __repr__(友好输出)
    %% - _fields(字段元组)
    %% - _asdict()(转字典)
    %% - _replace()(创建修改副本)
    %% - _make()(工厂方法)

4. 元组不可变性示意图

t = (1, [2, 3], 4)

内存中的元组对象:
┌─────────────────────────────┐
│ 元组对象 (不可变外壳)        │
│                             │
│  ob_item[0] ───→ 整数 1    │ ← 不可替换(❌ t[0]=99)
│  ob_item[1] ───→ 列表对象   │ ← 不可替换(❌ t[1]=[5,6])
│  ob_item[2] ───→ 整数 4    │ ← 不可替换
└─────────────────────────────┘
                        │
                        ▼
                  ┌──────────┐
                  │ 列表 [2,3]│ ← 可变内容!
                  │          │   可以修改(✅ t[1].append(4))
                  │ 内容可变 │   因为元组只存引用,不存内容
                  └──────────┘

关键理解:
  "元组的不可变 = 引用不可变,引用的对象可变"
  元组保存的是"到对象的指针",指针不能改
  但指针指向的对象内部可以改变

5. 列表 vs 元组 — 选择决策树

数据需要放在一个序列中吗?
│
├── 是
│   │
│   ├── 数据数量会变化吗?(增/删/改)
│   │   │
│   │   ├── 是 ─────────→ 使用 列表 list
│   │   │
│   │   └── 否
│   │       │
│   │       ├── 需要做字典键吗?
│   │       │   │
│   │       │   ├── 是 ─→ 使用 元组 tuple
│   │       │   │
│   │       │   └── 否
│   │       │       │
│   │       │       ├── 数据是同质的(全是同一类型)?
│   │       │       │   ├── 是 → 列表(通常要遍历操作)
│   │       │       │   └── 否 → 元组(异质数据常是记录)
│   │       │       │
│   │       │       └── 性能敏感且数据量大?
│   │       │           ├── 是 → 元组(更快更省内存)
│   │       │           └── 否 → 列表(灵活优先)
│   │
│   └── 需要命名属性访问吗?
│       ├── 是 → namedtuple 或 dataclass
│       └── 否 → 普通元组或列表
│
└── 否 → 不需要序列,用其他数据结构

6. 多返回值拆包示意

def func():
    return 1, 2, 3

Python 解析步骤:
┌─── return 1, 2, 3 ───────────────────────┐
│                                           │
│  1. 1, 2, 3 → Python 构造元组 (1, 2, 3)  │
│     (元组字面量,自动打包)                  │
│                                           │
│  2. return → 返回元组对象                  │
│                                           │
│  3. 调用方接收:result = func()            │
│     → result = (1, 2, 3),类型是 tuple     │
│                                           │
│  4. 拆包:a, b, c = func()                │
│     → 右侧先执行 func() 返回 (1, 2, 3)     │
│     → 再执行拆包:a=1, b=2, c=3            │
└───────────────────────────────────────────┘