第5章 函数与模块

定义函数、必选/默认/可变参数、返回值与作用域,以及模块与包的组织方式。

函数第6-7周3 讲8 段示例代码

本章学习目标

  • 能定义带参数和返回值的函数
  • 能正确使用默认参数、关键字参数与 *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(函数) 就能看到。
  • 写清参数含义、返回值、异常,比写一堆注释有用。

代码示例

▶ 求平均分
PYTHON
def average(scores):
    """返回平均分;空列表返回 0"""
    if not scores:
        return 0
    return sum(scores) / len(scores)

print(average([90, 80, 70]))
print(average([]))
👀 运行结果
80.0
0
▶ 默认参数与关键字参数
PYTHON
def greet(name, greeting="你好", punct="!"):
    return f"{greeting},{name}{punct}"

print(greet("张三"))
print(greet("李四", punct="。"))
👀 运行结果
你好,张三!
你好,李四。
▶ 可变参数
PYTHON
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。

代码示例

▶ 返回多值
PYTHON
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
▶ 作用域
PYTHON
count = 0

def add_one(x):
    return x + 1

print(add_one(count), count)
👀 运行结果
1 0
▶ 递归求阶乘
PYTHON
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 时不执行。
  • 这是写“既能当脚本又能被复用”的标准做法。

代码示例

▶ 导入与使用
PYTHON
import math
from random import randint

print(round(math.sqrt(16), 2))
print(randint(1, 6))
👀 运行结果
4.0
(1~6 的随机数)
▶ 入口判断
PYTHON
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),否则会覆盖标准库。