Day 04:循环与列表

🎯 学习目标:精通 for 循环与列表操作,理解序列概念,掌握列表推导式

1. 作业讲解

回顾 Day 03 的猜数字游戏,常见问题和优化:

 
# guess = input("猜数字:")  # ❌ guess 是 str,不能和 int 比较
 
guess = int(input("猜数字:"))  # ✅ 先转换
 
# 问题 2:空输入没处理
 
user_input = input("猜数字(输入 q 退出):")
 
if user_input.lower() == "q":
 
    break
 
guess = int(user_input)
 
# 优化:使用 try-except 处理非法输入(预习 Day 10)
 
try:
 
    guess = int(input("猜数字:"))
 
except ValueError:
 
    print("请输入有效数字!")
 
    continue
 

2. for 循环

2.1 基本语法

 
# for 循环:遍历可迭代对象中的每一个元素
 
for item in [1, 2, 3, 4, 5]:
 
    print(item)
 
# 遍历字符串(每个字符)
 
for ch in "Python":
 
    print(ch)
 
# 遍历字典(默认遍历 key)
 
d = {"name": "张三", "age": 20}
 
for key in d:
 
    print(key, d[key])
 

2.2 for 的底层原理

 
# for item in obj 等价于:
 
iterator = iter(obj)     # 获取迭代器
 
while True:
 
    try:
 
        item = next(iterator)  # 获取下一个元素
 
        # 执行循环体
 
    except StopIteration:
 
        break            # 没有更多元素,退出
 

3. range 函数

range 是 for 循环的最佳搭档:

 
# range(stop):从 0 到 stop-1
 
for i in range(5):
 
    print(i, end=" ")     # 0 1 2 3 4
 
# range(start, stop):从 start 到 stop-1
 
for i in range(2, 6):
 
    print(i, end=" ")     # 2 3 4 5
 
# range(start, stop, step):步长为 step
 
for i in range(0, 10, 2):
 
    print(i, end=" ")     # 0 2 4 6 8
 
# 负数步长:倒序
 
for i in range(10, 0, -1):
 
    print(i, end=" ")     # 10 9 8 7 6 5 4 3 2 1
 
# range 是惰性的(不会一次性生成所有数)
 
r = range(1000000000)
 
print(r[999999999])       # 可以直接按索引取,不占内存
 

range 不是列表

range(5) 返回的是一个 range 对象,它只在需要时生成数字,内存占用极小。

4. 循环嵌套

4.1 基本嵌套

 
# 九九乘法表
 
for i in range(1, 10):
 
    for j in range(1, i + 1):
 
        print(f"{j}×{i}={i*j}", end="\t")
 
    print()  # 换行
 

输出:

 
1×1=1
 
1×2=2  2×2=4
 
1×3=3  2×3=6  3×3=9
 
...
 

4.2 嵌套循环的执行过程

 
# 外层循环每执行 1 次,内层循环执行完整的 N 次
 
for i in range(3):          # 外循环 3 次
 
    for j in range(2):      # 内循环 2 次
 
        print(f"({i},{j})", end=" ")
 
    print()
 
# 总执行次数:3 × 2 = 6
 

5. continue、break、pass、else

5.1 break—终止循环

 
# break 立即退出整个循环
 
for i in range(10):
 
    if i == 5:
 
        break          # 到 5 就停止
 
    print(i)
 
# 输出:0 1 2 3 4
 

5.2 continue—跳过本次

 
# continue 跳过当前迭代,继续下一次
 
for i in range(5):
 
    if i == 2:
 
        continue       # 跳过 i=2 的情况
 
    print(i)
 
# 输出:0 1 3 4
 

5.3 pass—占位符

 
# pass 什么都不做,用于占位
 
def not_implemented_yet():
 
    pass               # 以后再来实现
 
if condition:
 
    pass               # 暂时还没想好写什么
 
#                       # ❌ 缩进块不能为空!
 

5.4 for-else

 
# 经典用法:查找元素
 
items = [1, 3, 5, 7, 9]
 
target = 4
 
for item in items:
 
    if item == target:
 
        print("找到了!")
 
        break
 
else:
 
    print(f"{target} 不在列表中")   # ← 找不到时执行
 
# 如果找不到(正常结束),else 执行
 

6. 序列介绍

列表(List)是 Python 中最重要的数据结构之一,属于序列类型。

序列的共同特性

 
# 1. 索引访问
 
s = "Python"
 
print(s[0])       # 'P'
 
print(s[-1])      # 'n'(负数索引从末尾开始)
 
# 3. 长度
 
print(len(s))     # 6
 
# 4. 成员检查
 
print("th" in s)  # True
 

7. 列表的创建与切片

7.1 创建列表

 
# 多种创建方式
 
empty = []                      # 空列表
 
nums = [1, 2, 3, 4, 5]        # 整数列表
 
mixed = [1, "hello", 3.14, True]  # 混合类型(Python 允许但不推荐)
 
nested = [[1, 2], [3, 4]]     # 嵌套列表
 
# 用 list() 构造
 
chars = list("Python")         # ['P', 'y', 't', 'h', 'o', 'n']
 
range_list = list(range(5))   # [0, 1, 2, 3, 4]
 
# 列表乘法(重复)
 
zeros = [0] * 5                # [0, 0, 0, 0, 0]
 
# ⚠️ 乘法陷阱:嵌套列表是浅拷贝!
 
matrix = [[0] * 3] * 3
 
matrix[0][0] = 99
 
print(matrix)  # [[99, 0, 0], [99, 0, 0], [99, 0, 0]]
 
# 三行都变了!因为它们引用的是同一个子列表
 

7.2 索引与切片

 
nums = [10, 20, 30, 40, 50, 60, 70, 80]
 
# 正向索引:从 0 开始
 
print(nums[0])     # 10
 
print(nums[3])     # 40
 
# 负向索引:从 -1 开始(最后一个)
 
print(nums[-1])    # 80
 
print(nums[-3])    # 60
 
# start 默认 0,stop 默认末尾,step 默认 1
 
print(nums[0:3])    # [10, 20, 30](索引 0,1,2)
 
print(nums[:3])     # [10, 20, 30](同上,start 默认 0)
 
print(nums[3:])     # [40, 50, 60, 70, 80](从索引 3 到末尾)
 
print(nums[::2])    # [10, 30, 50, 70](步长 2,隔一个取一个)
 
print(nums[::-1])   # [80, 70, 60, 50, 40, 30, 20, 10](倒序!)
 
# 切片不会越界
 
print(nums[2:100])  # [30, 40, 50, 60, 70, 80](自动截断)
 

切片口诀

“包左不包右”[start:stop] 包含 start 位置的元素,不包含 stop 位置的元素。

元素个数 = stop - start

8. 列表常用方法

8.1 增加元素

 
fruits = ["苹果", "香蕉"]
 
# append:在末尾添加一个元素
 
fruits.append("橘子")        # ['苹果', '香蕉', '橘子']
 
# insert:在指定位置插入
 
fruits.insert(1, "葡萄")     # ['苹果', '葡萄', '香蕉', '橘子']
 
# extend:在末尾添加另一个列表的所有元素
 
fruits.extend(["西瓜", "草莓"])  # ['苹果', '葡萄', '香蕉', '橘子', '西瓜', '草莓']
 
# append vs extend
 
fruits.append(["芒果", "榴莲"])   # 把整个列表当一个元素加进去
 
# extend 则会把每个元素拆开加进去
 

8.2 删除元素

 
nums = [1, 2, 3, 4, 5, 3]
 
# pop():删除并返回最后一个元素(默认)
 
last = nums.pop()        # last=5, nums=[1,2,3,4,3]
 
# pop(index):删除并返回指定索引的元素
 
second = nums.pop(1)     # second=2, nums=[1,3,4,3]
 
# remove(value):删除第一个匹配的值
 
nums.remove(3)           # nums=[1,4,3](只删了第一个 3)
 
# del:按索引删除
 
del nums[1]              # nums=[1,3]
 
# clear:清空列表
 
nums.clear()             # nums=[]
 

8.3 修改和查找

 
nums = [10, 20, 30, 20, 40]
 
# 修改
 
nums[0] = 100            # [100, 20, 30, 20, 40]
 
# index(value):查找第一次出现的索引
 
print(nums.index(20))    # 1
 
# count(value):统计出现次数
 
print(nums.count(20))    # 2
 
# len():获取长度
 
print(len(nums))         # 5
 

8.4 排序和反转

 
nums = [3, 1, 4, 1, 5, 9, 2]
 
# sort():原地排序(修改原列表)
 
nums.sort()                   # [1, 1, 2, 3, 4, 5, 9]
 
nums.sort(reverse=True)       # [9, 5, 4, 3, 2, 1, 1] 降序
 
# sorted():返回新排序列表(不修改原列表)
 
original = [3, 1, 4]
 
new = sorted(original)        # [1, 3, 4]
 
print(original)               # [3, 1, 4](原列表不变)
 
# reverse():原地反转
 
nums.reverse()                # 反转列表
 
# 自定义排序(按 key 函数)
 
words = ["apple", "kiwi", "banana", "pear"]
 
words.sort(key=len)           # 按长度排序:['kiwi', 'pear', 'apple', 'banana']
 
words.sort(key=lambda w: w[-1]) # 按最后一个字母排序
 

8.5 列表遍历

 
nums = [10, 20, 30, 40, 50]
 
# 方式 1:直接遍历值
 
for num in nums:
 
    print(num)
 
# 方式 2:遍历索引+值(推荐 enumerate)
 
for index, value in enumerate(nums):
 
    print(f"nums[{index}] = {value}")
 
# enumerate 可以指定起始索引
 
for index, value in enumerate(nums, start=1):
 
    print(f"第 {index} 个元素是 {value}")
 

9. 列表推导式 ⭐

列表推导式是 Python 最具特色的语法之一,一行搞定”过滤+映射”:

9.1 基本推导式

 
# 传统写法:
 
squares = []
 
for i in range(10):
 
    squares.append(i ** 2)
 
# 推导式写法:
 
squares = [i ** 2 for i in range(10)]
 
# 结果:[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
 

9.2 带过滤条件的推导式

 
# 任务:0~19 中的所有偶数
 
evens = [i for i in range(20) if i % 2 == 0]
 
# 等价于:
 
evens = []
 
for i in range(20):
 
    if i % 2 == 0:
 
        evens.append(i)
 

9.3 带 if-else 的推导式

 
# 任务:偶数保留,奇数变为 "odd"
 
result = [i if i % 2 == 0 else "odd" for i in range(6)]
 
#       if-else 在 for 前面 = 映射变换
 

9.4 嵌套推导式

 
# 展开二维列表
 
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
 
flat = [num for row in matrix for num in row]
 
# 等价于:
 
flat = []
 
for row in matrix:
 
    for num in row:
 
        flat.append(num)
 

📝 综合练习:成绩统计分析

 
"""
 
成绩统计分析系统
 
"""
 
# 生成测试数据:20 个随机成绩
 
import random
 
scores = [random.randint(40, 100) for _ in range(20)]
 
print(f"原始成绩:{scores}")
 
# 1. 排序
 
sorted_scores = sorted(scores, reverse=True)
 
print(f"排序后(降序):{sorted_scores}")
 
# 2. 统计
 
total = sum(scores)
 
average = total / len(scores)
 
highest = max(scores)
 
lowest = min(scores)
 
print(f"\n人数:{len(scores)}")
 
print(f"总分:{total}")
 
print(f"平均分:{average:.1f}")
 
print(f"最高分:{highest}")
 
print(f"最低分:{lowest}")
 
# 3. 等级分布(推导式 + 条件)
 
excellent = [s for s in scores if s >= 90]
 
good = [s for s in scores if 80 <= s < 90]
 
medium = [s for s in scores if 70 <= s < 80]
 
passed = [s for s in scores if 60 <= s < 70]
 
failed = [s for s in scores if s < 60]
 
print(f"\n优秀(≥90):{len(excellent)} 人 → {excellent}")
 
print(f"良好(80-89):{len(good)} 人 → {good}")
 
print(f"中等(70-79):{len(medium)} 人 → {medium}")
 
print(f"及格(60-69):{len(passed)} 人 → {passed}")
 
print(f"不及格(<60):{len(failed)} 人 → {failed}")
 
# 4. 计算及格率
 
pass_rate = (len(scores) - len(failed)) / len(scores) * 100
 
print(f"\n及格率:{pass_rate:.1f}%")
 

速记卡(面试闪卡)

Q1:一句话讲清「Day 04:循环与列表」到底是什么?

A:这一讲讲透 for 循环与列表:遍历靠迭代器、range 惰性生成、列表切片包左不包右、推导式一行过滤+映射。

Q2:2-3. for 循环与 range 函数 —— 怎么理解?

A:for 循环本质是调迭代器:for x in obj 等价于 iter(obj) 拿迭代器、反复 next() 直到 StopIteration。range 是 for 最佳搭档,返回惰性 range 对象(只在需要时生成数字、按索引取也不占内存),支持 start/stop/step,负 step 可倒序。

Q3:5. break / continue / pass / for-else —— 怎么理解?

A:break 立即退出整个循环;continue 跳过本次继续下一轮;pass 占位(缩进块不能为空);for-else 的 else 在循环没被 break 中断时才执行——经典用来”查找元素,找不到就提示”,找到(break)就不走 else。

Q4:7. 列表创建、索引与切片 —— 怎么理解?

A:列表用 []/list() 创建,乘法 [0]*3 重复但嵌套列表是浅拷贝陷阱([[0]*3]*3 改一个全变)。索引从 0、负索引从 -1;切片 [start:stop:step] 包左不包右、元素数=stop-start,[::-1] 倒序、越界自动截断。append 加元素、extend 拆开加、pop/remove/del 删。

Q5:9. 列表推导式与常见坑 —— 怎么理解?

A:推导式一行搞定”过滤+映射”:[i**2 for i in range(10)]、带 if 过滤、if-else 在 for 前是映射。坑点:① for 循环里删元素会索引错位(用推导式或倒序);② 推导式循环变量覆盖外部变量;③ 列表作默认参数共享同一对象(用 None);④ range 越界 IndexError;⑤ sort 原地返 None、sorted 返新列表。

Q6:核心速记主线有哪些?

  • for 循环=迭代器:iter() 取、next() 走、StopIteration 退

  • range 惰性对象:start/stop/step,负步长倒序,不占内存

  • 控制:break 退、continue 跳、pass 占位、for-else 未 break 才执行

  • 切片包左不包右;嵌套列表乘法是浅拷贝陷阱

  • 推导式一行过滤+映射;坑:删元素错位、默认参数共享、sort vs sorted

口诀

A:for 循环迭代器,next 走到头便退

range 惰性不占内存,负步倒着追

break 退 continue 跳,for-else 未断才相随

切片包左不包右,推导式一行美

相关链接


for 循环执行流程图


graph TD

    A[for循环开始] --> B["获取可迭代对象迭代器<br/>iter(obj)"]

    B --> C["调用next获取下一个元素<br/>next(iterator)"]

    C --> D{"是否有元素?"}

    D -->|是| E[执行循环体]

    E --> C

    D -->|否| F[捕获StopIteration异常]

    F --> G[循环结束]

    style A fill:#e1f5fe

    style F fill:#f3e5f5

    style G fill:#e8f5e8

列表操作时间复杂度


graph LR

    A[列表操作] --> B[索引访问 O1]

    A --> C[尾部追加 O1]

    A --> D[任意位置插入 On]

    A --> E[删除元素 On]

    A --> F[查找元素 On]

    A --> G[切片 O-k]

    B --> B1[arr5]

    C --> C1[append]

    D --> D1[insert0,x]

    E --> E1[remove或pop]

    F --> F1[x in arr]

    G --> G1[arr1:4]

常见坑点

1. for 循环中修改列表

 
arr = [1, 2, 3, 4, 5]
 
for item in arr:
 
    if item % 2 == 0:
 
        arr.remove(item)  # ❌ 删除元素后索引错位
 
print(arr)  # [1, 3, 5]?实际可能是 [1, 3, 4]
 
  • 原理:删除元素后,后续元素前移,但迭代器索引继续递增

  • 解决:使用列表推导式或倒序遍历

 
arr = [x for x in arr if x % 2 != 0]  # ✅ 推导式
 
# 或
 
for item in reversed(arr):  # ✅ 倒序
 
    if item % 2 == 0:
 
        arr.remove(item)
 

2. 列表推导式副作用

 
x = 10
 
result = [x for x in range(5)]  # [0, 1, 2, 3, 4]
 
print(x)  # 4 ← 外部x被修改!
 
  • 原理:推导式的循环变量会覆盖外部变量

  • 解决:使用不同变量名或临时变量

 
result = [i for i in range(5)]  # 用i代替x
 

3. range 索引越界

 
arr = [10, 20, 30]
 
for i in range(len(arr) + 1):  # ❌ range(4) 但只有3个元素
 
    print(arr[i])  # IndexError: list index out of range
 
  • 正确range(len(arr)) 或直接遍历 for item in arr

4. 列表复制陷阱

 
a = [1, 2, 3]
 
b = a          # ❌ 引用赋值,指向同一对象
 
b.append(4)
 
print(a)       # [1, 2, 3, 4] ← a也被修改
 
c = a.copy()   # ✅ 浅拷贝
 
d = a[:]       # ✅ 切片拷贝
 
import copy
 
e = copy.deepcopy(a)  # ✅ 深拷贝(嵌套列表)
 

5. 列表作为默认参数

 
def add_item(item, lst=[]):  # ❌ 默认参数是列表
 
    lst.append(item)
 
    return lst
 
print(add_item(1))  # [1]
 
print(add_item(2))  # [1, 2] ← 不是[2]!
 
  • 原理:默认参数在函数定义时创建,后续调用共享同一对象

  • 解决:使用 None 作为默认值

 
def add_item(item, lst=None):
 
    if lst is None:
 
        lst = []
 
    lst.append(item)
 
    return lst
 

6. 列表排序 vs sorted

 
arr = [3, 1, 4, 1, 5]
 
arr.sort()      # 原地排序,返回None
 
print(arr)      # [1, 1, 3, 4, 5]
 
arr2 = sorted(arr)  # 返回新列表,原列表不变
 
print(arr2)     # [1, 1, 3, 4, 5]
 
  • 选择:需要保留原列表用 sorted(),否则用 .sort() 更高效

技术学习路线图 > 语法核心