# 定义函数
def add(x, y):
sum = x + y
return sum
9 Python 函数基础
9.1 引言
在这一课中,你将学习如何使用函数来组织你的代码。
函数就像是一个小机器,专门用来完成特定任务。想象一下电饭煲,你放入米和水,按下按钮,它就能为你做出米饭 — 函数也是如此!函数可以让你多次执行同样的任务,而不需要重复写代码。
我们已经在前面的课程中使用过 print()
这样的函数。但那些是 Python 自带的内置函数。在这一课中,你将学习如何创建自己的函数。
9.2 函数简介:一个简单的例子
我们先来看一个简单的函数例子。下面的add()
函数接受两个数字,将它们相加,然后返回结果。
让我们仔细观察上面的代码,一个函数主要由两部分组成:
- 函数头:第一行没有缩进的部分,告诉 Python 我们要定义一个叫
add
的函数,它需要两个输入值x
和y
。 - 函数体:缩进的部分是函数的”身体”,包含函数实际要执行的代码。
9.2.1 函数头详解
函数头就像函数的”身份证”,定义了函数的名称和需要的输入。
def add(x, y):
def
是一个特殊词,告诉 Python:“嘿,我要定义一个新函数了!”add
是我们给函数起的名字- 括号里的
x
和y
是参数,就像是函数的原料输入口 - 最后的冒号
:
告诉 Python 函数头结束了,下面要开始写函数体了
9.2.2 函数体详解
函数体是函数真正工作的地方,就像电饭煲内部的机制。
sum = x + y
return sum
- 函数体中的每一行都必须缩进(通常是4个空格或一次Tab键)
- 函数体按顺序执行代码:
- 先把
x
和y
相加,存到新变量sum
中 - 然后用
return
语句把结果”送出”函数
- 先把
函数体必须缩进,这样 Python 才知道哪些代码属于函数内部!
9.2.3 调用函数
定义函数后,需要”唤醒”它才能工作,这就是调用函数。
# 调用函数
= add(3, 5)
result
# 打印结果
print(result)
8
调用函数就像按下电饭煲的按钮:
- 我们调用
add(3, 5)
,把3
和5
作为参数传入 - 函数内部执行
sum = 3 + 5
,计算得sum = 8
return sum
将值8
送出函数- 最后
result
变量接收到值8
当我们写 add()
时,括号表示这是一个函数。实际调用时,括号里要填入真正的参数值!
9.3 函数的命名
在编程中,好的命名就像好的标签一样重要!想象一下,如果超市里所有商品都叫”东西1”、“东西2”,那购物就会变得非常困难。
在上面的示例中,我们的函数名是 add
。Python中函数命名有以下规则:
- 可以使用字母、数字和下划线
_
- 不能以数字开头
- 不能使用Python保留的关键字(如
if
、for
、while
等) - 函数名应该清晰描述函数的功能
# 好的函数名示例
# 计算平均值
calculate_average() # 检查用户登录状态
check_login() # 温度转换 convert_to_celsius()
Python中常用”蛇形命名法”(Snake Case)来命名函数 — 所有字母小写,单词间用下划线连接。例如:get_user_data()
、calculate_final_score()
避免使用下划线_
开头的函数名,因为这在Python中通常表示这是一个”私有”函数,有特殊用途。
9.4 函数的参数与返回值:一个更复杂的例子
现在你已经掌握了函数定义的基础知识,让我们看一个更实际的例子:
假设你在帮朋友优化一个简单的机器学习模型,需要计算损失值(损失值越低,模型越准确)。我们使用均方误差(MSE)的简化公式:loss = ((predicted - actual) ** 2) / 2
# 计算机器学习模型的损失值
def calculate_loss(predicted, actual):
# 计算差异
= predicted - actual
difference # 计算差异的平方
= difference ** 2
squared_difference # 计算损失
= squared_difference / 2
loss return loss
让我们来使用这个函数,看看它是如何工作的:
# 假设模型预测值为5,真实值为2
= calculate_loss(5, 2)
result print("模型的损失值是:", result)
模型的损失值是: 4.5
我们还可以用不同的值调用这个函数:
# 假设模型预测值为4,真实值为3
= calculate_loss(4, 3)
result print("模型的损失值是:", result)
模型的损失值是: 0.5
9.4.1 函数的参数
在Python中,参数让我们能够向函数传递信息。就像告诉微波炉加热多少分钟、用多大火力一样,参数让函数知道要处理什么数据和如何处理。
Python提供了几种不同的参数传递方式,让我们逐一了解:
9.4.1.1 位置参数
位置参数是最基本的参数类型,它们按照在函数中定义的顺序传递。顺序很重要!如果顺序错了,函数可能会产生意想不到的结果。
看看这个例子:
# 自我介绍函数
def introduce_myself(name, age):
print("Hello, my name is", name)
print("I am", age, "years old\n")
# 按正确顺序调用函数
"Jack", 25)
introduce_myself(
# 参数顺序颠倒
25, "Jack") introduce_myself(
Hello, my name is Jack
I am 25 years old
Hello, my name is 25
I am Jack years old
当我们颠倒参数顺序时,函数输出了”Hello, my name is 25”和”I am Jack years old”——这显然不对!Python只知道第一个值填入第一个参数,第二个值填入第二个参数,它不理解这些值的实际含义。
9.4.1.2 关键字参数
关键字参数通过参数名称传递值,这样就不用担心顺序问题了。这种方式特别适合有多个参数的函数,因为你不必记住参数的确切顺序。
# 使用关键字参数调用函数
="Jack", age=25)
introduce_myself(name
# 即使顺序颠倒也没问题
=25, name="Jack") introduce_myself(age
Hello, my name is Jack
I am 25 years old
Hello, my name is Jack
I am 25 years old
使用关键字参数可以让你的代码更清晰易读,尤其是当函数有很多参数时。它就像给每个值贴上标签,确保它们被正确使用。
9.4.1.3 默认参数
默认参数让你可以为函数参数设定一个预设值。如果调用函数时没有提供这个参数的值,Python就会使用默认值。
想象一下,如果我们经常使用相同的预测值进行测试:
# 设置predicted参数的默认值为3
def calculate_loss(actual, predicted=3):
# 计算差异
= predicted - actual
difference # 计算差异的平方
= difference ** 2
squared_difference # 计算损失
= squared_difference / 2
loss return loss
# 只传入actual参数,predicted使用默认值3
= calculate_loss(2)
result print("使用默认预测值的损失是:", result)
# 也可以覆盖默认值
= calculate_loss(2, 5)
result print("使用自定义预测值的损失是:", result)
使用默认预测值的损失是: 0.5
使用自定义预测值的损失是: 4.5
- 默认参数必须放在所有普通参数(没有默认值的参数)之后
- 错误示例:
def wrong_function(a=1, b):
← 这会导致语法错误! - 正确示例:
def correct_function(b, a=1):
使用默认参数有几个好处:
- 让常用值更容易使用
- 减少重复代码
- 使函数调用更简洁
如果函数有多个默认参数,但你只想修改其中一部分,最好使用关键字参数方式:
def create_profile(name, age=25, city="New York", hobby="reading"):
# 函数体...
# 只修改hobby参数
"Alice", hobby="swimming") create_profile(
9.4.2 函数的返回值
函数不仅可以接收输入(通过参数),还可以将计算结果发送回来。这个发送回来的结果就是函数的返回值。返回值是通过 return
语句实现的。
想象函数就像一台榨汁机:你输入水果(参数),它处理这些水果,然后给你果汁(返回值)。有些榨汁机可能不返回任何东西,有些可能会返回多种果汁。
9.4.2.1 无返回值的函数
如果我们不在函数中写 return
语句,或者只写 return
而不指定返回值,那么函数默认返回 None
。None
是Python中表示”无”或”空值”的特殊值。
让我们对比两个函数:
# 有返回值的函数
def calculate_loss(predicted, actual):
= predicted - actual
difference = difference ** 2
squared_difference = squared_difference / 2
loss return loss # 明确返回计算结果
# 无返回值的函数
def calculate_loss_without_return(predicted, actual):
= predicted - actual
difference = difference ** 2
squared_difference = squared_difference / 2
loss # 没有return语句
# 测试两个函数
= calculate_loss(4, 3)
result1 = calculate_loss_without_return(4, 3)
result2
print("有return的函数结果:", result1)
print("无return的函数结果:", result2)
有return的函数结果: 0.5
无return的函数结果: None
无返回值的函数会导致一些问题,特别是当你想对返回值进行操作时:
# 尝试对None值进行数学运算
try:
= calculate_loss_without_return(5, 2) + calculate_loss_without_return(4, 3)
result print("结果:", result)
except TypeError as e:
print(f"错误: {e}")
错误: unsupported operand type(s) for +: 'NoneType' and 'NoneType'
这个错误发生是因为我们尝试将两个 None
值相加,而Python不允许这样做。
无返回值的函数通常用于那些执行动作但不需要返回结果的情况,比如打印信息、保存文件或更新数据库等。
9.4.2.2 具有多个返回值的函数
Python中的函数可以同时返回多个值,只需在 return
语句后列出多个用逗号分隔的值即可。Python会自动将这些值打包成一个元组返回。
# 返回多个值的函数
def calculate_loss_details(predicted, actual):
# 计算差异
= predicted - actual
difference # 计算差异的平方
= difference ** 2
squared_difference # 计算损失
= squared_difference / 2
loss # 返回所有计算结果
return difference, squared_difference, loss
# 调用函数并获取多个返回值
= calculate_loss_details(5, 2)
result print("返回的完整结果:", result)
print("这是一个", type(result), "类型")
print("差异是:", result[0])
print("差异的平方是:", result[1])
print("损失值是:", result[2])
返回的完整结果: (3, 9, 4.5)
这是一个 <class 'tuple'> 类型
差异是: 3
差异的平方是: 9
损失值是: 4.5
我们可以使用一种更优雅的方式来获取多个返回值,这种方式叫做”解包”:
# 使用变量直接接收多个返回值
= calculate_loss_details(5, 2)
diff, squared_diff, loss_value
print("差异是:", diff)
print("差异的平方是:", squared_diff)
print("损失值是:", loss_value)
差异是: 3
差异的平方是: 9
损失值是: 4.5
这种方式更加直观,每个变量都对应一个返回值,代码更易读和维护。
9.4.2.3 条件返回
函数中的 return
语句一旦执行,函数就会立即结束,不再执行后面的代码。我们可以利用这个特性来实现条件返回。
def check_score(score):
if score >= 90:
return "优秀"
elif score >= 80:
return "良好"
elif score >= 60:
return "及格"
else:
return "不及格"
# 测试不同成绩
print("95分:", check_score(95))
print("85分:", check_score(85))
print("75分:", check_score(75))
print("55分:", check_score(55))
95分: 优秀
85分: 良好
75分: 及格
55分: 不及格
在这个函数中,根据不同的分数条件,会返回不同的评级结果,并且一旦满足某个条件并返回,就不会继续检查后面的条件。
9.4.2.4 返回复杂数据结构
函数不仅可以返回简单的数值或字符串,还可以返回更复杂的数据结构,如列表、字典或自定义对象。
def analyze_student(name, scores):
# 计算总分和平均分
= sum(scores)
total = total / len(scores)
average
# 确定最高分和最低分
= max(scores)
highest = min(scores)
lowest
# 返回包含所有分析结果的字典
return {
"name": name,
"total": total,
"average": average,
"highest": highest,
"lowest": lowest,
"pass": average >= 60 # 添加一个布尔值表示是否及格
}
# 分析一个学生的成绩
= analyze_student("小明", [85, 92, 78, 90, 88])
student_data print(f"学生 {student_data['name']} 的分析结果:")
print(f"总分: {student_data['total']}")
print(f"平均分: {student_data['average']:.1f}")
print(f"最高分: {student_data['highest']}")
print(f"最低分: {student_data['lowest']}")
print(f"是否及格: {'是' if student_data['pass'] else '否'}")
学生 小明 的分析结果:
总分: 433
平均分: 86.6
最高分: 92
最低分: 78
是否及格: 是
返回复杂数据结构的好处是可以在一次函数调用中传递大量相关信息,使代码更加组织化和易于维护。
9.5 变量的作用域
在上面的示例中,你有没有考虑过,如果我们在函数内部定义一个变量,那么这个变量在函数外部是否可用呢?
依旧以函数 calculate_loss()
为例,我们在函数内部定义了一个变量 difference
,如果我们想要在函数的外部使用这个变量,会发生什么呢?
# 定义计算损失的函数
def calculate_loss(predicted, actual):
# 计算差异
= predicted - actual
difference # 计算差异的平方
= difference ** 2
squared_difference # 计算损失
= squared_difference / 2
loss return loss
# 尝试访问函数内部的变量
try:
= calculate_loss(5, 2)
result print("差异是:", difference) # 这里会出错
print("模型的损失值是:", result)
except NameError as e:
print("错误:", e)
错误: name 'difference' is not defined
运行上面的代码,我们可以看到出现了 NameError
错误。这是因为在 Python 中,函数内部定义的变量只能在函数内部使用,无法在函数外部使用。
9.5.1 变量的作用域
变量的可用范围称为变量的作用域。在 Python 中,变量的作用域主要有两种:
- 全局作用域:在程序的主体部分定义的变量,可以在程序的任何地方使用。
- 局部作用域:在函数内部定义的变量,只能在定义它的函数内部使用。
就像一个人的名字只在特定的班级里有效一样,局部变量只在特定的函数内有效。
9.5.2 使用全局变量
如果我们想要让函数内部的变量在外部也能使用,我们可以使用 global
关键字将其定义为全局变量:
# 重新定义函数,将变量 difference 定义为全局变量
def calculate_loss(predicted, actual):
global difference # 声明这是一个全局变量
# 计算差异
= predicted - actual
difference # 计算差异的平方
= difference ** 2
squared_difference # 计算损失
= squared_difference / 2
loss return loss
# 现在可以访问 difference 变量了
= calculate_loss(5, 2)
result print("差异是:", difference)
print("模型的损失值是:", result)
差异是: 3
模型的损失值是: 4.5
9.5.3 在函数内部使用全局变量
同样地,如果我们在函数外部定义一个变量,这个变量在函数内部是可以直接使用的:
# 在函数外部定义变量
= 5
predicted
def calculate_loss(actual):
# 可以直接使用外部变量 predicted
= predicted - actual
difference = difference ** 2
squared_difference = squared_difference / 2
loss return loss
# 调用函数并输出结果
= calculate_loss(2)
result print("模型的损失值是:", result)
模型的损失值是: 4.5
9.5.4 修改全局变量
但是,如果我们想在函数内部修改全局变量,就会遇到一个问题:
# 定义全局变量
= 5
predicted
def calculate_loss(actual):
# 尝试修改全局变量
= 4 # 这实际上创建了一个新的局部变量
predicted print("函数内部 predicted 的值是:", predicted)
= predicted - actual
difference = difference ** 2
squared_difference = squared_difference / 2
loss return loss
# 调用函数
= calculate_loss(2)
result print("函数调用完成后 predicted 的值是:", predicted) # 全局变量没有被修改
函数内部 predicted 的值是: 4
函数调用完成后 predicted 的值是: 5
在上面的例子中,当我们在函数内部写 predicted = 4
时,Python 实际上创建了一个新的局部变量 predicted
,而不是修改全局变量。这就像在班级里有两个同名的同学,一个在教室里,一个在操场上,修改了教室里同学的成绩并不会影响操场上那个同学。
如果我们真的想在函数内部修改全局变量,需要使用 global
关键字:
# 定义全局变量
= 5
predicted
def calculate_loss(actual):
global predicted # 声明我们要使用全局变量
= 4 # 现在修改的是全局变量
predicted print("函数内部 predicted 的值是:", predicted)
= predicted - actual
difference = difference ** 2
squared_difference = squared_difference / 2
loss return loss
# 调用函数
= calculate_loss(2)
result print("函数调用完成后 predicted 的值是:", predicted) # 全局变量被修改了
函数内部 predicted 的值是: 4
函数调用完成后 predicted 的值是: 4
9.5.5 小结
- 局部变量:函数内部定义的变量,只能在函数内部使用
- 全局变量:函数外部定义的变量,可以在程序的任何地方使用
- global 关键字:用于在函数内部声明一个变量为全局变量,使其可以在函数外部访问或在函数内部修改
理解变量的作用域对于编写清晰、没有意外错误的代码非常重要!
9.6 help()
函数与函数的文档字符串
在编程过程中,我们常常需要了解函数的功能和使用方法。无论是使用别人创建的函数,还是自己编写的函数,清晰的文档都能大大提高工作效率。Python提供了一种称为”文档字符串”的机制,让我们能够为函数添加详细说明。
9.6.1 使用help()
函数查看文档
Python内置的help()
函数可以显示任何函数的帮助文档。使用时只需将函数名(不带括号)作为参数传入:
# 查看print()函数的帮助文档
help(print)
Help on built-in function print in module builtins:
print(*args, sep=' ', end='\n', file=None, flush=False)
Prints the values to a stream, or to sys.stdout by default.
sep
string inserted between values, default a space.
end
string appended after the last value, default a newline.
file
a file-like object (stream); defaults to the current sys.stdout.
flush
whether to forcibly flush the stream.
通过查看print()
函数的文档,我们可以发现它有许多可选参数,如sep
(分隔符)和end
(结尾字符)。这些参数可以帮助我们灵活控制输出格式:
# 使用sep和end参数自定义输出格式
print("Hello", "world", sep=", ", end="!")
Hello, world!
这个例子中,我们通过设置sep=", "
使各个参数之间用逗号和空格分隔,并通过end="!"
将结尾的换行符替换为感叹号。
9.6.2 为自定义函数添加文档字符串
当我们创建自己的函数时,添加文档字符串非常重要,尤其是当函数比较复杂或将被他人使用时。文档字符串使用三引号("""
或 '''
)定义,放在函数定义后的第一行:
def predict(slope, intercept, x_value):
"""
根据给定的斜率、截距和自变量值,计算简单线性回归模型的预测因变量值。
参数:
slope (float): 线性回归模型的斜率。
intercept (float): 线性回归模型的截距。
x_value (float): 需要进行预测的自变量值。
返回:
float: 根据线性回归模型计算出的预测因变量值。
示例:
>>> prediction = predict(2.5, 3.0, 5.0)
>>> print(prediction)
15.5
"""
= slope * x_value + intercept
result return result
# 查看我们的文档字符串
help(predict)
# 测试我们的函数
= predict(2.5, 3.0, 5.0)
prediction print("预测值:", prediction)
Help on function predict in module __main__:
predict(slope, intercept, x_value)
根据给定的斜率、截距和自变量值,计算简单线性回归模型的预测因变量值。
参数:
slope (float): 线性回归模型的斜率。
intercept (float): 线性回归模型的截距。
x_value (float): 需要进行预测的自变量值。
返回:
float: 根据线性回归模型计算出的预测因变量值。
示例:
>>> prediction = predict(2.5, 3.0, 5.0)
>>> print(prediction)
15.5
预测值: 15.5
9.6.3 文档字符串的规范
一个好的文档字符串通常包含:
- 函数功能的简要描述:在第一行说明函数的主要功能
- 参数说明:列出每个参数的名称、类型和作用
- 返回值说明:说明函数返回什么、返回值的类型
- 使用示例:提供简单的代码示例,展示如何使用该函数
- 注意事项:需要注意的特殊情况或限制条件(如果有)
9.6.4 为什么文档字符串很重要?
- 提高代码可读性:让其他人(包括未来的你)能够快速理解函数的用途
- 简化协作:团队成员可以通过文档了解如何使用你的函数
- 减少错误:清晰说明参数类型和作用,减少误用
- 支持自动生成文档:许多工具可以从文档字符串自动生成项目文档
即使是看似简单的函数,也值得添加文档字符串。这是一种良好的编程习惯,能够让你的代码更加专业和易于维护。
9.6.5 小结
- 使用
help(函数名)
可以查看任何函数的文档 - 文档字符串使用三引号定义,放在函数定义的第一行之后
- 好的文档字符串应包含函数功能、参数说明、返回值说明和使用示例
- 养成为自己创建的函数添加文档字符串的习惯
9.7 异常处理:程序的”安全气囊”
想象你在骑自行车,为了安全你会:
- 戴头盔(预防措施)
- 在摔倒时保护自己(处理意外)
- 爬起来继续骑(恢复运行)
在编程中,异常处理就像这样的安全措施!
9.7.1 什么是异常?
异常就是程序运行时遇到的”意外情况”。比如:
- 试图把”abc”转换成数字
- 试图除以0
- 试图访问不存在的文件
# 一些会导致异常的例子
try:
print("=== 一些常见的错误 ===")
= int("abc") # 这会失败
number except:
print("1. 不能把'abc'转成数字!")
try:
= 10 / 0 # 这会失败
result except:
print("2. 不能除以0!")
try:
= [90, 85, 88]
scores print(scores[10]) # 这会失败
except:
print("3. 不能访问列表中不存在的位置!")
=== 一些常见的错误 ===
1. 不能把'abc'转成数字!
2. 不能除以0!
3. 不能访问列表中不存在的位置!
9.7.2 如何处理异常
使用 try
和 except
语句,就像是给代码装上”安全气囊”:
def divide_numbers(a, b):
"""安全的除法函数"""
try:
= a / b
result return f"{a} 除以 {b} 等于 {result}"
except ZeroDivisionError:
return "除数不能为0!"
except TypeError:
return "请输入有效的数字!"
# 测试这个函数
print(divide_numbers(10, 2)) # 正常情况
print(divide_numbers(10, 0)) # 除以0
print(divide_numbers(10, "2")) # 类型错误
10 除以 2 等于 5.0
除数不能为0!
请输入有效的数字!
9.7.3 实际应用示例:成绩录入系统
def input_score(student_name):
"""安全的成绩录入函数"""
try:
= float(input(f"请输入{student_name}的成绩:"))
score
if 0 <= score <= 100:
return score
else:
print("成绩必须在0-100之间!")
return None
except ValueError:
print("请输入有效的数字!")
return None
def process_scores():
"""处理多个学生的成绩"""
= {}
scores = ["小明", "小红", "小华"]
students
for student in students:
# 这里我们使用模拟数据,而不是真正的输入
if student == "小明":
= 85
score elif student == "小红":
= "九十五" # 故意制造错误
score else:
= 120 # 故意制造错误
score
try:
if isinstance(score, str):
= float(score) # 尝试转换字符串为数字
score
if 0 <= score <= 100:
= score
scores[student] else:
print(f"{student}的成绩({score})无效,必须在0-100之间")
except ValueError:
print(f"{student}的成绩({score})无效,必须是数字")
return scores
# 测试成绩处理系统
print("\n=== 成绩录入测试 ===")
= process_scores()
valid_scores print("\n有效的成绩:", valid_scores)
=== 成绩录入测试 ===
小红的成绩(九十五)无效,必须是数字
小华的成绩(120)无效,必须在0-100之间
有效的成绩: {'小明': 85}
9.7.4 完整的异常处理结构
try:
# 可能出错的代码
pass
except ValueError:
# 处理特定类型的错误
pass
except TypeError:
# 处理另一种类型的错误
pass
else:
# 如果没有错误发生,执行这里
pass
finally:
# 无论是否有错误,都会执行这里
pass
9.7.5 常见的异常类型
ValueError:值的类型正确但不合适
int("abc") # 不能把字母转换成数字
TypeError:类型错误
"123" + 456 # 不能把字符串和数字相加
IndexError:索引超出范围
1, 2, 3][10] # 列表中没有第10个元素 [
ZeroDivisionError:除以零
10 / 0 # 不能除以0
9.7.6 实用技巧
- 验证用户输入:
def get_age():
"""安全地获取年龄"""
while True:
try:
= int(input("请输入你的年龄:"))
age if 0 <= age <= 120:
return age
else:
print("年龄必须在0-120之间!")
except ValueError:
print("请输入有效的数字!")
# print("开始输入年龄:")
# age = get_age() # 取消注释来测试
- 文件操作安全处理:
try:
with open("成绩单.txt", "r") as file:
= file.read()
content except FileNotFoundError:
print("找不到成绩单文件!")
找不到成绩单文件!
🌟 要点总结
- 异常处理让程序更健壮
- try-except 是基本的异常处理结构
- 可以处理不同类型的异常
- 要针对具体的异常类型进行处理
- 异常处理可以让程序优雅地处理错误
- 不要过度使用异常处理
- 优先使用条件语句预防错误