12 周课程 / 第 2 周 10 分钟阅读

第 2 周:循环、函数、随机数与对象

本周开始把“重复执行”和“重复使用”分开处理:循环负责重复步骤,函数负责封装一个可以反复调用的完整任务,对象负责把同一实体的数据和行为放在一起。

建议用时:8-10 小时。

本周目标#

学完本周,你应该能够:

  • 根据执行次数选择 for 或 while。
  • 正确写出 range() 的起点、终点和步长。
  • 用变量跟踪表推演循环每一轮的状态。
  • 使用 break、continue 和嵌套循环。
  • 定义函数,传入参数并接收返回值。
  • 区分 print() 与 return,解释函数返回 None 的情况。
  • 使用 random 生成可测试的随机结果。
  • 用类和对象保存一个学生、一本书或一个游戏角色的数据。

2.1 为什么需要循环#

输出 1 到 5 可以直接写五行:

print(1)
print(2)
print(3)
print(4)
print(5)

但输出 1 到 1000 时,重复写代码显然不可取。循环可以把“变化的部分”统一成规律:

for number in range(1, 6):
    print(number)

结果:

1
2
3
4
5

循环代码要回答三个问题:

  1. 循环变量从哪个值开始?
  2. 每轮循环变量怎样变化?
  3. 什么时候停止?

2.2 for 与 range()#

for 适合“数据或次数大致已知”的重复。

最常用的三种 range()#

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

for i in range(2, 6):
    print(i)
# 2 3 4 5

for i in range(1, 10, 3):
    print(i)
# 1 4 7

规则:

  • range(stop):从 0 开始,不包含 stop。
  • range(start, stop):从 start 开始,不包含 stop。
  • range(start, stop, step):每次增加 step,仍然不包含 stop。
  • step 可以是负数,但起点要大于终点。
for i in range(5, 0, -1):
    print(i)
# 5 4 3 2 1

循环变量只在下一轮重新赋值#

total = 0
for i in range(1, 5):
    total += i
    print("i =", i, "total =", total)

变量跟踪表:

轮次 i 本轮后 total 输出
1 1 1 i = 1 total = 1
2 2 3 i = 2 total = 3
3 3 6 i = 3 total = 6
4 4 10 i = 4 total = 10

循环结束后,i 保留最后一轮的值 4。total 保留累积结果 10。

循环做多少次#

range 值序列 次数
range(4) 0, 1, 2, 3 4
range(1, 4) 1, 2, 3 3
range(2, 11, 2) 2, 4, 6, 8, 10 5
range(5, 0, -1) 5, 4, 3, 2, 1 5
range(3, 3) 无 0

手推时不要凭“感觉循环几次”,直接写下序列。

2.3 while#

while 适合“不知道要重复几次,只要条件成立就继续”的情况。

number = 1
total = 0

while number <= 5:
    total += number
    number += 1

print(total)  # 15

循环条件在每一轮开始时检查。因为循环体最后让 number 增加,所以它最终会超过 5。

while 的三个必备部分#

number = 10          # 1. 初始化

while number > 0:    # 2. 条件
    print(number)
    number -= 1      # 3. 更新

忘记更新可能造成无限循环:

number = 1
while number <= 3:
    print(number)
    # 缺少 number += 1

程序会一直输出 1,直到被手动停止。排错时先检查循环变量是否在循环体内部被修改。

先执行后判断#

有些交互适合先执行一次,再决定是否继续:

command = ""

while command != "退出":
    command = input("请输入命令:")
    print("收到:", command)

输入“退出”的那一轮仍会执行后面的 print,因为条件是在这一轮开始前检查的。如果需要输入后立即停止,可以使用 break。

2.4 break 与 continue#

break 立即结束整个循环:

for number in range(1, 10):
    if number == 4:
        break
    print(number)
# 1 2 3

continue 立即结束本轮,进入下一轮:

total = 0
for number in range(1, 6):
    if number % 2 == 0:
        continue
    total += number

print(total)  # 9

这里只累加 1、3、5。continue 之后的语句在本轮不会执行。

用 while True 和 break 组织菜单#

while True:
    choice = input("1 继续,2 退出:")
    if choice == "2":
        break
    print("继续练习")

print("程序结束")

while True 不是无条件永远运行。只要每条路径最终都有机会执行 break,它就是一种清晰的循环写法。

2.5 嵌套循环#

嵌套循环就是循环体内还有循环。外层每执行一轮,内层会完整执行一遍。

for row in range(1, 4):
    for column in range(1, 4):
        print(row, column)

执行顺序:

1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3

外层共 3 轮,内层每轮 3 次,所以内层语句总共执行 3 * 3 = 9 次。

乘法表#

for row in range(1, 4):
    for column in range(1, 4):
        print(f"{row}x{column}={row * column}", end="  ")
    print()

结果:

1x1=1  1x2=2  1x3=3  
2x1=2  2x2=4  2x3=6  
3x1=3  3x2=6  3x3=9  

print() 在外层循环中结束一行,end=" " 在内层循环中让同一行的内容不换行。

星号三角形#

for row in range(1, 5):
    print("*" * row)

结果:

*
**
***
****

嵌套循环常见错误是缩进。确定某个 print 属于内层还是外层,要通过它的缩进层级判断。

2.6 函数:给一段逻辑起名字#

函数把一段会重复使用的逻辑封装起来。

def show_welcome(name):
    print("欢迎", name)
    print("开始学习 Python")

show_welcome("小林")
show_welcome("李雨")

结构:

def 函数名(参数):
    函数体

关键规则:

  • def 表示定义函数,定义时函数体不会执行。
  • 调用 show_welcome(...) 时才执行。
  • 参数接收调用时传入的值。
  • 函数体统一缩进 4 个空格。
  • 函数名后需要圆括号和冒号。

参数与实参#

def add(a, b):
    result = a + b
    print(result)

add(3, 5)
add(10, 20)

定义中的 a、b 是形参,调用中的 3、5 是实参。默认按位置对应。

return 返回值#

仅仅打印结果的函数不能方便地被其他计算继续使用。返回值让函数把结果交还给调用者:

def add(a, b):
    return a + b

result = add(3, 5)
print(result * 2)  # 16

比较:

def print_sum(a, b):
    print(a + b)

def return_sum(a, b):
    return a + b

x = print_sum(2, 3)   # 输出 5,同时 x 得到 None
y = return_sum(2, 3)  # 不输出,y 得到 5

print() 是向屏幕显示内容,return 是把结果交给调用者。两者不是一回事。

return 会立即结束函数#

def check_score(score):
    if score < 0 or score > 100:
        return "成绩无效"
    if score >= 60:
        return "及格"
    return "不及格"

当第一个 return 执行时,后面的代码不再执行。这种“提前返回”可以减少嵌套。

如果函数没有执行任何 return,调用结果就是 None:

def show_message():
    print("你好")

value = show_message()
print(value)  # None

2.7 参数规则与变量作用域#

参数数量必须匹配#

def rectangle_area(length, width):
    return length * width

print(rectangle_area(4, 3))  # 12

只传一个参数会报 TypeError,因为它缺少 width;传三个参数也会报错。

默认参数#

def greet(name, greeting="你好"):
    return f"{greeting},{name}"

print(greet("小林"))            # 你好,小林
print(greet("小林", "早上好"))  # 早上好,小林

带默认值的参数一般放在不带默认值的参数后面。不要在基础练习中使用可变对象作为默认参数,例如 [] 或 {}。

局部变量#

函数中创建的变量默认只属于这个函数:

def calculate():
    local_value = 10
    return local_value

print(calculate())  # 10
# print(local_value)  # NameError

函数外部无法直接使用 local_value。需要把结果通过 return 带回外部。

同样是 result 这个名称,在不同函数中可以表示不同变量。这种隔离让函数更容易单独测试。

2.8 生成随机数#

import random

number = random.randint(1, 6)
print(number)

randint(a, b) 的结果包含 a 和 b,所以 randint(1, 6) 可能是 1、2、3、4、5、6。

常用函数:

写法 作用 示例结果范围
random.randint(1, 6) 生成闭区间整数 1-6
random.randrange(1, 7) 和 range 一样不含终点 1-6
random.choice(["A", "B", "C"]) 随机选一个元素 三个元素之一
random.shuffle(items) 原地打乱列表 原列表被修改

随机程序难以只靠肉眼复现时,可以固定随机种子:

import random

random.seed(2026)
print(random.randint(1, 100))
print(random.randint(1, 100))

相同 Python 环境和相同种子通常会得到相同序列,便于排查问题。正式比赛如果要求随机结果,不要为了“稳定输出”擅自固定种子,除非题目允许。

2.9 类与对象#

类描述一类事物“有哪些数据、能做什么”。对象是由类创建出来的具体实例。

以学生为例:

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def is_passed(self):
        return self.score >= 60


student1 = Student("小林", 86)
student2 = Student("李雨", 52)

print(student1.name)          # 小林
print(student1.score)         # 86
print(student1.is_passed())   # True
print(student2.is_passed())   # False

逐行理解#

  • class Student: 定义一个类。
  • __init__ 是初始化方法,创建对象时自动执行。
  • self 表示当前这个对象。
  • self.name = name 把传入的姓名保存到对象自己的属性中。
  • student1 = Student(...) 创建对象并自动调用 __init__。
  • student1.name 读取该对象的属性。
  • student1.is_passed() 调用该对象的方法。

两个对象各自保存自己的数据:

student1.score = 95
print(student1.score)  # 95
print(student2.score)  # 52

修改一个对象的属性,不会自动改变另一个对象。

常见错误#

class Book:
    def __init__(self, title):
        self.title = title

book = Book("Python")
print(book.title)  # Python
  • 定义 __init__ 时忘记 self,创建对象时会报参数数量错误。
  • 方法内部访问属性时把 self.title 写成局部变量 title。
  • 把类名 Book 当成对象属性,或误以为 print(book) 会自动输出书名。

2.10 本周作品一:猜数字#

需求#

  • 答案在 1-100 之间。
  • 每次输入一个整数。
  • 猜小、猜大、猜对分别给出提示。
  • 猜对后输出尝试次数并结束。
import random

answer = random.randint(1, 100)
attempts = 0

while True:
    guess = int(input("请输入 1-100 的整数:"))
    attempts += 1

    if guess < answer:
        print("猜小了")
    elif guess > answer:
        print("猜大了")
    else:
        print(f"猜对了,共尝试 {attempts} 次")
        break

测试时可以将 answer = random.randint(1, 100) 临时改成固定值,例如 answer = 50,这样能稳定测试“猜小、猜大、猜对”三条路径。完成后恢复随机答案。

边界测试:

  • 第一次就猜对。
  • 连续猜小。
  • 连续猜大。
  • 猜测 1 和 100。

2.11 本周作品二:成绩统计函数#

先写小函数,再组合。下面的程序接受一个分数列表,统计人数、平均分、及格人数和最高分:

def calculate_average(scores):
    if not scores:
        return 0.0
    return sum(scores) / len(scores)


def count_passed(scores):
    count = 0
    for score in scores:
        if score >= 60:
            count += 1
    return count


def find_highest(scores):
    highest = scores[0]
    for score in scores:
        if score > highest:
            highest = score
    return highest


scores = [88, 59, 92, 70, 100]
print("人数:", len(scores))
print(f"平均分:{calculate_average(scores):.2f}")
print("及格人数:", count_passed(scores))
print("最高分:", find_highest(scores))

本周只需要理解“函数接收一组数据并返回结果”。索引、切片和列表方法会在第 3 周系统讲解。

为什么空列表要先处理?因为 sum([]) / len([]) 会除以 0。先判断空数据,是边界测试意识。

2.12 本周作品三:学生对象#

class Student:
    def __init__(self, student_id, name, score):
        self.student_id = student_id
        self.name = name
        self.score = score

    def level(self):
        if self.score < 0 or self.score > 100:
            return "成绩无效"
        if self.score >= 90:
            return "优秀"
        if self.score >= 80:
            return "良好"
        if self.score >= 60:
            return "及格"
        return "不及格"

    def add_bonus(self, points):
        self.score += points


student = Student("S001", "张晨", 78)
student.add_bonus(5)

print(student.student_id, student.name, student.score)
print(student.level())

输出:

S001 张晨 83
良好

add_bonus() 修改的是对象自身的 score,所以调用后再次读取 student.score 会得到新值。

2.13 独立练习#

A. 快速判断题#

  1. range(2, 8, 2) 依次产生哪些数?

  2. 下面的循环输出几行?

    for i in range(3):
        for j in range(2):
            print(i, j)
    
  3. 函数中没有 return,调用结果是什么?

  4. return 和 print 有什么区别?

  5. random.randint(1, 6) 能否得到 6?

  6. 对象方法为什么通常需要第一个参数 self?

  7. 下面的程序为什么不会停止?运行时会最先不断输出什么?

    count = 0
    while count < 3:
        print(count)
    

B. 编程题#

  1. 用循环计算 1 到 100 的整数和。
  2. 输出 1 到 20 中的所有偶数。
  3. 输入一个正整数,判断它是不是质数。
  4. 输出九九乘法表。
  5. 编写函数 findMinMax(values),返回列表的最小值和最大值。
  6. 编写函数 count_above(scores, target),统计高于目标分数的数量。
  7. 定义 Book 类,包含书名、作者、价格和显示信息的方法。
  8. 让猜数字程序在输入非整数时给出提示并继续,而不是直接崩溃。提示:本周的完整异常处理还没学,可以先只在输入 "q" 时退出,其他非法输入留到第 7 周。

2.14 练习答案与解释#

A. 快速判断题答案#

  1. 2, 4, 6。终点 8 不包含。
  2. 6 行。外层 3 轮,内层每轮 2 次。
  3. None。
  4. print 显示内容但不把值交给调用者;return 结束函数并把值交给调用者。
  5. 可以。randint 包含区间两端。
  6. self 指向当前对象,用来区分和访问该对象自己的属性。
  7. count 从未增加,条件 count < 3 永远为真,循环会无限执行。程序最先不断输出 0,直到被手动中断。

B. 编程题参考答案#

  1. 1 到 100 求和:

    total = 0
    for number in range(1, 101):
        total += number
    print(total)
    
  2. 1 到 20 的偶数:

    for number in range(2, 21, 2):
        print(number)
    
  3. 质数判断:

    number = int(input())
    is_prime = True
    
    if number < 2:
        is_prime = False
    else:
        for divisor in range(2, number):
            if number % divisor == 0:
                is_prime = False
                break
    
    if is_prime:
        print("质数")
    else:
        print("不是质数")
    
  4. 九九乘法表:

    for row in range(1, 10):
        for column in range(1, row + 1):
            print(f"{column}x{row}={column * row}", end="\t")
        print()
    
  5. 最小值和最大值:

    def findMinMax(values):
        minimum = values[0]
        maximum = values[0]
        for value in values:
            if value < minimum:
                minimum = value
            if value > maximum:
                maximum = value
        return minimum, maximum
    
    print(findMinMax([4, 9, 1, 7]))
    

    返回结果是元组 (1, 9)。空列表不能直接取 values[0],调用者应保证列表非空或先处理空列表。

  6. 统计高于目标分数的数量:

    def count_above(scores, target):
        count = 0
        for score in scores:
            if score > target:
                count += 1
        return count
    
    print(count_above([58, 76, 90, 60], 60))  # 2
    
  7. Book 类:

    class Book:
        def __init__(self, title, author, price):
            self.title = title
            self.author = author
            self.price = price
    
        def show_info(self):
            print(f"《{self.title}》 作者:{self.author} 价格:{self.price:.2f} 元")
    
    book = Book("Python 入门", "张老师", 45.5)
    book.show_info()
    
  8. 猜数字增加退出选项:

    import random
    
    answer = random.randint(1, 100)
    
    while True:
        guess_text = input("请输入整数,或输入 q 退出:")
        if guess_text == "q":
            print("已退出")
            break
    
        guess = int(guess_text)
        if guess < answer:
            print("猜小了")
        elif guess > answer:
            print("猜大了")
        else:
            print("猜对了")
            break
    

    输入 abc 仍会触发 ValueError。第 7 周会学习完整处理方式。

2.15 常见错误清单#

  • 把 range(1, 6) 误认为包含 6。
  • 循环变量不在循环体中更新,造成无限循环。
  • 把 continue 当成结束整个循环。
  • 忘记函数体的缩进。
  • 定义了有返回值的函数,却把调用结果丢掉,只期待屏幕上出现结果。
  • 把 return 写在循环第一轮中,导致循环提前结束。
  • 认为函数中的局部变量可以在函数外用。
  • self.attribute 与局部变量混用。
  • 随机程序只测试一次就认为正确。

2.16 过关检查#

  • 能不运行代码,写出任意 range() 产生的序列和次数。
  • 能用表格推演一个 while 循环每轮变量值。
  • 能说清 break 和 continue 的差异。
  • 能预测两层嵌套循环的总执行次数。
  • 能定义带参数和返回值的函数,并至少调用两次。
  • 能解释“函数返回 None”的原因。
  • 能使用 randint,并通过固定答案完成完整路径测试。
  • 能定义类、创建两个对象,并证明它们的属性互相独立。
  • 独立完成猜数字程序,覆盖猜小、猜大、猜对和退出。
  • 第 1 周的三个作品可以关掉资料重写。

本周进度

完成讲义、练习和验收后再标记。