PY-05 函数
05 · 函数(Functions)
📅 预计 70 分钟 | ⭐ = 高频考点
✍️ 本讲配套练习:本地python grader.py 05,或网页练习
5.1 函数定义与调用
把函数想成做菜的菜谱:菜谱躺在书架上不会自己做菜,只有你照着步骤动手(调用),才会端出菜。Python 里 def 就是在"写菜谱",用"函数名 + 括号"就是在"照着做一次"。
def add(a, b): # def + 函数名 + 参数 + 冒号
return a + b # 返回结果
print(add(3, 4)) # 7
几个关键规则:
- 函数体必须缩进,不缩进的代码不属于这个函数。
- 没写
return的函数,返回None(考试超爱考)。 - 一个函数只做一件事,名字要能"望文生义"。
def greet(name):
print("你好", name) # 只打印,没有 return
print(greet("小明")) # 先打印「你好 小明」,再打印 None
小练习:猜猜
print(greet("小明"))会输出几行?答案:两行——先执行函数里的None。
⚠️ 常见错误
- 忘写冒号:
def add(a, b)结尾漏了冒号,直接SyntaxError。 - 缩进错位:混用 Tab 和空格会报
IndentationError: unexpected indent。 - 以为没 return 的函数有返回值:没有
return→ 返回值是None,别拿它继续做运算。
5.2 参数:四种类型 ⭐
参数就是菜谱上列的食材:菜谱写"放鸡蛋",但没说是哪个鸡蛋——真正做菜那一刻才把具体的蛋递进去。**形参(parameter)**是菜谱上的占位名,**实参(argument)**是调用时真正传入的值。
# 1) 位置参数 positional:按顺序一一对应
def f(a, b):
return a - b
f(10, 3) # 7,a=10、b=3(位置决定是谁)
# 2) 默认参数 default:必须放在最后 ⭐
def greet(name, greeting="你好"):
return greeting + "," + name
greet("小明") # '你好,小明'(用默认值)
greet("小明", "早上好") # '早上好,小明'(覆盖默认值)
# 3) 关键字参数 keyword:调用时指名道姓,顺序随便
f(b=3, a=10) # 7,等价于 f(10, 3)
# 4) 可变参数 *args / **kwargs ⭐
def total(*nums): # * 把任意多个位置参数收成一个元组
return sum(nums)
total(1, 2, 3) # 6
total() # 0(一个都不传也行)
def info(**kw): # ** 把任意多个关键字参数收成一个字典
return kw
info(name="小明", age=20) # {'name':'小明','age':20}
对比记忆:
*args收到的是元组(有序),**kwargs收到的是字典(带名字)。名字本身不重要,*和**才关键。
⚠️ 常见错误
- ⭐ 默认参数位置放错:
def f(a, b=1, c)是语法错误——默认参数必须都在无默认参数的后面。 - 默认参数用可变对象(列表/字典):默认值只在定义时创建一次,多次调用会"记住"上次改的结果,俗称"变参陷阱"。
def bad(items=[]): # 危险!所有调用共享同一个列表
items.append(1)
return items
print(bad()) # [1]
print(bad()) # [1, 1] ← 上一次的结果还在
def good(items=None): # 安全写法:用 None 占位,函数里再新建
items = [] if items is None else items
items.append(1)
return items
5.3 返回值:函数做好的菜
return 就是出锅:菜做好端出来,后面的工序(代码)不再执行。return 还可以一次端出好几道菜——本质是把它们打包成一个元组。
def stats(nums):
return sum(nums), len(nums) # 返回一个元组 (和, 个数)
s, n = stats([1, 2, 3]) # 解包接收:s=6, n=3
提前 return 可以当"提前结束开关":
def check(score):
if score < 60:
return "挂科" # 提前 return,直接结束函数
return "通过"
print(check(50), check(90)) # 挂科 通过
⚠️ 常见错误
- return 后面的代码不执行(不可达代码,笔试爱考):
def f():
return 1
print("这行永远打不出来")
- 多返回值忘了解包:
r = stats([1, 2, 3])拿到的是元组(6, 3),不是一个6一个3。 - 分支漏 return:有的分支返回了值,有的分支忘了
return,那个分支会悄悄返回None。
5.4 作用域 scope ⭐
作用域就是变量的活动范围,好比房间:小房间里的东西在客厅看不见,客厅里的东西小房间里能看见。函数内是"小房间"(局部),函数外是"客厅"(全局)。
x = 10 # 全局变量(客厅里的东西)
def f():
x = 20 # 这是小房间里新建的变量,不影响客厅
print(x) # 20
f()
print(x) # 10,客厅没变
- 函数内读全局变量:直接用,读得到。
- 函数内改全局变量:必须
global声明,否则 Python 会认为你只是新建了局部变量。
def g():
global x
x = 99 # 这才真的改了全局
g()
print(x) # 99
变量查找顺序:局部 → 外层 → 全局 → 内置,英文缩写 LEGB(Local → Enclosing → Global → Built-in)。
⚠️ 常见错误
- 改全局忘了 global:函数里
x = 99只是新建局部变量,全局还是原样(5.4 第一个例子的 20/10 就是这个坑)。 - 先读后写同一个名字:函数里既有
print(x)又有x = 2,Python 看到有赋值就把x判为局部变量,前面的读就变成"读未赋值的局部",报UnboundLocalError。
x = 1
def bad():
print(x) # 报错 UnboundLocalError
x = 2
5.5 lambda 匿名函数与高阶函数 ⭐
lambda 是没有名字的一次性函数,适合"用完就扔"的小逻辑。Python 里函数是"一等公民":能当参数传、能当返回值返回,这样的函数叫高阶函数。
f = lambda x: x * 2 # 等价于 def f(x): return x*2
print(f(3)) # 6
# 常和排序一起用 ⭐
students = [("小明", 90), ("小红", 85)]
students.sort(key=lambda s: s[1]) # 按成绩升序
# map / filter / sorted 全家桶 ⭐
nums = [1, 2, 3, 4]
list(map(lambda x: x * 2, nums)) # [2, 4, 6, 8]
list(filter(lambda x: x % 2 == 0, nums)) # [2, 4]
sorted(nums, key=lambda x: -x) # [4, 3, 2, 1] 降序
三个函数怎么记:
map(fn, 可迭代):把 fn 作用到每个元素上,映射出新的可迭代对象(要list()才看得到)。filter(fn, 可迭代):留下 fn 结果为真的元素,过滤。sorted(可迭代, key=fn):按 fn 的返回值排序,返回新列表,原列表不动。
⚠️ 常见错误
- map/filter 忘了转 list:直接打印出来是
<map object at 0x...>,不是[2,4,6,8]。 - lambda 里写多条语句:lambda 只能写一个表达式;
if/else只能用三元式a if 条件 else b,不能写普通if块。 - sorted 忘了 reverse:默认升序,要降序加
reverse=True,或给 key 取负数。
5.6 递归 recursion ⭐
递归是函数调用自己,像俄罗斯套娃:打开一个,里面还有更小的一个,直到最小的那个不用再拆。写递归只需两件事:终止条件(最小的娃)+ 递推关系(大娃怎么变成小娃)。
def fact(n):
if n <= 1:
return 1 # 终止条件:最小的娃
return n * fact(n - 1) # 递推关系:n! = n × (n-1)!
fact(5) # 120
def fib(n): # 斐波那契数列
if n <= 1:
return n # 终止条件
return fib(n-1) + fib(n-2) # 递推关系
fib(6) # 8
脑内走查一遍
fact(3):3*fact(2)→3*(2*fact(1))→3*(2*1)→6。先一路"往下拆",到底后再一路"往回算"。
⚠️ 常见错误
- 漏掉终止条件 → 无限递归,报
RecursionError: maximum recursion depth exceeded。 - 终止条件写在递归调用之后:还没检查到就被不断调用,照样爆栈。
- 递归太深:Python 默认递归上限约 1000 层(
sys.setrecursionlimit可调),题目给的数据一般不会触顶。
5.7 闭包:函数"记住"环境 ⭐
闭包 = 内层函数 + 外层函数的变量。外层函数返回内层函数,内层函数即使在外层调用结束后,仍"记得"外层那个局部变量——像带了个私人记事本。
def make_multiplier(n):
def multiply(x):
return x * n # 用到了外层的 n
return multiply # 注意:返回的是函数,不是值
triple = make_multiplier(3)
print(triple(4), triple(10)) # 12 30
内层函数要修改外层变量,用 nonlocal(和全局用 global 一个道理)。
def make_counter(start=0):
count = start
def counter():
nonlocal count
count += 1
return count
return counter
c = make_counter(10)
print(c(), c(), c()) # 11 12 13,count 被记住了
⚠️ 常见错误
- 忘了 return 内层函数:
def make_multiplier(n): def multiply(x): ...却没return multiply,调用返回None。 - 内层改外层变量没写 nonlocal:Python 当你在新建局部变量,要么报
UnboundLocalError,要么计数不生效。 - 闭包延迟绑定:循环里造的多个闭包,最后都记住循环变量"最后的值",调用结果一样(需要默认参数或立即绑定来规避)。
5.8 装饰器思想:给函数穿外套 ⭐
装饰器就是接收函数、返回新函数的函数,等于在原函数外面"套一件外套"(加日志、改输出、计时等)。@装饰器名 写在函数定义上方,等价于 原函数 = 装饰器(原函数)。
def uppercase(fn):
def wrapper(*args, **kwargs):
return fn(*args, **kwargs).upper() # 先执行原函数,再把结果变大写
return wrapper
@uppercase # 等价于 greet = uppercase(greet)
def greet(word):
return word
print(greet("hi")) # HI
看懂这个例子就懂装饰器了:@uppercase 让 greet 不再是原来的 greet,而是被包了外套的"新 greet"——调用它先跑原逻辑,再套上 upper()。
⚠️ 常见错误
- 装饰器没 return 内层函数:
@uppercase后greet变成None,一调用就报TypeError: 'NoneType' object is not callable。 - wrapper 没透传参数:必须写
wrapper(*args, **kwargs),才能把原函数的参数原样转交。 - wrapper 里没 return fn(...):原函数的返回值被丢掉,外面拿到的是
None。
5.9 文档字符串与类型提示
文档字符串 docstring """...""" 是函数开头的说明文字,用 函数.__doc__ 或 help(函数) 能读到——相当于菜谱封面的"菜品说明"。类型提示 type hint 用 : 类型 和 -> 类型 标注参数和返回值类型,是给人和 IDE 看的"说明书",Python 运行时不会强制校验。
def add(a: int, b: int) -> int:
"""返回两个整数的和。"""
return a + b
print(add(3, 4)) # 7
print(add.__doc__) # 返回两个整数的和。
⚠️ 常见错误
- docstring 写成普通注释:
# 说明只给开发者看,"""说明"""才是能被程序读到的文档。 - 以为类型提示会报错:
add("3", "4")不会因为类型不对而报错,提示只是"说明书"不是"检查员"。 - docstring 里嵌套英文双引号:docstring 用
"""包裹,里面再写英文"会让字符串提前结束,报语法错误;要用中文引号「」代替。
💡 记忆口诀
- 参数四兄弟:位置按顺序,默认放最后,关键字点名,
*收元组**收字典 - 作用域:要改全局写
global,要改外层写nonlocal - 递归两件套:有终止、有递推,缺一必爆栈
- lambda 三连:map 映射、filter 过滤、sorted 排序靠 key
📌 双语术语表(本讲)
| 中文 | English |
|---|---|
| 形参 / 实参 | parameter / argument |
| 返回值 | return value |
| 作用域 | scope |
| 局部变量 | local variable |
| 全局变量 | global variable |
| 默认参数 | default argument |
| 位置参数 | positional argument |
| 关键字参数 | keyword argument |
| 可变参数 | variable-length argument (*args / **kwargs) |
| 匿名函数 | lambda |
| 高阶函数 | higher-order function |
| 闭包 | closure |
| 装饰器 | decorator |
| 递归 | recursion |
| 文档字符串 | docstring |
| 类型提示 | type hint |
⭐ 本讲考点清单
- 无
return→ 返回None;return后的代码不执行 - 默认参数必须放最后;默认参数别用可变对象
*args收成元组、**kwargs收成字典- 改全局要
global,改外层要nonlocal lambda+map/filter/sorted(key=)三件套- 递归两要素:终止条件 + 递推关系
- 多返回值本质是元组,用解包接收
- 闭包 = 内层函数 + 外层变量的记忆
- 装饰器 = 接收函数返回函数,
@是语法糖 - docstring 与类型提示的作用(不强制校验)