Day13 · 列表与元组

Day13 · 列表与元组

阶段:阶段一 · 核心基础 | 前置:Day12 | 技术基准:Python 3.15.0

学习目标

  • 掌握列表(list)的定义、索引、切片、遍历与常用方法
  • 理解列表是"可变、有序、元素可异构"的序列
  • 掌握列表生成式,能写出简洁高效的列表构造代码
  • 掌握元组(tuple)的定义、单元素陷阱、不可变性与拆包
  • 会做列表/元组与其他类型之间的显式类型转换

从今天开始进入 Python 最常用的"容器"类型。前面我们用变量只能存一个数,而真实业务要存"全班 50 个同学的成绩""购物车几十件商品"------这就需要能装一堆数据的容器。本章先讲两个有序序列:列表(可变)和元组(不可变)。

一、列表 list 是什么

列表是对传统数组的高级封装,但比数组更强大:大小可变、元素类型可以不同、有序、可通过索引访问 。定义用方括号 [],元素之间用逗号 , 隔开。

特点一览:

  • 元素按顺序排列,通过 下标 访问(下标从 0 开始);
  • 元素类型可以各不相同(数字、字符串、甚至另一个列表都行);
  • 大小可以动态增删,没有固定上限;
  • 是可变(mutable)类型,可以原地修改某个元素。
python 复制代码
# Author: XiaoJia
# 字面量创建
empty = []                       # 空列表
nums = [1, 2, 3, 4, 5]
mix = [1, "小嘉", 3.14, True]     # 元素类型可以不同

# 构造函数创建
print(list())                    # []
print(list("Python"))            # ['P','y','t','h','o','n']  字符串拆成字符列表
print(list(range(1, 6)))         # [1, 2, 3, 4, 5]

二、索引与切片

2.1、索引访问

python 复制代码
# Author: XiaoJia
data = [10, 20, 30, 40, 50]

print(data[0])     # 10   第一个元素,下标从 0 开始
print(data[2])     # 30
print(data[-1])    # 50   负数下标从右往左数
print(data[-2])    # 40

data[1] = 200     # 列表可变,可以通过下标改元素
print(data)        # [10, 200, 30, 40, 50]

下标越界会报 IndexError:data[5](最大下标是 4)直接崩。

2.2、切片

切片语法 列表[开始:结束:步长],含头不含尾:

python 复制代码
# Author: XiaoJia
data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print(data[2:5])      # [2, 3, 4]
print(data[:3])       # [0, 1, 2]     从头切到 3
print(data[7:])       # [7, 8, 9]     从 7 切到尾
print(data[::2])      # [0, 2, 4, 6, 8]  步长 2
print(data[::-1])     # [9,8,7,...,0]   步长 -1 逆序
print(data[2:100])    # [2,3,...,9]   结束下标越界也不报错

切片还能用来批量替换(Day15 会再深入):

python 复制代码
# Author: XiaoJia
data = [0, 1, 2, 3, 4]
data[0:2] = [100, 200]
print(data)           # [100, 200, 2, 3, 4]

三、遍历列表

python 复制代码
# Author: XiaoJia
data = [10, 20, 30]

# 最常用:for...in 直接拿元素
for item in data:
    print(item, end=" ")
print()                       # 10 20 30

# 同时拿下标:enumerate
for i, v in enumerate(data):
    print(i, v)

# 求和
print("总和", sum(data))       # 60

四、列表常用方法

方法 作用
append(x) 末尾追加一个元素
extend(可迭代) 末尾批量追加多个元素
insert(i, x) 在下标 i 处插入 x
remove(x) 删除第一个值为 x 的元素
pop() / pop(i) 弹出末尾 / 下标 i 的元素
index(x) 找 x 第一次出现的下标
count(x) 统计 x 出现几次
sort() 原地升序排序
reverse() 原地反转
clear() 清空
copy() 浅拷贝一份
python 复制代码
# Author: XiaoJia
data = [3, 1, 4, 1, 5]

data.append(9)            # 末尾加一个
data.insert(0, 0)         # 最前面插一个
print(data)               # [0, 3, 1, 4, 1, 5, 9]

data.remove(1)            # 删除第一个 1
print(data)               # [0, 3, 4, 1, 5, 9]
print(data.pop())         # 9   弹出末尾
print(data.pop(0))        # 0   弹出下标 0

print(data.count(1))      # 1
print(data.index(4))       # 1

data.sort()                # 原地升序
print(data)               # [1, 3, 4, 5]
data.reverse()
print(data)               # [5, 4, 3, 1]

data.extend([7, 8])        # 批量追加
print(data)               # [5, 4, 3, 1, 7, 8]

append 和 extend 的区别是新手高频考点:

python 复制代码
# Author: XiaoJia
a = [1, 2]
a.append([3, 4])      # 把整个列表当一个元素塞进去
print(a)              # [1, 2, [3, 4]]

b = [1, 2]
b.extend([3, 4])      # 把列表里的元素逐个加进去
print(b)              # [1, 2, 3, 4]

sorted() vs list.sort():sort() 原地改、返回 None;sorted() 返回一个新列表、不动原列表:

python 复制代码
# Author: XiaoJia
nums = [3, 1, 2]
new = sorted(nums)       # 返回新列表
print(nums)              # [3, 1, 2]  原列表没变
print(new)               # [1, 2, 3]

五、列表生成式

用一行表达式从旧序列快速构造新列表,是 Python 最著名的特色写法之一。语法只有一句话:

python 复制代码
[表达式 for 变量 in 序列 if 条件]

5.1、基础语法:从旧序列构造新列表

python 复制代码
# Author: XiaoJia
# 传统写法:先建空列表,再循环逐个 append
result = []
for i in range(1, 6):
    result.append(i * i)
print(result)            # [1, 4, 9, 16, 25]

# 列表生成式一行搞定
print([i * i for i in range(1, 6)])             # [1, 4, 9, 16, 25]

读法:[表达式 for 变量 in 序列] 就是"对序列里每个元素,套进表达式,收集成一个新列表",表达式中用到遍历变量 i。它不只对 range 用,对任何可迭代对象(列表、字符串、元组......)都可以:

python 复制代码
# Author: XiaoJia
words = ["python", "java", "go", "c"]
print([w.upper() for w in words])          # ['PYTHON','JAVA','GO','C']

# 对字符串(字符序列)做变换
print([ch + "!" for ch in "abc"])          # ['a!','b!','c!']

# 对已有列表做数学变换
nums = [1, 2, 3, 4]
print([n * 10 + 1 for n in nums])          # [11, 21, 31, 41]

# 从字符串拆出每个字符再处理
print([int(d) for d in "1234"])            # [1, 2, 3, 4]

5.2、带 if 过滤

在 for 后面加 if 条件,只有满足条件的元素才会进入结果:

python 复制代码
# Author: XiaoJia
# 1~10 里的奇数
print([i for i in range(1, 11) if i % 2 == 1])   # [1,3,5,7,9]

# 从单词里只挑长度 >= 3 的
words = ["我", "Python", "AI", "数据", "TensorFlow"]
print([w for w in words if len(w) >= 3])          # ['Python','数据','TensorFlow']

# 过滤负数
print([n for n in [-3, 1, -2, 5, 0] if n > 0])    # [1, 5]

顺序:for 在前、if 在后,if 起"筛选"作用。想对不符合条件的元素做"替换"而不是丢弃,用下一节的 if-else。

5.3、if-else 三元变换(过滤后再改值)

当你想"保留所有元素、但对不同元素给不同值"时,把 if-else 写回表达式里 (位置在 for 之前):

python 复制代码
# Author: XiaoJia
# 正数保留,负数归零
print([x if x > 0 else 0 for x in [-3, 1, -2, 5]])   # [1, 0, 0, 5]

# 偶数是原值,奇数变成它的负值
print([x if x % 2 == 0 else -x for x in range(1, 6)])  # [-1, 2, -3, 4, -5]

两种 if 别搞混:

  • [x for x in 序列 if 条件] ------ if 在后面,作用是"筛掉不满足的",结果元素个数会变少;
  • [x if 条件 else y for x in 序列] ------ if-else 在前面,作用是"逐个选值",结果元素个数不变。

5.4、嵌套循环:一行写出多重 for

生成式里可以写多个 for,等价于嵌套循环:

python 复制代码
# Author: XiaoJia
# 字母 × 数字
print([x + y for x in "AB" for y in "12"])      # ['A1','A2','B1','B2']

# 二维矩阵摊平成一维
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [num for row in matrix for num in row]
print(flat)                                     # [1, 2, 3, 4, 5, 6]

把它拆开看就懂了,等价写法:

python 复制代码
# Author: XiaoJia
matrix = [[1, 2], [3, 4], [5, 6]]
flat = []
for row in matrix:          # 第一个 for
    for num in row:         # 第二个 for
        flat.append(num)    # 收集
print(flat)                 # [1, 2, 3, 4, 5, 6]

读诀窍:从左往右读 for,最左边的先循环,最后面跟的是要收集的表达式 。[num for row in matrix for num in row] = 先取 row,再从 row 里取 num,收集 num。

5.5、可读性:什么时候用、什么时候别硬用

列表生成式追求"一行简洁",但不是越短越好:

python 复制代码
# Author: XiaoJia
# 推荐:一目了然
squares = [i * i for i in range(10)]

# 勉强可以:两个 for + 一个 if,已接近可读性极限
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
even = [n for row in matrix for n in row if n % 2 == 0]

# 别硬写:超过三层嵌套、或逻辑复杂时,回到普通 for 循环更清晰
# 用循环不是"退步",而是"可读性优先"(呼应 Day01 的 Python 之禅)

经验法则:单个 for + 最多一个 if,随便写;两个 for,看清层级再写;三个及以上 for,果断改用普通循环。

5.6、性能:生成式通常比循环更快

生成式在 CPython 里是高度优化的,通常比"手动建空列表 + append"更快,代码也更短:

python 复制代码
# Author: XiaoJia
# 时间对比(timeit 统计 1 万次构造 1~1000 平方列表的耗时)
import timeit

def by_loop():
    result = []
    for i in range(1, 1001):
        result.append(i * i)
    return result

def by_comprehension():
    return [i * i for i in range(1, 1001)]

t1 = timeit.timeit(by_loop, number=10000)
t2 = timeit.timeit(by_comprehension, number=10000)
print(f"循环耗时:   {t1:.3f}s")            # 本机约 0.29s
print(f"生成式耗时: {t2:.3f}s")            # 本机约 0.26s,略快(数值随机器波动)

结论:生成式更简洁、通常也更快,是构造列表的首选。真正的性能差异在数据量大时才明显,日常规模两者都能跑。它的更多进阶形态(字典/集合推导式、生成器表达式、性能再对比)在 Day15 展开。

六、元组 tuple 是什么

元组和列表几乎一样,区别只有一个:元组不可变(immutable) ------创建之后不能增、删、改元素。定义用圆括号 ()。

python 复制代码
# Author: XiaoJia
t = (1, 2, 3)
print(t[0])         # 1
print(len(t))       # 3

# t[0] = 99         # TypeError:元组不支持修改

6.1、单元素元组的陷阱

python 复制代码
# Author: XiaoJia
a = (123)       # 这不是元组!就是整数 123,括号被当成普通括号
print(type(a))  # <class 'int'>

b = (123,)      # 必须加逗号,才是单元素元组
print(type(b))  # <class 'tuple'>

一个元素的元组后面必须跟逗号 ,这是规定,别问为什么。空元组则直接 ()。

6.2、元组的运算与方法

python 复制代码
# Author: XiaoJia
t1 = (1, 2)
print(t1 + (3, 4))    # (1, 2, 3, 4)   拼接,产生新元组
print(t1 * 3)         # (1, 2, 1, 2, 1, 2)  重复

t = (1, 2, 3, 2, 1)
print(t.count(2))     # 2
print(t.index(3))     # 2
print(max(t), min(t)) # 3 1

因为不可变,元组没有 append/sort/remove 这些修改自身的方法,只有 count 和 index。

6.3、拆包(unpacking)

把一个元组(或列表)里的元素一次性赋给多个变量:

python 复制代码
# Author: XiaoJia
point = (3, 4)
x, y = point
print(x, y)           # 3 4

# 经典应用:交换两个变量的值(不需要中间临时变量)
a, b = 1, 2
a, b = b, a
print(a, b)           # 2 1

# 带星号的拆包
first, *rest = [1, 2, 3, 4]
print(first)          # 1
print(rest)           # [2, 3, 4]

6.4、namedtuple 简介

元组的元素只能靠下标 t[0]、t[1] 访问,可读性差。collections.namedtuple 让元组元素可以"按名字"访问:

python 复制代码
# Author: XiaoJia
from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y)       # 3 4
print(p[0], p[1])     # 3 4   下标照样能用

七、列表 vs 元组对比

对比项 列表 list 元组 tuple
定义符号 [] ()
可变性 可变,能增删改 不可变
方法数量 多(append/sort 等) 少(只有 count/index)
性能 稍慢 稍快、更省内存
能否做字典 key 不能(可变不可哈希) 能
典型用途 同类型数据的动态集合 固定记录、多返回值、拆包

经验法则:数据需要变就用列表,数据定下来不想被改就用元组。函数一次返回多个值,Python 底层就是打包成元组返回的。

八、显式类型转换

Day06 提过,这里结合容器系统列一下常用内建转换函数:

python 复制代码
# Author: XiaoJia
print(int("18"))         # 18      字符串 → 整数
print(float("3.14"))     # 3.14
print(str(100))          # "100"   数字 → 字符串
print(list("abc"))        # ['a','b','c']  字符串 → 列表
print(tuple([1, 2]))      # (1, 2)         列表 → 元组
print(list((1, 2)))       # [1, 2]         元组 → 列表
print(set([1, 1, 2]))     # {1, 2}         列表 → 集合(自动去重,Day14 讲)

# 常用统计函数,对列表/元组都适用
nums = [3, 1, 4, 1, 5]
print(len(nums), max(nums), min(nums), sum(nums))   # 5 5 1 14

九、栈、队列与元组的"不可变假象"

9.1、用列表当栈和队列

append() 在末尾加、pop() 从末尾弹,正好构成"后进先出"的栈(stack):

python 复制代码
# Author: XiaoJia
stack = []
stack.append("第一")
stack.append("第二")
print(stack.pop())      # 第二   后进先出
print(stack.pop())      # 第一

但用列表当**队列(queue,先进先出)**就很不合适:pop(0) 要把后面所有元素整体前移,数据一大非常慢。真要高效队列,标准库 collections.deque 才是正解(两端操作都是 O(1)),这里先建立概念,Day27 会用。

9.2、赋值是引用,不是拷贝

b = a 只是给同一个列表再起个名字,不是复制:

python 复制代码
# Author: XiaoJia
a = [1, 2, 3]
b = a
b.append(4)
print(a)        # [1, 2, 3, 4]   改 b 连 a 一起变

想要一份互不影响的副本,用 a.copy()、a[:] 或 list(a)。注意这只是"浅拷贝",列表里若还套着列表,内层仍共享------完整解释见 Day15。

9.3、元组不可变的"假象"

元组本身不可变,但它里面装的那个列表可是可变的:

python 复制代码
# Author: XiaoJia
t = (1, [2, 3])
# t[0] = 99          # TypeError:元组元素不能重新赋值
t[1].append(4)        # 合法!改的是那个列表内部,不是元组的指向
print(t)              # (1, [2, 3, 4])

这并不矛盾:元组保证的是"它指向的东西不变",至于它指向的列表自己怎么变,元组管不着。所以元组可哈希的前提是:里面装的全是不可变对象。反过来用一个"里面套了列表"的元组去做字典 key,照样会报 TypeError: unhashable type: 'list',原因就在这里------不是元组不哈希,是它内部那个列表不哈希。

练习题

  1. 定义列表 [7, 3, 9, 2, 5],用切片取出中间三个元素,再用 sort() 升序排列并打印。
  2. 用列表生成式生成 1~10 的平方数列表,再用 filter 思路(生成式加 if)只保留偶数平方。
  3. 写一个元组 (10, 20, 30),用拆包把它赋给三个变量,然后在不引入第三个变量的情况下交换前两个。
  4. 解释:为什么 (100) 不是元组?怎么写才是单元素元组?
  5. 给定列表 ["张三", "李四", "王五"],用 enumerate 输出 1. 张三 这样的编号列表。
  6. 思考题:什么场景下你会优先用元组而不是列表?为什么元组能当字典的 key 而列表不行?
  7. 用列表生成式,从一个句子 s = "I love Python and data" 里提取所有长度不小于 4 的单词(提示:s.split() 先切成单词列表)。
  8. 用列表生成式把摄氏温度列表 [0, 20, 37, 100] 转换为华氏(公式 F = C * 9 / 5 + 32),结果保留一位小数(可用 round 或 f-string)。
  9. 用嵌套生成式把二维列表 [[1, 2], [3, 4], [5, 6]] 摊平,再只保留其中的奇数。

小结

  • 列表 []:有序、可变、元素可异构;支持索引、切片、增删改查。
  • 常用方法:append/extend/insert/remove/pop/index/count/sort/reverse;注意 append 是塞一个,extend 是拆进去。
  • 列表生成式 [表达式 for x in 序列 if 条件] 是 Pythonic 的列表构造方式。
  • 两种 if 区分:for 后的 if 是"筛掉",表达式里的 if-else 是"选值";多层 for 从左往右读、等价嵌套循环。
  • 可读性优先:单 for + 一个 if 随便写,三 for 以上改回循环;生成式通常比手写循环更快。
  • 元组 ():不可变;单元素必须写逗号 (x,);支持拆包,交换变量 a, b = b, a。
  • 列表 vs 元组:可变用列表,定下来用元组;元组可哈希能做字典 key。
  • 类型转换:int/float/str/list/tuple/set;统计函数 len/max/min/sum 通用。

扩展阅读

相关推荐
时间售卖铺1 小时前
MySQL事务ACID与隔离级别深度解析
运维·数据库·mysql·云原生
努力努力再努力wz1 小时前
【Lua入门系列】从 C++ 迭代器到 Lua 迭代器:一文深入理解 Lua 有状态以及无状态迭代器与泛型for、闭包、ipairs、pairs 和 next
开发语言·c++·lua
小小张说故事2 小时前
Python 的 int 为什么不会溢出?拆开 PyLongObject 和它背后那串变长数组
python
努力实干2 小时前
第二阶段 · 第 7 课:C++ 多线程与线程安全
开发语言·c++·安全
boy快快长大2 小时前
EasyTier初次使用
服务器
中屹指纹浏览器svip2 小时前
指纹浏览器RPA自动化靠谱吗?先分清适用边界
运维·自动化·rpa
zuiyuewentian2 小时前
借助 AI 在 Win11 上搭建 Rocky Linux:用 Visual Studio 直接调试 Linux 服务
linux·运维·visual studio
牢姐与蒯2 小时前
WSL2 迁移教程:把Ubuntu+WSL2从C盘挪到别的盘
linux·运维·服务器·网络·ubuntu·1024程序员节
垆边人似月.2 小时前
RDMA 文件传输踩坑(二):为什么 `sleep()` 不能保证接收端准备好了?
linux·服务器·网络·rdma