跳到主要内容

2.4 可以复用的代码

知识点​

1.为什么需要"复用"​

问题:前面写的求最大公约数、判断素数、求平均值……下一个程序还要用。如果每次都复制粘贴一遍,会有三个麻烦:

  1. 代码变长,可读性差;
  2. 改一次要改多处,容易漏改出错;
  3. 无法独立测试,错误难定位。

解决方案:把这些"功能单一、会被反复使用"的代码打包成函数,用时调用即可。

函数的两大来源:

来源说明例子
自定义函数自己用 def 定义求最大公约数、判断闰年
内置函数/库函数Python 自带或第三方提供print()、input()、len()、math.sqrt()、random.randint()

复用带来的三个好处(答题落点):提高代码复用率、降低维护成本、便于分工协作与测试。

2.函数的定义与调用​

(1)定义格式​

def 函数名(参数表):
"""函数说明(可选)"""
函数体
return 返回值 # 可选

四条语法要求:

  1. 以关键字 def 开头;
  2. 函数名后必须有一对圆括号(没有参数也要写 ());
  3. 括号后必须有英文冒号 :;
  4. 函数体必须缩进(4 个空格)。

(2)调用格式​

函数名(实参表)

关键理解:定义函数时函数体不执行,只有在被调用时才执行;定义一次,可以调用无数次。

# 2-10 定义一个求圆面积的函数
def circle_area(r):
"""计算圆的面积"""
return 3.14159 * r * r

print(circle_area(1)) # 3.14159
print(circle_area(2)) # 12.56636
print(circle_area(10)) # 314.159

函数名的命名规则:与变量相同(字母/数字/下划线,不能数字开头,不用关键字),见名知义,一般用动词或动宾短语(get_max、is_leap、circle_area)。

(3)函数说明文档(docstring)​

函数体第一行的三引号字符串是函数说明,可用 help(函数名) 查看。这是好习惯,也是"代码规范"的考点。

3.参数:函数的"入口"​

(1)形参与实参​

概念位置说明
形参(形式参数)定义时括号里的变量只是"占位符",没有具体值
实参(实际参数)调用时括号里的值真正参与计算的数据
def add(a, b): # a、b 是形参
return a + b

print(add(3, 5)) # 3、5 是实参

传递过程:调用时把实参的值赋给对应的形参(a = 3, b = 5),再执行函数体。

(2)三种参数形式​

形式写法说明示例
位置参数按顺序对应最常见,顺序不能错add(3, 5)
关键字参数形参名=值按名字对应,顺序可换add(b=5, a=3)
默认参数def f(a, b=1):调用时可省略,取默认值f(3) 等价于 f(3, 1)

默认参数的两条规则:

  1. 默认参数必须放在参数表末尾(不能写在无默认值参数前面);
  2. 默认参数的值在函数定义时确定一次,不会每次调用重新计算(了解即可)。

(3)参数个数必须匹配​

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

add(3) # TypeError:缺少一个参数
add(3, 5, 7) # TypeError:多了参数

报错关键词:missing 1 required positional argument(少参数)、takes 2 positional arguments but 3 were given(多参数)。

4.返回值:函数的"出口"​

(1)return 的作用​

  1. 把结果交回给调用处;
  2. 立即结束函数(return 之后的语句不再执行)。
def get_grade(score):
if score >= 90:
return "A"
elif score >= 60:
return "及格"
return "不及格" # 前面都没返回,才会走到这里

对比 print 与 return(极重要):

print(x)return x
作用把值显示在屏幕上把值交给调用者
能否参与运算不能(print(3) + 1 报错)能(get_grade(95) + "!" 合法)
是否结束函数不结束立即结束

一句话:print 是"给人看",return 是"给程序用"。

(2)没有 return 时返回什么​

返回 None(表示"没有值")。

def show(msg):
print(msg)

x = show("hi") # 屏幕输出 hi
print(x) # 输出 None

易错:忘了写 return,结果被赋给变量后是 None,后续运算报错 TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'。

(3)返回多个值​

def min_max(nums):
return min(nums), max(nums) # 用逗号分隔,实际返回一个元组

lo, hi = min_max([3, 1, 4, 1, 5]) # 解包接收
print(lo, hi) # 1 5

本质:返回的是元组(tuple),可以用解包的方式接收。

5.变量的作用域​

概念定义位置可见范围生命周期
局部变量函数内部定义只在函数内可见函数调用结束即销毁
全局变量函数外部定义整个程序可见(函数内可读)程序运行期间一直存在
total = 0 # 全局变量

def add(n):
s = 0 # 局部变量
for i in range(1, n + 1):
s = s + i
return s

print(add(10))
print(s) # NameError:s 在函数外不存在

三条规则:

  1. 函数内可以读全局变量;
  2. 函数内要给全局变量赋值,必须先用 global 声明——但这是不推荐的做法(耦合高、易出错);
  3. 同名变量:函数内的局部变量会遮蔽同名的全局变量,两者互不影响。

最佳实践:尽量用参数传入、用 return 传出,少用全局变量。

6.库与模块:站在别人的肩膀上​

库(library)/模块(module):别人写好的、功能相关的函数集合,放在一个 .py 文件里,导入后即可使用。

Python 的标准库:随 Python 一起安装,无需额外下载(如 math、random、time、turtle)。

(1)三种导入方式​

写法使用方式特点
import mathmath.sqrt(16)推荐:来源清楚,不会与自定义函数重名
from math import sqrt, pisqrt(16)、pi简洁,但可能重名冲突
import math as mm.sqrt(16)起别名,名字长时方便
from math import *全部导入不推荐:污染命名空间,不知道函数来自哪

(2)math 库常用函数​

函数/常量作用示例 → 结果
math.sqrt(x)平方根math.sqrt(16) → 4.0
math.pi圆周率 π3.141592653589793
math.e自然常数 e2.718281828459045
math.pow(x, y)幂(等价于 x ** y)math.pow(2, 3) → 8.0
math.fabs(x)绝对值(返回 float)math.fabs(-3) → 3.0
math.ceil(x)向上取整math.ceil(3.2) → 4
math.floor(x)向下取整math.floor(3.8) → 3
math.gcd(a, b)最大公约数math.gcd(48, 18) → 6
math.sin/cos/tan(x)三角函数(弧度)math.sin(math.pi/2) → 1.0
math.log(x)自然对数math.log(math.e) → 1.0
math.factorial(n)阶乘math.factorial(5) → 120

注意:math.sqrt() 的参数不能为负数(会报 ValueError: math domain error);math.ceil 与 math.floor 返回的是整数。

(3)random 库常用函数​

函数作用示例 → 可能结果
random.random()产生 0~1 之间的随机小数(含 0 不含 1)0.7234...
random.randint(a, b)产生 a~b 之间(含两端) 的随机整数random.randint(1, 6) → 骰子点数
random.randrange(a, b, step)按 range 规则取随机整数(含头不含尾)random.randrange(1, 10, 2) → 1、3、5、7、9
random.choice(seq)从序列中随机选一个random.choice(["石头","剪刀","布"])
random.shuffle(lst)原地打乱列表顺序洗牌
random.sample(seq, k)随机取 k 个不重复元素抽奖
random.uniform(a, b)产生 a~b 之间的随机小数random.uniform(1, 10)

最容易错的一对:randint 含两端,randrange 含头不含尾。

# 2-11 猜数字小游戏(random + while + if 的综合运用)
import random

answer = random.randint(1, 100)
count = 0
while True:
guess = int(input("猜一个 1~100 的数:"))
count += 1
if guess > answer:
print("大了")
elif guess < answer:
print("小了")
else:
print(f"猜对了!共猜 {count} 次")
break

(4)自定义模块​

把自己写的函数保存为 mytools.py,在同一目录的新程序里 import mytools 即可用 mytools.函数名() 调用。

# mytools.py
def is_leap(year):
return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0

# main.py
import mytools
print(mytools.is_leap(2024)) # True

意义:把自己积累的工具函数沉淀成模块,下次直接复用——这就是"可以复用的代码"的完整含义。

7.递归入门(可了解)​

递归:函数自己调用自己,必须包含两个部分:

  1. 递归关系(把大问题化成同类的小问题);
  2. 终止条件(边界,否则无限递归导致 RecursionError)。
# 2-12 递归求阶乘
def fact(n):
if n == 1: # 终止条件
return 1
return n * fact(n - 1) # 递归关系

print(fact(5)) # 120

递归 vs 循环:递归代码短、贴近数学定义,但占用栈空间、层次深时效率低;能用循环解决的简单问题,优先用循环。汉诺塔等问题的递归解法见 4.3。

8.易错点清单​

序号错误说明正确做法
1def f() 后忘冒号SyntaxErrordef f():
2定义后直接写 f 不写 f()只是引用函数对象,不执行调用要加括号
3函数定义在调用之后程序自上而下执行,会 NameError先定义后调用
4忘写 return返回 None需要结果就 return
5用 print 代替 return结果无法参与后续运算用 return
6默认参数写在前面SyntaxError默认参数放末尾
7实参个数与形参不符TypeError个数对应或用默认值
8函数内改全局变量未声明实际创建了局部变量,全局值没变global 声明(不推荐)
9import math 后直接写 sqrt()NameErrormath.sqrt() 或 from math import sqrt
10math.sqrt(-1)ValueError先判断非负
11randint(1, 6) 与 randrange(1, 6) 混同前者含 6,后者不含看清是否需要取到端点
12三角函数直接传角度结果是错的先转弧度(* math.pi / 180)

9.速查表​

序号考点结论
1定义函数关键字def
2定义语法要点def 名(参数): + 缩进函数体
3调用函数名(实参),必须加括号
4形参/实参定义处的叫形参,调用处的叫实参
5默认参数位置必须放参数表末尾
6return 作用返回值 + 立即结束函数
7无 return 时返回值None
8print 与 returnprint 给人看,return 给程序用
9返回多个值用逗号,本质是元组,可解包接收
10局部变量函数内定义,函数外不可见
11全局变量函数外定义,函数内可读,改需 global
12库的导入import、from … import …、import … as …
13math.sqrt(16)4.0(参数不能为负)
14math.pi圆周率常量
15向上/向下取整math.ceil/math.floor
16最大公约数math.gcd(a, b)
17三角函数单位弧度,需角度转弧度
18random.random()0~1 的小数(不含 1)
19random.randint(1, 6)含 1 和 6
20random.randrange(1, 6)含 1 不含 6
21random.choice(seq)随机取一个元素
22自定义模块保存为 .py,同目录 import 即可
23递归两要素终止条件 + 递归关系

10.课堂提示(讲课参考)​

  • 先制造"复制粘贴的痛苦":让学生把求最大公约数的代码粘到第二个程序里,再改需求,体会"改三处、漏一处"的麻烦,再引入函数——需求驱动比直接讲语法有效。
  • print 与 return 用一个题区分:def f(x): print(x*2) 与 def g(x): return x*2,让学生写 f(3)+1 和 g(3)+1,一个报错一个得 7。
  • math.sqrt 与 **0.5 都演示一遍,说明"同一个功能可以有多条路"。
  • 猜数字游戏是 2.4 的最佳综合案例:random + while + if + break 全用上,学生做出成品会有成就感,也为 4.4 综合问题打基础。
  • 提醒"randint 含端点"这个坑:掷骰子写 randrange(1, 6) 永远掷不出 6,让学生自己试出来。