| Day 6 | 列表与元组:增删改查、切片与遍历 |

1 列表的底层 —— 内存中的连续车厢

是什么?为什么学?

列表(list)是 Python 里最常用的容器:一个变量装多个值。之前学的变量一次只能存一个值,但现实中常常有一堆数据——购物清单有 10 样东西、一个班有 30 个成绩。列表就是用来「一次装一堆」的容器。为什么零基础必学?了解它的底层设计,你才能理解「下标为什么从 0 开始」「为什么 insert 比 append 慢」这些高频问题。

底层原理

Python 的列表在内存里是动态数组:它申请一块连续的内存,每个元素存一个「引用」(指向真实数据的地址,类似快递单号)。因为存的是引用而不是数据本身,所以同一个列表里可以混装字符串、数字、布尔值。按下标取值 fruits[2] 时,Python 直接按「起始地址 + 2 × 每个引用占的字节数」算出位置,一步到位——所以下标取值极快,这也是下标从 0 开始的原因:第 1 个元素的偏移量是 0。

术语小词典:引用(reference)就是「指向真实数据的地址编号」,像快递单号——凭单号去仓库取真东西。

生活类比

列表像一列火车:车厢一节挨一节(内存连续),每节车厢里放的是一张「快递单号」(引用),凭单号去仓库取真东西(数据)。想找第 3 节车厢的东西,从车头数过去就行——这就是按下标取值的原理。

注意事项总结

1 一个变量装多个值 + 按下标取

fruits = ["苹果", "香蕉", "橘子", "葡萄"]
print(fruits[0])     # 第一个元素
print(fruits[1])     # 第二个元素
print(fruits[3])     # 第四个元素

运行结果:

苹果
香蕉
葡萄

下标从 0 开始数,所以下标 0、1、2、3 对应第一、二、三、四个元素。写 fruits[4] 就会报 IndexError(越界)。

2 负数下标——从尾部数

fruits = ["苹果", "香蕉", "橘子"]
print(fruits[-1])    # 倒数第一个
print(fruits[-2])    # 倒数第二个

运行结果:

橘子
香蕉

负数下标本质是「长度 – 正数」。列表长了之后,用 -1 取最后一个元素是最高频的写法。

3 len() 数长度 + 字符串也能按下标取

fruits = ["苹果", "香蕉", "橘子"]
print(len(fruits))      # 列表长度
word = "Python"
print(word[0])          # 字符串也能按下标取字符
print(len(word))

运行结果:

3
P
6

字符串本质上也是「字符的序列」,同样支持下标。这也解释了为什么 Day 5 里 for ch in "abc" 能遍历字符串——凡是「能按下标取」的东西都能被 for 遍历。

易错点

  • 错误写法:fruits[3](列表只有 3 个元素) → 问题:报 IndexError: list index out of range,下标越界 → 正确写法:最大下标是 len(列表) - 1,或用负数下标 [-1] 取最后一个。
  • 错误写法:以为下标从 1 开始,fruits[1] 想取第一个 → 问题:实际取到的是第二个元素 → 正确写法:下标从 0 开始,第 n 个元素的下标是 n – 1。
  • 错误写法:list = [1, 2] 用 list 当变量名 → 问题:覆盖了内置函数 list(),后面再调用 list() 会报错 → 正确写法:别用 liststrint 等内置名字当变量名。

记忆口诀:下标从零数,-1 取末尾;内存连续存,取值快如飞。

2 增删改查 —— 动态数组的四大操作

是什么?为什么学?

列表能随时增、删、改,这是它比元组、字符串强的地方。以后写程序处理数据(加一条记录、删一条记录、改一个值)全靠这几个操作,是列表使用频率最高的部分。

底层原理

因为列表是动态数组:append 在末尾追加时,容量够就直接写;容量不够就「搬家」——重新申请一块更大的内存(通常是原容量的 1.125 倍或更多),把旧元素复制过去再追加,所以 append 偶尔慢一下,但平均很快。insert 插到中间则要把插入位置之后的元素全部往后挪一位,元素越多越慢。remove 删除同理:先找到值的位置,再把后面的元素往前挪。

生活类比

列表像一列可改造的火车append 是在车尾挂一节新车厢(最省事);insert 是硬在中间插一节,后面的车厢都得往后挪;remove / pop 是卸掉一节车厢,后面的再往前补位。挪动越多越慢——所以车尾操作(append / pop 末尾)总是最快的。

注意事项总结

1 增——append 和 insert

shopping = ["牛奶"]
shopping.append("鸡蛋")      # 末尾追加
shopping.append("面包")
shopping.insert(1, "火腿")   # 插到下标 1
print(shopping)

运行结果:

['牛奶', '火腿', '鸡蛋', '面包']

append(值) 永远把新元素放到最后;insert(位置, 值) 把值插到指定下标,原本该位置及之后的元素自动往后挪。

2 删——remove 按值删,pop 按下标删

nums = [1, 2, 3, 4]
nums.remove(2)          # 按「值」删除第一个 2
last = nums.pop()       # 弹出最后一个
first = nums.pop(0)     # 弹出下标 0 的元素
print(nums)

运行结果:

[3]

remove(值) 按值删除(只删第一个匹配的,值不存在会报 ValueError);pop(位置) 删除并返回被删的值,不写位置默认弹最后一个。

3 改——按下标直接赋值

scores = [85, 92, 78]
scores[2] = 99          # 把第三个成绩改成 99
scores[0] = scores[0] + 10   # 第一个成绩加 10 再存回去
print(scores)

运行结果:

[95, 92, 99]

列表[下标] = 新值 是「改」的唯一写法。scores[0] = scores[0] + 10 先读旧值、加 10、再写回——这是「原地更新」的经典写法。

易错点

  • 错误写法:nums.remove(99)(99 不在列表里) → 问题:报 ValueError: x is not in list → 正确写法:先 if 99 in nums: 判断,再 remove。
  • 错误写法:shopping[5] = "可乐"(想加新元素) → 问题:报 IndexError,下标赋值不能越界新增 → 正确写法:追加用 append,插入用 insert
  • 错误写法:nums.remove(2) 想删掉所有 2 → 问题:只删了第一个 2 → 正确写法:循环 remove 直到 2 not in nums,或用列表推导(Day 8 后学)。
  • 错误写法:fruits = fruits.append("梨") → 问题:把 append 的返回值赋给变量,fruits 变成 None! → 正确写法:append 是「原地操作」,返回 None,直接 fruits.append("梨") 即可。

记忆口诀:末尾追加 append,指定位置 insert;按值删除 remove,弹出用 pop 有回礼。

3 切片 —— 一次取出一段数据

是什么?为什么学?

切片(slicing)用 列表[开始:结束:步长] 一次取出一段,是 Python 最优雅的语法之一。反转、取前三个、隔一个取一个……一行搞定。学会切片,很多看似复杂的操作会变成一行代码。

底层原理

切片操作会创建一个新列表(拷贝):Python 按你给的开始、结束、步长,把对应的元素复制到新列表里。所以切出来的列表和原列表是「两份独立的数据」——修改切片结果不会影响原列表。步长为负时,Python 从尾部反向取,[::-1] 因此成了「反转」的经典写法。

生活类比

切片像复印机:你从一摞资料里选出第 3~5 张,复印出一份新的小册子——原资料一动不动,复印件随便改。[:] 就是「整摞全部复印一遍」,[::-1] 是「倒着复印一遍」。

注意事项总结

1 基本切片——含头不含尾

nums = [10, 20, 30, 40, 50]
print(nums[1:4])     # 下标 1 到 3
print(nums[:3])      # 从头到下标 2
print(nums[2:])      # 从下标 2 到末尾
print(nums[:])       # 整个拷贝

运行结果:

[20, 30, 40]
[10, 20, 30]
[30, 40, 50]
[10, 20, 30, 40, 50]

[1:4] 取下标 1、2、3(含 1 不含 4),和 range 一样「含头不含尾」;省略开始 = 从头开始;省略结束 = 到末尾;[:] 都省略 = 整个列表的拷贝(不是同一个列表!)。

2 步长——隔一个取一个、反转

nums = [10, 20, 30, 40, 50]
print(nums[::2])     # 隔一个取一个
print(nums[::-1])    # 反转

运行结果:

[10, 30, 50]
[50, 40, 30, 20, 10]

[::2] 步长为 2,取下标 0、2、4;[::-1] 步长为 -1,从尾部开始每次减 1,结果就是反转。

3 切片产生的是新列表(修改切片不影响原列表)

nums = [1, 2, 3, 4, 5]
part = nums[1:3]
part[0] = 99
print(part)          # 修改切片结果
print(nums)          # 原列表不受影响

运行结果:

[99, 3]
[1, 2, 3, 4, 5]

改 part 只是改那份拷贝,nums 还是原样。理解这一点,你就不会犯「改切片以为改了原列表」的错误。

易错点

  • 错误写法:nums[1:4] 以为取 4 个 → 问题:只取 3 个(下标 1、2、3) → 正确写法:记住含头不含尾:个数 = 结束 – 开始。
  • 错误写法:想反转却写 nums[-1] → 问题:nums[-1] 只是最后一个元素,不是反转 → 正确写法:反转写 nums[::-1]nums[-1] 是取末尾单个元素。
  • 错误写法:切片的开始比结束大(正步长,如 nums[4:1]) → 问题:返回空列表 [],不报错但结果不是想要的 → 正确写法:想从后往前取要用负步长。
  • 错误写法:以为切片会修改原列表 → 问题:切片永远返回新列表,原列表不变 → 正确写法:想原地改原列表用赋值:nums[:2] = [0, 0]

记忆口诀:切片三参数,含头不含尾;负号一出现,方向就反转。

4 元组 —— 为什么需要「不可变」?

是什么?为什么学?

元组(tuple)用圆括号,长得像列表,但创建后不能增删改。听起来很没用?恰恰相反:正因为不可变,元组才能当字典的键、才能安全地到处传递不被意外修改,运行速度还更快。它是 Python 里「固定数据」的专用容器。

底层原理

元组在内存里是固定大小的数组:创建时一次性分配好所有元素的空间,之后结构不再变化,所以元组没有 append/remove 这些方法。也正因为结构固定,Python 能做更多优化(更少内存、更快的访问),还能算出固定的「哈希值」——这是它以后能当字典键的根本原因(Day 7 会学)。列表可变,算不出稳定的哈希,所以列表不能当字典键。

术语小词典:哈希值(hash value)就是给每个键算一个类似「索引编号」的数字,按编号直接定位,查起来飞快——就像图书馆按书号找书,而不是一本本翻。

生活类比

元组像塑封的证件照:照片定好后就不能再改(不可变),正因为不会变,才能放心地到处提交(当字典键、安全传递);列表像普通照片,随时能涂改,反而不适合当「身份证明」。

注意事项总结

1 创建和读取元组 + 拆包

point = (3, 5)          # 一个坐标点
print(point[0])         # 按下标取
x, y = point            # 拆包:一次性取出所有值
print("x =", x, "y =", y)

运行结果:

3
x = 3 y = 5

x, y = point 是元组专属的「拆包」语法——左边写几个变量,右边元组有几个元素,自动一一对应赋值。Day 7 遍历字典时还会见到。

2 元组不能修改(错误示范,运行会报错)

point = (3, 5)
# point[0] = 100   # 运行会报错:TypeError: 'tuple' object does not support item assignment
print("元组一旦创建,内容就固定了")

「不可变」指的是结构不可变:元组里的位置和个数固定了,但元素本身如果是可变类型(如列表)仍然可以改。

3 元组的两种创建姿势(一个小陷阱)

t1 = (1, 2, 3)          # 正常元组
t2 = (1)                # 没有逗号!这是整数不是元组
t3 = (1,)               # 加逗号才是元组
print(type(t1))
print(type(t2))
print(type(t3))

运行结果:

<class 'tuple'>
<class 'int'>
<class 'tuple'>

(1) 里没有逗号,Python 把它当成数学里的括号,结果是整数 1;创建单元素元组必须写 (1,)——这个逗号是元组的「身份标识」。

易错点

  • 错误写法:point[0] = 100 → 问题:报 TypeError,元组不能改 → 正确写法:用列表;或重新创建元组 point = (100, 5)
  • 错误写法:t = (1) 想创建单元素元组 → 问题:t 其实是整数 1 → 正确写法:写 t = (1,),逗号不能省。
  • 错误写法:试图 tuple.append(1) → 问题:报 AttributeError,元组没有 append → 正确写法:元组创建后不能加元素,需要变长就用列表。
  • 错误写法:忘记元组能拆包,写三行分别取 → 问题:代码啰嗦 → 正确写法:x, y, z = (1, 2, 3) 一行搞定。

记忆口诀:元组圆括号,出生定终身;想取全出来,拆包一行成。

5 遍历列表与常用方法

是什么?为什么学?

把列表里的元素「一个一个」拿出来处理,就是遍历。结合 Day 5 的 for 循环,就能处理成百上千条数据(求和、统计、筛选)。这是「容器 + 循环」的组合拳。

注意事项总结

1 遍历 + enumerate 同时拿下标 + 求和

scores = [85, 92, 78]
for score in scores:                 # 直接遍历元素
    print("成绩:", score)
for index, score in enumerate(scores):   # 还需要位置信息时用 enumerate
    print("第", index + 1, "个成绩是", score)
total = 0
for score in scores:                 # 累加器(Day 5 的升级版)
    total = total + score
print("总分:", total, "平均分:", total / len(scores))

运行结果:

成绩: 85
成绩: 92
成绩: 78
第 1 个成绩是 85
第 2 个成绩是 92
第 3 个成绩是 78
总分: 255 平均分: 85.0

只关心「每个值」用 for x in 列表;还需要「第几个」的位置信息时用 enumerate

2 常用方法速查表

方法作用
列表.sort()从小到大排序(会改变原列表)
列表.reverse()反转顺序(会改变原列表)
列表.index(值)查找值第一次出现的下标
列表.count(值)统计值出现次数
值 in 列表判断值在不在列表里(返回 True/False)
nums = [5, 2, 8, 1]
nums.sort()          # 从小到大
print(nums)          # [1, 2, 5, 8]
nums.sort(reverse=True)  # 从大到小
print(nums)          # [8, 5, 2, 1]

nums2 = [3, 7, 3, 9, 3]
print(nums2.index(7))   # 7 的下标是 1
print(nums2.count(3))   # 3 出现了 3 次
print(9 in nums2)       # True
print(5 in nums2)       # False

易错点

  • 错误写法:遍历时想改列表元素却直接 for x in 列表: x = 新值 → 问题:x 只是元素的拷贝,改了不影响原列表 → 正确写法:用下标 for i in range(len(列表)): 列表[i] = 新值
  • 错误写法:sort()sorted() 分不清 → 问题:sort() 原地改并返回 None,sorted() 返回新列表 → 正确写法:想保留原列表用 sorted(列表),想原地排用 列表.sort()

记忆口诀:遍历用 for,位置用 enumerate;排序原地 sort,统计 count 和 in。

6 动手实践:购物清单管理

做一个购物清单管理程序,支持 4 个功能:查看清单、添加物品、删除物品、退出。

# 购物清单管理
shopping = ["牛奶", "鸡蛋", "面包"]

while True:
    print("\n当前清单:", shopping)
    print("1. 查看清单  2. 添加物品  3. 删除物品  4. 退出")
    choice = input("请选择操作:")

    if choice == "1":
        if len(shopping) == 0:
            print("清单是空的")
        else:
            for i, item in enumerate(shopping):
                print(i + 1, ". ", item)

    elif choice == "2":
        item = input("要添加什么:")
        shopping.append(item)
        print("已添加:", item)

    elif choice == "3":
        item = input("要删除什么(输入物品名):")
        if item in shopping:
            shopping.remove(item)
            print("已删除:", item)
        else:
            print("清单里没有这个东西")

    elif choice == "4":
        print("再见!")
        break

    else:
        print("无效选项,请输入 1~4")

试着运行:添加「可乐」、删除「鸡蛋」、输入无效选项,观察程序的表现。这个程序把 Day 4 的 if/elif、Day 5 的 while/break、Day 6 的列表增删改查全部串起来了。

今日总结

列表是什么:内存里的「动态数组」,存的是引用所以能混装类型,按下标取值极快 下标从 0 开始,-1 是最后一个,最大下标是 len(列表) - 1 增:append(末尾)/ insert(指定位置);删:remove(按值)/ pop(按位置并返回) 改:列表[下标] = 新值append / sort / reverse 都是原地操作,返回 None 切片 列表[开始:结束:步长] 含头不含尾、返回新列表;[::-1] 反转 遍历:for x in 列表 取元素,enumerate 同时拿下标,配合累加器做统计 元组用 (),不可变、可拆包、更快;单元素元组必须带逗号 (1,) 完成购物清单管理并测试添加、删除、无效输入三种场景

报错原因修复
IndexError: list index out of range下标超出了列表长度检查下标范围,最大下标是 len(列表) - 1
ValueError: x is not in listremove / index 的值在列表里不存在先判断 if 值 in 列表 再删除或查找
TypeError: 'tuple' object does not support item assignment试图修改元组元组不可变,改用列表或重新创建元组
AttributeError: 'list' object has no attribute 'append'把变量名拼错,或对非列表调用了 append确认变量真的是列表,检查拼写
TypeError: 'int' object is not subscriptable对数字用了下标(5[0]下标只能用在列表、元组、字符串上
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇