【Python入门】流程控制 + 函数:从猜数字游戏到闭包陷阱
【Python入门】流程控制 + 函数:从猜数字游戏到闭包陷阱
学 Python,流程控制和函数是最基础也是最重要的。这篇文章用一个猜数字游戏带你入门,再深入闭包陷阱和装饰器——两个新手最容易踩坑的地方。
一、先看问题
还记得猜数字游戏吗?一个人心里想一个 1-100 的数字,另一个人来猜:
- 猜小了 → “太小了!”
- 猜大了 → “太大了!”
- 猜对了 → “恭喜你!”
用 Python 怎么实现?只需要 if/elif/else + while 就行。
二、基础语法
1. if / elif / else
a = 1
b = 2
# 基础判断
if a < b:
print('a小于b')
else:
print('a大于等于b')
# 多条件判断用 elif
if a < b:
print('a小于b')
elif a == b:
print('a等于b')
else:
print('a大于b')
# 简写:只有一行时可以写在一行
if a < b: print('a小于b')
# 三元表达式
print('a小于b') if a < b else print('a大于等于b')
# and:多个条件同时满足
if a < b and a > 0: print('a小于b大于0')
# or:满足其中一个即可
if a < b or a == 0: print('a小于b或a等于0')
# pass:占位,还没想好写什么时用
if a == 0: pass
⚠️ 新手常见坑:多个条件判断要用
elif,不要写多个if。elif匹配后不再判断后面的条件,多个if每个都会判断。
2. while / for + break / continue
a = 1
# while 循环:条件成立就继续执行
while a < 3:
print(f'a = {a}')
a += 1
else:
print(f'循环结束,a = {a}') # 正常完成后执行 else
# for 循环
for i in range(10, 0, -1): # 从10到1,步长-1
if i == 9:
continue # 跳过本次,继续下一次
if i == 3:
break # 直接跳出循环
print(f'i = {i}')
else:
print('循环圆满完成') # break 跳出时不执行,continue 会执行
💡 while/for 的 else:正常完成循环才执行 else,break 跳出则不执行。continue 不影响 else 的执行。
三、实战:猜数字游戏
第 1 版:基本实现
import random
num = random.randint(1, 100)
count = 0
while True:
unum = int(input('请输入1到100之间的整数:').strip())
if unum < num:
print('太小了')
elif unum > num:
print('太大了')
else:
print(f'恭喜你在第 {count} 轮就猜对了')
break
count += 1
能跑,但有两个问题。
第 2 版:修复两个 bug
问题 1:第一次就猜对,显示"第 0 轮猜对了"——不符合人的直觉。
问题 2:输入字母直接崩溃——没有输入校验。
import random
num = random.randint(1, 100)
count = 1 # ← 修复1:从1开始计数
while True:
try: # ← 修复2:输入校验
unum = int(input('请输入1到100之间的整数:').strip())
except ValueError:
print('请输入有效数字')
continue
if unum == num:
print(f'恭喜你在第 {count} 轮就猜对了')
break
elif unum < num:
print('太小了')
else:
print('太大了')
count += 1
四、函数参数 5 种类型
| 类型 | 示例 | 特点 |
|---|---|---|
| 必填参数 | (a, b, c) |
按顺序赋值,少填一个就报错 |
| 默认参数 | (a, b=1, c=2) |
有默认值的可以不填 |
| 可变参数 | (*args) |
可以传 0 或多个参数 |
| 关键字参数 | (**kv) |
以键值对形式传参 |
| 命名关键字参数 | (*, a, b=1) |
必须指定变量名,否则 TypeError |
核心原则:范围大的放后面。 组合顺序:必填 → 默认 → 可变 → 命名关键字 → 关键字。
# 示例
def func(a, b=2, *args, c, d=4, **kv):
print(a, b, args, c, d, kv)
func(1, 3, 5, 7, c=9, e=11)
# 输出:1 3 (5, 7) 9 4 {'e': 11}
五、闭包陷阱:为什么 c1() c2() 都返回 4?
看这段代码:
def count1():
l = []
for i in range(1, 3):
def s():
return i + i
l.append(s)
return l
c1, c2 = count1()
print(c1(), c2()) # 预期 2, 4,实际输出:4 4
为什么? 函数 s 引用的是外层变量 i,不是 i 的值。当 count1 执行完毕,i 已经变成了 2。c1 和 c2 调用时,取到的 i 都是 2。
修复:用默认参数绑定当前值
def count1():
l = []
for i in range(1, 3):
def s(i=i): # ← 关键:默认参数在定义时就绑定了当前 i 的值
return i + i
l.append(s)
return l
c1, c2 = count1()
print(c1(), c2()) # 输出:2 4 ✅
只改了一个地方:def s(): → def s(i=i):,问题解决。
⚠️ 面试高频题:闭包循环变量陷阱是 Python 面试的经典题目,一定要记住。
六、装饰器:给函数加个"壳"
装饰器就是在不改变原函数代码的前提下,给函数加上额外功能。
import functools
def log(f):
@functools.wraps(f) # 保留原函数的 __name__ 等属性
def w(*args, **kv):
print(f'我要执行{f.__name__}了')
result = f(*args, **kv) # 把参数原样传给原函数
print(f'我已经执行完成了')
return result
return w
@log # 等价于 go = log(go)
def go(a):
print(a)
go(1)
# 输出:
# 我要执行go了
# 1
# 我已经执行完成了
执行流程:
@log相当于go = log(go),此时go指向的是log返回的w函数- 调用
go(1)实际调用的是w(1) w先打印"我要执行了",再调用原函数f(1),最后打印"执行完成"
💡 @functools.wraps(f) 不能省!不加的话
go.__name__会变成w,可能导致依赖函数名的代码出错。
七、踩坑记录
坑 1:猜数字 count 从 0 开始
第一次就猜对显示"第 0 轮",不符合人的直觉。count 应该从 1 开始。
坑 2:闭包循环变量陷阱
返回的函数引用了外层循环变量,循环结束后变量已变。修复:用默认参数 def s(i=i): 绑定当前值。
坑 3:装饰器不要写死参数
装饰器应该用 f(*args, **kv) 通用调用,不要写死 f(a=1) ——会破坏装饰器的通用性。
八、总结
| 知识点 | 核心要点 |
|---|---|
| if/elif/else | 多条件用 elif,不要写多个 if |
| while/for + else | 正常完成才执行 else,break 不执行 |
| 函数参数 5 种类型 | 范围大的放后面 |
| 闭包陷阱 | 返回函数引用外层变量,循环结束后变量已变 |
| 装饰器 | 给函数加"壳",用 @functools.wraps 保留原函数属性 |
📌 下一篇:文件读写 + 面向对象,用类管理你的数据文件。
更多推荐

所有评论(0)