第 12 课 - 返回值与作用域(LEGB)
课程模块: 模块三:函数 难度: Beginner 标签: functions, return, scope 上一课: 函数参数(位置、关键字、默认值) 下一课: Lambda 表达式与高阶函数
1. 学习目标
通过学习本课,你将能够:
- 熟练使用
return语句从函数中返回一个或多个值。 - 理解 Python 作用域的概念,并区分局部变量和全局变量。
- 掌握变量查找的 LEGB 规则,解释为什么同名变量在不同地方会得到不同结果。
- 避免在函数内部错误地修改全局变量。
- 利用返回值将函数作为"黑盒"组件,构建更清晰、模块化的代码。
2. 核心概念
2.1 函数的"快递打包":返回值
函数不仅仅是为了执行一段代码,更像一个"加工厂"。我们给它原料(参数),它处理完后,需要把结果(产品)"打包"交还给我们。这个"打包"和"交还"的动作,就由 return 语句完成。
- 没有返回值的函数:像一个没有产出的任务,执行完就结束了。
- 有返回值的函数:执行后,会生成一个结果。这个结果可以是一个数字、一个字符串、一个列表,甚至是另一个函数。
2.2 变量的"房间":作用域 (Scope)
在 Python 中,变量就像放在不同"房间"里的东西。一个变量属于哪个"房间",决定了它在程序的哪些部分可以被访问。这就是作用域。
- 局部作用域 (Local): 函数内部定义的变量,只在本函数内可见。就像你卧室里的东西,只有你能轻易拿到。
- 全局作用域 (Global): 在函数外部(通常是文件顶层)定义的变量,在整个文件的任何地方都可见。就像客厅里的东西,家里人都能看到。
2.3 变量的"寻宝地图":LEGB 规则
当在函数内使用一个变量名时,Python 会按照一个特定的顺序去查找它。这个顺序被称为 LEGB 规则,非常关键:
- L (Local): 先在当前函数的局部作用域内查找。
- E (Enclosing): 如果在嵌套函数中,则在外层函数的局部作用域内查找。
- G (Global): 在全局作用域(模块层级)内查找。
- B (Built-in): 最后在 Python 内置的名称中查找(如
len,print)。
如果这四个层级都找不到,就会抛出 NameError。
3. 代码示例
示例 1:返回值的基础用法
def calculate_rectangle_area(length, width):
"""计算并返回矩形的面积。"""
area = length * width
return area # 将计算结果"打包"返回
# 调用函数,并用一个变量来接收返回值
my_area = calculate_rectangle_area(5, 3)
print(f"矩形的面积是:{my_area}") # 输出:矩形的面积是:15
# 返回值可以直接用于其他操作
total_cost = calculate_rectangle_area(4, 2) * 10 # 假设每平方单位10元
print(f"总成本是:{total_cost}") # 输出:总成本是:80
示例 2:返回多个值
Python 可以轻松返回多个值,本质上是返回一个元组。
def get_user_info():
"""模拟获取用户信息,并返回姓名和年龄。"""
name = "Alice"
age = 25
return name, age # 返回一个元组 (name, age)
# 使用多个变量接收返回值
user_name, user_age = get_user_info()
print(f"姓名:{user_name}, 年龄:{user_age}") # 输出:姓名:Alice, 年龄:25
# 也可以用一个变量接收整个元组
info = get_user_info()
print(info) # 输出:('Alice', 25)
示例 3:LEGB 作用域演示
# 全局变量
global_var = "我在全局作用域 (G)"
def outer_function():
# 外层函数的局部变量 (Enclosing)
outer_var = "我在外层函数作用域 (E)"
def inner_function():
# 内层函数的局部变量 (Local)
local_var = "我在内层函数作用域 (L)"
# 此处可以访问 local_var, outer_var, global_var, 以及内置的 len
print(local_var)
print(outer_var) # 符合 E 规则
print(global_var) # 符合 G 规则
print(len("内置函数")) # 符合 B 规则
inner_function()
# print(local_var) # 错误!local_var 在此处不可见
outer_function()
# print(outer_var) # 错误!outer_var 在此处不可见
4. 实践练习
练习 1:基础返回
编写一个函数 circle_area(radius),接受圆的半径作为参数,计算并返回其面积(使用公式 area = 3.14159 * radius ** 2)。
- 调用函数,计算半径为 4 的圆的面积,并打印结果。
- 预期输出:
圆的面积是:50.26544
练习 2:返回多个值
编写一个函数 analyze_numbers(a, b),接受两个数字,返回它们的和、差(第一个减第二个)、乘积。
- 调用函数,传入数字 10 和 3,并分别打印出和、差、积。
- 预期输出:
和:13 差:7 积:30
练习 3:作用域探险
以下代码的输出是什么?请先自己思考,再运行验证。
x = 10
def func1():
x = 20
print(f"func1 内部,x 的值是:{x}")
def func2():
print(f"func2 内部,x 的值是:{x}")
def func3():
global x # 声明我们要修改全局变量 x
x = 30
print(f"func3 内部,x 的值是:{x}")
func1()
func2()
func3()
print(f"函数调用结束后,全局 x 的值是:{x}")
- 思考并运行,解释每一行输出的原因。
5. 常见错误
-
忘记返回值:
def add(a, b): result = a + b # 忘记写 return result sum_val = add(2, 3) print(sum_val) # 输出:None,因为函数没有返回任何东西。修正: 函数需要对外输出结果时,务必使用
return。 -
在函数内错误地修改全局变量:
count = 0 def increment(): count = count + 1 # 错误!Python 会认为这行代码创建了一个新的局部变量 count,但初始化时引用了未赋值的局部变量。 # 报错:UnboundLocalError: local variable 'count' referenced before assignment修正: 如果确需在函数内修改全局变量,使用
global关键字声明:global count。但通常建议避免这么做,因为它会让代码状态变得难以追踪。更好的做法是将变量作为参数传入,并将新值返回。 -
混淆返回和打印:
def greet(name): print(f"Hello, {name}!") # 这只是打印到屏幕,函数本身没有返回值。 message = greet("Alice") # message 的值是 None print(message + " Welcome!") # 报错:TypeError,因为 None 不能和字符串相加。修正: 明确函数目的。如果函数是为了生成一个供后续程序使用的结果,请用
return。打印(print)只是让用户看到,不改变程序数据。
6. 小结
return是函数的生命线: 它决定了函数"产出"什么,是函数与外界交换数据的核心方式。可以返回单个值,也可以返回用逗号分隔的多个值(元组)。- 作用域决定变量的可见性: 函数内部是"小房间"(局部作用域),函数外部是"大客厅"(全局作用域)。
- LEGB 是变量查找的顺序图: 记住这个顺序,你就能解释大部分关于变量可见性的问题。它遵循"由内而外,就近原则"。
- 最佳实践: 优先使用返回值和参数来在函数间传递数据,而不是依赖全局变量。这样代码更清晰、更易维护和调试。
理解返回值和作用域,是让你编写的函数从"执行命令"升级为"构建模块"的关键一步。现在,你的函数不仅能做事,还能有条理地汇报结果,并清晰地管理自己的"内部物品"了。