第5章 函数与模块
定义函数、必选/默认/可变参数、返回值与作用域,以及模块与包的组织方式。
本章学习目标
- 能定义带参数和返回值的函数
- 能正确使用默认参数、关键字参数与 *args/**kwargs
- 理解局部变量与全局变量、能说明为什么不建议用 global
- 能拆分模块、用 import 组织多文件程序
本章导览
核心内容
5.1
函数定义与参数
把重复逻辑收进函数:def、参数、返回值,是“会写程序”的分水岭。
能定义并调用函数能用默认参数、关键字参数与可变参数知道参数是“传对象引用”
知识点
定义与调用
- def 函数名(参数): 缩进写函数体,用 return 返回结果。
- 没有 return 时函数返回 None。
- 函数名用小写加下划线,动词开头(calc_avg、load_data)。
四种参数
- 必选参数、默认参数(def f(a, b=10):)、关键字参数(f(b=2, a=1))。
- *args 收集多余位置参数为元组,**kwargs 收集多余关键字参数为字典。
- 默认参数不要用可变对象(def f(a=[]): 是经典坑)。
文档字符串
- 函数第一行写 """说明""",help(函数) 就能看到。
- 写清参数含义、返回值、异常,比写一堆注释有用。
代码示例
▶ 求平均分
def average(scores):
"""返回平均分;空列表返回 0"""
if not scores:
return 0
return sum(scores) / len(scores)
print(average([90, 80, 70]))
print(average([]))👀 运行结果
80.0 0
▶ 默认参数与关键字参数
def greet(name, greeting="你好", punct="!"):
return f"{greeting},{name}{punct}"
print(greet("张三"))
print(greet("李四", punct="。"))👀 运行结果
你好,张三! 你好,李四。
▶ 可变参数
def total(*nums, **info):
print(f"{info.get('name', '合计')}: {sum(nums)}")
total(1, 2, 3, name="总分")👀 运行结果
总分: 6
常见错误
⚠️ 踩坑与解决
TypeError: average() missing 1 required positional argument
→ 调用时少传参数;补参数或用默认值默认参数是列表导致数据串联
→ def f(a=[]) 每次复用同一个列表;改为 a=None,函数内 if a is None: a = []
💡 函数只做一件事;超过 30 行就考虑拆成两个函数。
5.2
返回值、作用域与递归
变量在哪儿能用(作用域)、函数能返回什么、什么问题适合递归。
能返回单个值与多个值能说清局部/全局变量与 LEGB能写出简单递归(阶乘、斐波那契)
知识点
返回值
- return 立即结束函数;返回多个值其实是返回元组。
- 函数里修改传入的列表/字典会影响外部(可变对象引用)。
- 不想影响外部就传副本:f(lst.copy())。
作用域 LEGB
- 查找顺序:局部 Local → 外层函数 Enclosing → 全局 Global → 内置 Built-in。
- 函数内给变量赋值会创建局部变量;要改全局需 global(不推荐)。
- 更推荐:把需要的值当参数传进去、把结果 return 出来。
递归
- 自己调用自己,必须有终止条件;阶乘、目录遍历、树结构常用。
- 递归深度默认 1000 层左右,太深会 RecursionError。
代码示例
▶ 返回多值
def stats(nums):
return min(nums), max(nums), sum(nums) / len(nums)
lo, hi, avg = stats([80, 90, 100])
print(lo, hi, avg)👀 运行结果
80 100 90.0
▶ 作用域
count = 0
def add_one(x):
return x + 1
print(add_one(count), count)👀 运行结果
1 0
▶ 递归求阶乘
def fact(n):
if n <= 1:
return 1
return n * fact(n - 1)
print(fact(5))👀 运行结果
120
常见错误
⚠️ 踩坑与解决
UnboundLocalError: local variable referenced before assignment
→ 函数内先用了同名变量又赋值;检查是否需用 global,或改名RecursionError: maximum recursion depth exceeded
→ 递归没有终止条件;补 if 终止分支
💡 能用循环解决就别用递归;递归要么简单(阶乘),要么问题本身是树形(目录、菜单)。
5.3
模块与包
一个 .py 就是一个模块;把函数分文件放,程序才长得大。
能 import 三种写法能区分模块与包能用 __name__ 做入口判断
知识点
三种导入
- import math → math.sqrt(4);import math as m → m.sqrt(4)。
- from math import sqrt → 直接 sqrt(4)(少写前缀,但易重名)。
- 不要用 from module import *,会污染命名空间。
包
- 包是带 __init__.py 的目录;from mypkg import tools 用里面的模块。
- 自己写的 utils.py 与脚本放在同一目录即可直接 import。
入口判断
- if __name__ == "__main__": 只有直接运行该文件才执行,被 import 时不执行。
- 这是写“既能当脚本又能被复用”的标准做法。
代码示例
▶ 导入与使用
import math
from random import randint
print(round(math.sqrt(16), 2))
print(randint(1, 6))👀 运行结果
4.0 (1~6 的随机数)
▶ 入口判断
def main():
print("程序开始")
if __name__ == "__main__":
main()👀 运行结果
程序开始
常见错误
⚠️ 踩坑与解决
ModuleNotFoundError: No module named 'utils'
→ 模块不在同一目录或没装;检查文件位置,或用 sys.path.append 加路径ImportError: attempted relative import with no known parent package
→ 相对导入用错;脚本直接运行时不写相对导入
💡 自己写的模块名别和标准库同名(如 random.py、json.py),否则会覆盖标准库。