第 03 章

控制流与数据结构

长视频第三章:用 if/elif/else 与 for 循环控制程序走向,再用列表、字典、元组、集合把多个值组织起来,并说明零索引与四种容器的取舍。

Control Flow and Data Structures

Chapter 3 of the long-form course: steer programs with if/elif/else and for loops, then organise many values with lists, dictionaries, tuples and sets, including zero-based indexing and how to choose between them.

首发于 2025.10.21中 / EN
系统课程pythoncontrol-flowlistsdictionariesdata-structures
语言
字号
目录
篇内目录

这一章在讲什么

这是长视频的第三章,对应 1:40:34–2:06:10。上一章结尾已经出现 if,本章把它讲完(elif 链与嵌套),补上「重复」这件基本动作(for 循环),然后进入数据结构:当你要一次存放多个值时,Python 提供列表、字典、元组、集合四种容器,各有明确用途。

这一章是「Python 基础」板块的收尾。作者说得很直白:这些概念此刻很抽象,不需要背下来,先建立第一次接触的印象,后面在真实项目里会反复用到。

时间轴

时间内容
1:40:34elif 链:命中即停,用于等级划分
1:42:04条件与 and/or/not 组合;嵌套 if
1:43:37循环的意义:重复执行而不必复制代码
1:44:09for 与 range:计数器、零索引
1:46:27range 的起点、终点与步长
1:47:50数据结构总览:列表、字典、元组、集合
1:49:00列表:创建、索引、切片
1:53:13列表的增删改:append、insert、remove
1:55:39字典:键值对、查找、增删
1:59:45元组:不可变
2:01:37集合:去重
2:04:05基础板块收尾与练习建议

条件语句:把分支写完整

elif 链

评分是最典型的例子。elif 从上到下依次判断,一旦某个条件成立,后面的分支全部跳过:

score = 85

if score >= 90:
    print("A")
elif score >= 80:
    print("B")
elif score >= 70:
    print("C")
else:
    print("需要改进")
# B

顺序很关键。如果把 >= 80 放在 >= 90 前面,95 分也会先命中 >= 80,得到 B。判断顺序应该从最严格到最宽松。

与逻辑运算符组合

条件本身可以是复合布尔表达式:

age = 12
has_license = True

if age >= 16 and has_license:
    print("可以开车")
else:
    print("不能开车")
# 不能开车

嵌套

一个 if 里可以再放 if,处理分层的判断:

has_ticket = True
age = 15

if has_ticket:
    if age >= 18:
        print("尽情观影")
    else:
        print("需要监护人陪同")
else:
    print("请先购票")
# 需要监护人陪同

嵌套层数不宜过深,通常两层以上就该考虑拆成函数或提前返回。

循环:让重复只写一次

需要把同一段逻辑执行五次时,复制五遍既冗长又难改。for 循环让代码只写一次:

for i in range(5):
    print(i)
# 0
# 1
# 2
# 3
# 4

拆开看:

  • for 开始一个循环;
  • i 是循环变量,名字随意,常用 i;
  • in range(5) 表示依次取 0、1、2、3、4;
  • 冒号与缩进标记循环体。

Python 从 0 开始计数(zero-indexed),所以 range(5) 给的是 0 到 4,共五个值,而不是 1 到 5。

range 的三种写法

range(5)        # 0,1,2,3,4
range(1, 6)     # 1,2,3,4,5   含起点,不含终点
range(0, 10, 2) # 0,2,4,6,8   步长为 2

「含头不含尾」是最容易出错的地方:想要 1 到 6,需要写 range(1, 7)。作者说他至今仍会在交互窗口里试两次来确认。

循环变量可用于循环体

for i in range(3):
    print(f"第 {i + 1} 次")
# 第 1 次
# 第 2 次
# 第 3 次

不需要计数器时,可以省略变量:

for _ in range(3):
    print("hello world")

数据结构:一个变量装多个值

到目前为止每个变量只存一个值。真实数据往往是一组:一页文档的段落、一条订单的多个字段、一批去重后的用户 ID。四种容器对应四种需求:

结构语法特点典型用途
列表 list[...]有序、可修改、可重复一组同类数据
字典 dict{key: value}键值对、按键查找一条记录的字段
元组 tuple(...)有序、不可修改坐标、RGB、固定配置
集合 set{...} 或 set()无序、元素唯一去重、成员判断

列表

my_list = ["Alice", 25, True]

列表可以混装不同类型,Python 不做限制。索引从 0 开始:

my_list = ["Alice", 25, True]

print(my_list[0])   # Alice
print(my_list[1])   # 25
print(my_list[-1])  # True  最后一个
print(my_list[-2])  # 25    倒数第二个

切片取一段(同样含头不含尾):

numbers = [10, 20, 30, 40, 50]
print(numbers[1:3])   # [20, 30]
print(numbers[:2])    # [10, 20]
print(numbers[2:])    # [30, 40, 50]

修改与增删:

my_list = ["Alice", 25, True]

my_list[0] = "Dave"
print(my_list)          # ['Dave', 25, True]

my_list.append("New York")   # 追加到末尾
my_list.insert(1, "engineer")  # 插入到索引 1
my_list.remove("Dave")       # 删除第一个匹配值
print(my_list)          # [25, 'engineer', 'New York']

常用方法还有 len()、sort()、reverse()、count()。作者提醒:这些方法不必背,知道「列表能做什么」即可,需要时查文档。

字典

列表里 Alice 和 25 只是位置相邻,没有语义关系。字典用键值对表达关系:

person = {
    "name": "Alice",
    "age": 25,
    "city": "New York"
}

print(person["name"])  # Alice
print(person["age"])   # 25

语法要点:创建用花括号,读取用方括号加键。键通常是字符串,也可以是数字等不可变类型。

修改、新增、删除:

person["name"] = "Dave"      # 修改
person["license"] = True     # 新增(键不存在即新增)
del person["license"]         # 删除
print(person)
# {'name': 'Dave', 'age': 25, 'city': 'New York'}

取不存在的键会抛 KeyError。更安全的读取方式:

print(person.get("email"))        # None
print(person.get("email", "无"))  # 无

查看结构的方法:

print(person.keys())    # dict_keys(['name', 'age', 'city'])
print(person.values())  # dict_values(['Dave', 25, 'New York'])
print(person.items())   # dict_items([('name', 'Dave'), ...])

选择列表还是字典的经验:需要按位置顺序遍历用列表;需要按名字查字段用字典。API 返回的 JSON 解析后就是嵌套字典,所以字典在后续章节出现频率最高。

元组

元组一旦创建就不能修改:

point = (3, 5)
color = (255, 0, 0)

print(point[0])  # 3
print(color[-1]) # 0
point[0] = 10
# TypeError: 'tuple' object does not support item assignment

为什么要有不可变的容器?当某个值在语义上不该被改动时(坐标、颜色、一次性配置),元组把这条约定写进类型,程序的其他部分就不能意外修改它。需要改动时,创建一个新元组。

集合

集合自动去除重复,判断成员也更快:

scores = [90, 85, 90, 85, 70]
unique_scores = set(scores)
print(unique_scores)  # {90, 85, 70}

fruits = {"apple", "banana", "orange", "banana"}
print(fruits)  # {'apple', 'banana', 'orange'}

注意空集合必须写 set(),{} 会被解释成空字典。

集合无序,因此不能索引;它的用途是去重与成员判断:

print("apple" in fruits)  # True

本章结论

  • elif 链从上到下判断,命中即停;顺序应从严格到宽松。
  • for i in range(n) 重复执行;range 含头不含尾,可指定起点与步长。
  • Python 索引从 0 开始,负索引从末尾数。
  • 列表:有序可改,方括号,用于一组数据。
  • 字典:键值对,花括号创建、方括号按键读取,用于带字段的记录。
  • 元组:不可变,用于不该改动的值。
  • 集合:自动去重,用于去重与成员判断。

理解检验

1. range(1, 6) 会输出哪些数?要输出 1 到 6 应该怎么写?为什么?

参考答案:range(1, 6) 产生 1、2、3、4、5,不包含终点 6。要包含 6 就写 range(1, 7)。原因是 range(start, stop) 的区间是左闭右开,长度为 stop - start;这种约定让 range(n) 恰好给出 n 个数,也让切片与循环的边界计算保持一致。

2. 给定 nums = [10, 20, 30, 40, 50],nums[-2] 和 nums[1:4] 分别是什么?

参考答案:nums[-2] 是 40,负索引从末尾计数,-1 是最后一个元素,-2 是倒数第二个。nums[1:4] 是 [20, 30, 40],切片从索引 1 取到索引 4 之前,包含 1、2、3 三个位置。

3. 下面的代码为什么输出 B 而不是 A?如何修改才能输出 A?

score = 95
if score >= 80:
    print("B")
elif score >= 90:
    print("A")

参考答案:elif 链自上而下判断,95 已经满足第一个条件 >= 80,于是打印 B 并跳过后续分支。要把最严格的判断放在前面:先写 if score >= 90: print("A"),再写 elif score >= 80: print("B")。

4. 下面两种存法,哪种更适合「按姓名查年龄」?说明理由。

people_list = [("Alice", 25), ("Dave", 30)]
people_dict = {"Alice": 25, "Dave": 30}

参考答案:字典更合适。按姓名查询时,字典可以直接 people_dict["Alice"] 得到 25,平均查找代价接近常数;列表需要遍历并逐个比较姓名,代价随人数线性增长。如果需求是按插入顺序遍历所有人,列表反而更自然。选择取决于访问方式:按位置遍历用列表,按名字查字段用字典。

5. 为什么 scores = {90, 85, 90} 打印出来只有两个元素?这会带来什么限制?

参考答案:花括号且不含键值对时创建的是集合,集合自动去除重复元素,所以 90 只保留一个。限制是集合无序:不能通过索引取值,也不能依赖遍历顺序;此外元素必须是不可变类型(数字、字符串、元组),列表不能放进集合。

与前后章的关系

到这里「Python 基础」板块结束:值、变量、运算、分支、循环、容器都已具备。下一章把这些零件组装起来——用函数封装可复用的逻辑,理解参数、返回值与作用域,然后接触模块与外部库。从下一章起,代码开始像真正的程序,而不是零散的单行练习。

What this chapter covers

This is chapter 3 of the long-form video, covering 1:40:34–2:06:10. The previous chapter ended with a first if; this one finishes branching (elif chains and nesting), adds the "repeat" action (for loops), and then moves to data structures: when a variable must hold many values, Python offers lists, dictionaries, tuples and sets, each with a clear purpose.

This closes the "Python basics" section. The instructor is candid: the material is abstract at first and does not need memorising — the point is a first exposure that later project work will keep reinforcing.

Timeline

TimeTopic
1:40:34elif chains: stop at the first match, used for grading
1:42:04Conditions combined with and/or/not; nested if
1:43:37Why loops exist: repeat without copying code
1:44:09for and range: the counter, zero-based indexing
1:46:27range: start, stop and step
1:47:50Overview of containers: list, dict, tuple, set
1:49:00Lists: creation, indexing, slicing
1:53:13Changing lists: append, insert, remove
1:55:39Dictionaries: key-value pairs, lookup, insert, delete
1:59:45Tuples: immutable
2:01:37Sets: removing duplicates
2:04:05Wrapping up the basics section and how to practise

Conditionals: writing every branch

elif chains

Grading is the classic example. elif is evaluated top to bottom, and once a condition holds, every later branch is skipped:

score = 85

if score >= 90:
    print("A")
elif score >= 80:
    print("B")
elif score >= 70:
    print("C")
else:
    print("needs improvement")
# B

Order matters. Put >= 80 before >= 90 and a score of 95 matches the first branch and gets a B. Conditions should run from strictest to loosest.

Combining with logical operators

A condition can be any boolean expression:

age = 12
has_license = True

if age >= 16 and has_license:
    print("can drive")
else:
    print("cannot drive")
# cannot drive

Nesting

An if can contain another if for layered decisions:

has_ticket = True
age = 15

if has_ticket:
    if age >= 18:
        print("enjoy the movie")
    else:
        print("needs supervision")
else:
    print("please buy a ticket")
# needs supervision

Deep nesting gets hard to read; beyond two levels, consider a function or an early return.

Loops: write the repetition once

Running the same logic five times by copying it is verbose and hard to change. A for loop writes it once:

for i in range(5):
    print(i)
# 0
# 1
# 2
# 3
# 4

Breaking it down:

  • for starts the loop;
  • i is the loop variable, any name works, i is conventional;
  • in range(5) yields 0, 1, 2, 3, 4 in turn;
  • the colon and indentation mark the body.

Python counts from zero (zero-indexed), so range(5) gives 0 through 4 — five values, not 1 through 5.

Three forms of range

range(5)        # 0,1,2,3,4
range(1, 6)     # 1,2,3,4,5   includes start, excludes stop
range(0, 10, 2) # 0,2,4,6,8   step of 2

"Inclusive start, exclusive stop" is the usual mistake: to get 1 through 6, write range(1, 7). The instructor admits he still checks twice in the interactive window.

The loop variable is available in the body

for i in range(3):
    print(f"round {i + 1}")
# round 1
# round 2
# round 3

When the counter is unused, an underscore signals that:

for _ in range(3):
    print("hello world")

Data structures: many values in one variable

So far each variable held one value. Real data comes in groups: the paragraphs of a document, the fields of an order, a batch of de-duplicated user IDs. Four containers cover four needs:

StructureSyntaxPropertiesTypical use
list[...]ordered, mutable, duplicates alloweda collection of similar items
dict{key: value}key-value pairs, lookup by keythe fields of one record
tuple(...)ordered, immutablecoordinates, RGB, fixed config
set{...} or set()unordered, unique elementsde-duplication, membership

Lists

my_list = ["Alice", 25, True]

Lists can mix types; Python does not restrict them. Indexing starts at 0:

my_list = ["Alice", 25, True]

print(my_list[0])   # Alice
print(my_list[1])   # 25
print(my_list[-1])  # True   last
print(my_list[-2])  # 25     second from the end

Slicing takes a range (again inclusive start, exclusive stop):

numbers = [10, 20, 30, 40, 50]
print(numbers[1:3])   # [20, 30]
print(numbers[:2])    # [10, 20]
print(numbers[2:])    # [30, 40, 50]

Modifying and resizing:

my_list = ["Alice", 25, True]

my_list[0] = "Dave"
print(my_list)                 # ['Dave', 25, True]

my_list.append("New York")     # append to the end
my_list.insert(1, "engineer")  # insert at index 1
my_list.remove("Dave")         # remove first matching value
print(my_list)                 # [25, 'engineer', 'New York']

Other methods include sort(), reverse() and count(). The instructor's advice: do not memorise them; know what a list can do and look up the rest.

Dictionaries

In a list, Alice and 25 are merely adjacent; they carry no relationship. A dictionary expresses relationships through keys:

person = {
    "name": "Alice",
    "age": 25,
    "city": "New York"
}

print(person["name"])  # Alice
print(person["age"])   # 25

The rule: create with braces, read with square brackets and a key. Keys are usually strings, but any immutable type works.

Update, insert, delete:

person["name"] = "Dave"      # update
person["license"] = True     # insert (missing key creates it)
del person["license"]         # delete
print(person)
# {'name': 'Dave', 'age': 25, 'city': 'New York'}

Reading a missing key raises KeyError; the safe form is:

print(person.get("email"))        # None
print(person.get("email", "n/a")) # n/a

Inspecting structure:

print(person.keys())    # dict_keys(['name', 'age', 'city'])
print(person.values())  # dict_values(['Dave', 25, 'New York'])
print(person.items())   # dict_items([('name', 'Dave'), ...])

How to choose: lists when position and order matter; dictionaries when fields are looked up by name. JSON from an API parses into nested dictionaries, which is why dictionaries dominate the later chapters.

Tuples

A tuple cannot change after creation:

point = (3, 5)
color = (255, 0, 0)

print(point[0])  # 3
print(color[-1]) # 0
point[0] = 10
# TypeError: 'tuple' object does not support item assignment

Why have an immutable container? When a value should never change by design (a coordinate, a colour, a fixed setting), a tuple writes that rule into the type so no other part of the program can alter it accidentally. To change it, build a new tuple.

Sets

Sets remove duplicates automatically and make membership tests fast:

scores = [90, 85, 90, 85, 70]
unique_scores = set(scores)
print(unique_scores)  # {90, 85, 70}

fruits = {"apple", "banana", "orange", "banana"}
print(fruits)  # {'apple', 'banana', 'orange'}

An empty set must be set(); {} is an empty dictionary.

Sets are unordered and cannot be indexed; their job is de-duplication and membership:

print("apple" in fruits)  # True

Takeaways

  • elif chains evaluate top to bottom and stop at the first match; order them strictest first.
  • for i in range(n) repeats work; range excludes the stop value and accepts a step.
  • Indexing starts at 0; negative indices count from the end.
  • Lists: ordered and mutable, square brackets, for collections.
  • Dictionaries: key-value pairs, braces to create, brackets to read by key.
  • Tuples: immutable, for values that should not change.
  • Sets: unique elements, for de-duplication and membership.

Check your understanding

1. Which numbers does range(1, 6) produce? How do you get 1 through 6, and why?

Reference answer: range(1, 6) yields 1, 2, 3, 4, 5 — the stop value is excluded. To include 6, write range(1, 7). range(start, stop) is a half-open interval with length stop - start; that convention makes range(n) produce exactly n values and keeps slice and loop boundaries consistent.

2. Given nums = [10, 20, 30, 40, 50], what are nums[-2] and nums[1:4]?

Reference answer: nums[-2] is 40, since negative indices count from the end (-1 is last, -2 is second to last). nums[1:4] is [20, 30, 40]: positions 1, 2 and 3, stopping before index 4.

3. Why does this print B rather than A? How do you make it print A?

score = 95
if score >= 80:
    print("B")
elif score >= 90:
    print("A")

Reference answer: An elif chain evaluates top to bottom; 95 already satisfies >= 80, so B prints and later branches are skipped. Put the strictest test first: if score >= 90: print("A") followed by elif score >= 80: print("B").

4. Which storage suits "look up an age by name", and why?

people_list = [("Alice", 25), ("Dave", 30)]
people_dict = {"Alice": 25, "Dave": 30}

Reference answer: The dictionary. people_dict["Alice"] returns 25 directly, with roughly constant-time lookup; the list requires scanning and comparing names, which grows linearly. If the requirement were to iterate over everyone in insertion order, the list would be more natural. The access pattern decides: position-based iteration favours lists, name-based lookup favours dictionaries.

5. Why does scores = {90, 85, 90} print only two elements, and what limitation does that imply?

Reference answer: Braces without key-value pairs create a set, and sets discard duplicates, so 90 appears once. The limitation is that sets are unordered: no indexing, and iteration order is not something to rely on. Elements must also be immutable (numbers, strings, tuples), so a list cannot go into a set.

How it connects

The "Python basics" section ends here: values, variables, operators, branching, loops and containers are all in place. The next chapter assembles the parts — functions that package reusable logic, with parameters, return values and scope — then modules and external libraries. From there the code starts to look like a real program rather than isolated one-liners.

来源