第十章:AI进阶之---函数的定义与调用(二)
今天的课程中,我们学习了 Python 函数的定义与调用,这是 Python 编程中非常基础且重要的内容。重点回顾函数的定义与调用使用def关键字定义函数,包括参数列表、文档字符串、函数体和返回值。函数调用时传递实际参数,可以通过位置传递、关键字传递或混合传递。区分无返回值函数(返回 None)和有返回值函数的调用方式。参数传递机制对于不可变对象(如数字、字符串),传递的是值的副本,函数内部修改不
文章目录
五、函数作用域
5.1 作用域的概念
作用域是程序中定义的变量所存在的区域,它决定了变量在程序中的可见性和生命周期。在 Python 中,每个函数都创建了一个新的作用域,称为局部作用域,而函数外部的区域称为全局作用域。
变量的作用域规则:
-
变量在定义它的作用域内可见。
-
变量在定义它的作用域之外不可见,除非使用特殊关键字(如 global、nonlocal)。
-
当在一个作用域中引用变量时,Python 会按照特定的顺序查找变量,直到找到为止。
示例:
# 全局作用域
global_var = "I'm global"
def outer_function():
# 外部函数的局部作用域
outer_var = "I'm outer"
def inner_function():
# 内部函数的局部作用域
inner_var = "I'm inner"
print(global_var) # 可以访问全局变量
print(outer_var) # 可以访问外部函数的变量
print(inner_var) # 只能在内部函数中访问
inner_function()
# print(inner_var) # 这里会报错,因为inner_var在inner_function的作用域内
outer_function()
5.2 作用域链与 LEGB 规则
Python 中变量的查找遵循LEGB 规则,即按照以下顺序查找变量:
-
L(Local):当前函数的局部作用域。
-
E(Enclosing):外部嵌套函数的作用域(从内到外)。
-
G(Global):全局作用域。
-
B(Built-in):内置作用域(Python 内置的函数和变量)。
当在一个作用域中引用变量时,Python 会首先在当前局部作用域中查找,如果找不到,则在外层嵌套函数的作用域中查找,依此类推,最后在内置作用域中查找。
示例:
# 全局作用域
global_var = "Global"
def outer():
# 外部函数的局部作用域
outer_var = "Outer"
def inner():
# 内部函数的局部作用域
inner_var = "Inner"
# 按照LEGB规则查找变量
print(inner_var) # 首先在Local查找,找到inner_var
print(outer_var) # 在Local未找到,到Enclosing查找,找到outer_var
print(global_var) # 在Enclosing未找到,到Global查找,找到global_var
print(len) # 在Global未找到,到Built-in查找,找到len函数
inner()
outer()
5.3 global 和 nonlocal 关键字
在某些情况下,我们需要在函数内部修改全局变量或外部函数的变量,这时可以使用global
和nonlocal
关键字:
-
global
关键字:用于在函数内部声明变量为全局变量,这样函数内部对变量的修改会影响全局变量。 -
nonlocal
关键字:用于在嵌套函数内部声明变量为外部函数的局部变量,这样内部函数对变量的修改会影响外部函数的变量。
示例:
# global关键字示例
count = 0
def increment():
global count # 声明count为全局变量
count += 1
print(count)
increment() # 输出: 1
increment() # 输出: 2
# nonlocal关键字示例
def outer():
x = 10
def inner():
nonlocal x # 声明x为外部函数的变量
x += 5
print(x)
inner()
print(x) # 这里会输出15,因为inner函数修改了x
outer()
使用 global 和 nonlocal 的注意事项:
-
global
关键字可以声明多个变量,如global x, y
。 -
nonlocal
关键字只能在嵌套函数中使用。 -
应谨慎使用
global
和nonlocal
,过度使用会降低代码的可读性和可维护性。 -
在函数内部,如果对变量进行了赋值操作,Python 默认会将其视为局部变量,除非使用
global
或nonlocal
声明。
六、函数相关易错点
6.1 参数传递相关错误
在函数参数传递过程中,常见的错误包括:
错误 1:参数数量不匹配
# 错误示例:参数数量不足
def greet(name):
print(f"Hello, {name}!")
greet() # 错误:缺少1个位置参数
# 错误示例:参数数量过多
def add(a, b):
return a + b
add(1, 2, 3) # 错误:传递了3个位置参数,而函数只接受2个
错误原因:函数调用时传递的参数数量与函数定义的参数数量不一致。
解决方案:
-
检查函数定义和调用时的参数数量是否匹配。
-
使用默认参数或可变参数(*args、**kwargs)增加函数的灵活性。
错误 2:参数类型不匹配
# 错误示例:参数类型错误
def multiply(a, b):
return a * b
result = multiply("2", 3) # 虽然语法正确,但结果可能不是预期的
print(result) # 输出: "222",而不是6
错误原因:虽然参数数量正确,但参数的类型不符合函数的预期用途,导致逻辑错误。
解决方案:
-
在函数内部添加类型检查或转换。
-
使用函数注释(Python 3.5+)明确参数类型和返回值类型。
-
编写文档字符串说明参数的类型和用途。
错误 3:参数顺序错误
# 错误示例:参数顺序错误
def describe_person(name, age, city):
print(f"{name} is {age} years old and lives in {city}")
describe_person("30", "Alice", "New York") # 参数顺序错误
# 输出: 30 is Alice years old and lives in New York,逻辑错误
错误原因:使用位置传递时,参数顺序与函数定义不一致,导致逻辑错误。
解决方案:
-
当参数数量较多或顺序不明显时,使用关键字传递。
-
在函数调用时明确指定参数名称。
-
保持函数参数顺序的一致性和逻辑性。
6.2 作用域相关错误
在函数作用域方面,常见的错误包括:
错误 1:在函数内部修改全局变量未使用 global 关键字
# 错误示例:在函数内部修改全局变量未使用global
count = 0
def increment():
count += 1 # 错误:这里会报UnboundLocalError
print(count)
increment()
错误原因:在函数内部尝试修改全局变量count
,但未使用global
关键字声明,导致 Python 将count
视为局部变量,而局部变量count
在赋值前被引用。
解决方案:
-
在函数内部使用
global
关键字声明要修改的全局变量。 -
避免在函数内部修改全局变量,优先通过参数传递和返回值来实现数据交换。
错误 2:变量名遮蔽(Shadowing)
# 错误示例:变量名遮蔽
length = 10 # 全局变量
def calculate_area(width):
length = 5 # 局部变量,遮蔽了全局变量length
area = length * width
return area
print(calculate_area(4)) # 输出: 20,而不是40
错误原因:函数内部定义了与全局变量同名的局部变量,导致全局变量被遮蔽,函数使用的是局部变量。
解决方案:
-
避免在函数内部使用与全局变量同名的局部变量。
-
使用更具描述性的变量名,提高代码可读性。
-
当需要使用全局变量时,明确使用
global
关键字声明。
错误 3:闭包中的延迟绑定
# 错误示例:闭包中的延迟绑定
def create_multipliers():
multipliers = []
for i in range(1, 4):
multipliers.append(lambda x: x * i) # 这里的i是循环变量,在闭包中被延迟绑定
return multipliers
# 调用闭包函数
times2, times3, times4 = create_multipliers()
print(times2(2)) # 输出: 6,而不是4
print(times3(2)) # 输出: 6,而不是6
print(times4(2)) # 输出: 6,而不是8
错误原因:在循环中创建闭包时,内部函数引用的是循环变量i
,但由于闭包的延迟绑定特性,所有内部函数都引用了循环结束后i
的值(即 3)。
解决方案:
- 在循环中创建闭包时,通过默认参数强制绑定当前
i
的值:
def create_multipliers():
multipliers = []
for i in range(1, 4):
multipliers.append(lambda x, n=i: x * n) # 使用默认参数绑定当前i的值
return multipliers
times2, times3, times4 = create_multipliers()
print(times2(2)) # 输出: 4
print(times3(2)) # 输出: 6
print(times4(2)) # 输出: 8
6.3 默认参数相关错误
在使用函数默认参数时,常见的错误包括:
错误 1:使用可变对象作为默认参数
# 错误示例:使用可变对象作为默认参数
def add_to_list(item, lst=[]):
lst.append(item)
return lst
print(add_to_list(1)) # 输出: [1]
print(add_to_list(2)) # 输出: [1, 2],而不是预期的[2]
print(add_to_list(3)) # 输出: [1, 2, 3],而不是预期的[3]
错误原因:Python 中的默认参数在函数定义时计算一次,而不是每次调用时重新计算。对于可变对象(如列表),这意味着所有后续调用共享同一个默认参数对象,导致意外的副作用。
解决方案:
- 将可变对象的默认参数设置为
None
,并在函数内部进行检查和初始化:
def add_to_list(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
print(add_to_list(1)) # 输出: [1]
print(add_to_list(2)) # 输出: [2]
print(add_to_list(3)) # 输出: [3]
错误 2:默认参数值在函数定义时计算
# 错误示例:默认参数值在函数定义时计算
import time
def show_time(timestamp=time.time()):
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)))
# 第一次调用使用默认参数
show_time() # 输出函数定义时的时间
# 10秒后再次调用
time.sleep(10)
show_time() # 仍然输出函数定义时的时间,而不是当前时间
错误原因:默认参数值在函数定义时计算,而不是在函数调用时计算,导致默认参数值固定为函数定义时的值。
解决方案:
- 在函数内部动态获取需要的值,而不是使用默认参数:
import time
def show_time(timestamp=None):
if timestamp is None:
timestamp = time.time()
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)))
# 第一次调用使用默认值
show_time() # 输出当前时间
# 10秒后再次调用
time.sleep(10)
show_time() # 输出当前时间
七、函数的实际应用案例
7.1 代码复用案例:计算图形面积
假设我们需要在程序中多次计算不同图形的面积,包括矩形、三角形和圆形。通过将每个图形的面积计算封装成函数,我们可以实现代码的复用和逻辑的清晰组织。
示例代码:
# 定义计算矩形面积的函数
def rectangle_area(length, width):
"""计算矩形的面积"""
return length * width
# 定义计算三角形面积的函数
def triangle_area(base, height):
"""计算三角形的面积"""
return 0.5 * base * height
# 定义计算圆形面积的函数
def circle_area(radius):
"""计算圆形的面积"""
return 3.14159 * radius ** 2
# 使用这些函数进行计算
print("矩形面积:", rectangle_area(5, 3)) # 输出: 矩形面积:15
print("三角形面积:", triangle_area(4, 6)) # 输出: 三角形面积:12
print("圆形面积:", circle_area(2)) # 输出: 圆形面积:12.56636
代码分析:
-
每个图形的面积计算逻辑被封装在独立的函数中,提高了代码的可读性和可维护性。
-
当需要修改某个图形的面积计算公式时,只需要在对应的函数中修改一次。
-
可以方便地在其他部分的程序中复用这些函数。
7.2 逻辑封装案例:用户注册验证
假设我们需要实现一个用户注册功能,需要对用户输入的信息进行验证,包括用户名长度、密码强度、邮箱格式等。通过将这些验证逻辑封装成函数,可以使主程序更加简洁,逻辑更加清晰。
示例代码:
import random
def generate_secret_number():
"""生成一个1到100之间的随机数"""
return random.randint(1, 100)
def get_guess():
"""获取用户的猜测,并确保是有效的整数"""
while True:
guess = input("请输入你的猜测(1-100):")
if guess.isdigit():
return int(guess)
else:
print("错误:请输入一个有效的整数。")
def check_guess(guess, secret):
"""检查猜测是否正确,并返回提示信息"""
if guess < secret:
return "太小了!"
elif guess > secret:
return "太大了!"
else:
return "正确!"
def play_game():
"""主游戏函数"""
secret = generate_secret_number()
attempts = 0
max_attempts = 5
print("欢迎来到猜数字游戏!你有5次机会猜一个1到100之间的数字。")
while attempts < max_attempts:
guess = get_guess()
attempts += 1
result = check_guess(guess, secret)
print(result)
if result == "正确!":
print(f"恭喜你在第{attempts}次猜对了!")
return
print(f"很遗憾,你用完了所有机会。正确答案是{secret}。")
# 开始游戏
play_game()
代码分析:
-
每个验证逻辑被封装在独立的函数中,主程序只负责流程控制和用户交互。
-
每个验证函数都有明确的职责和返回值,便于测试和维护。
-
如果需要修改验证规则,只需要在对应的函数中修改,不会影响其他部分。
7.3 实战练习:猜数字游戏
现在,我们将综合运用函数的知识,实现一个猜数字游戏。游戏规则如下:
-
程序生成一个 1 到 100 之间的随机数。
-
用户有 5 次机会猜测这个数字。
-
每次猜测后,程序提示用户猜测的数字是太大、太小还是正确。
-
如果用户在 5 次内猜对,显示成功消息;否则,显示正确答案。
示例代码:
import random
def generate_secret_number():
"""生成一个1到100之间的随机数"""
return random.randint(1, 100)
def get_guess():
"""获取用户的猜测,并确保是有效的整数"""
while True:
guess = input("请输入你的猜测(1-100):")
if guess.isdigit():
return int(guess)
else:
print("错误:请输入一个有效的整数。")
def check_guess(guess, secret):
"""检查猜测是否正确,并返回提示信息"""
if guess < secret:
return "太小了!"
elif guess > secret:
return "太大了!"
else:
return "正确!"
def play_game():
"""主游戏函数"""
secret = generate_secret_number()
attempts = 0
max_attempts = 5
print("欢迎来到猜数字游戏!你有5次机会猜一个1到100之间的数字。")
while attempts < max_attempts:
guess = get_guess()
attempts += 1
result = check_guess(guess, secret)
print(result)
if result == "正确!":
print(f"恭喜你在第{attempts}次猜对了!")
return
print(f"很遗憾,你用完了所有机会。正确答案是{secret}。")
# 开始游戏
play_game()
代码分析:
-
游戏的各个功能被分解为多个函数,每个函数负责一个特定的任务。
-
generate_secret_number
负责生成随机数,get_guess
负责获取用户输入,check_guess
负责验证猜测,play_game
负责游戏流程控制。 -
这种模块化设计使得代码结构清晰,易于理解和维护。
-
每个函数都有明确的职责和返回值,便于测试和复用。
八、课程总结
今天的课程中,我们学习了 Python 函数的定义与调用,这是 Python 编程中非常基础且重要的内容。通过本节课的学习,你应该能够:
重点回顾:
- 函数的定义与调用:
-
使用
def
关键字定义函数,包括参数列表、文档字符串、函数体和返回值。 -
函数调用时传递实际参数,可以通过位置传递、关键字传递或混合传递。
-
区分无返回值函数(返回 None)和有返回值函数的调用方式。
- 参数传递机制:
-
对于不可变对象(如数字、字符串),传递的是值的副本,函数内部修改不影响外部变量。
-
对于可变对象(如列表、字典),传递的是对象的引用,函数内部修改会影响外部变量。
-
理解参数解包(* 和 ** 运算符)的使用方法。
- 函数作用域:
-
变量的作用域决定了其可见性和生命周期。
-
变量查找遵循 LEGB 规则:Local → Enclosing → Global → Built-in。
-
使用
global
和nonlocal
关键字修改全局变量和外部函数的变量。
- 函数相关易错点:
-
参数数量不匹配、类型不匹配和顺序错误。
-
作用域相关错误,如未使用
global
声明而修改全局变量。 -
默认参数的陷阱,尤其是使用可变对象作为默认参数。
- 函数的实际应用:
-
通过函数实现代码复用,如计算不同图形的面积。
-
通过函数封装逻辑,如用户注册验证。
-
综合运用函数实现复杂程序,如猜数字游戏。
课后任务:
-
复习本节课的内容,确保完全理解函数的定义、调用和参数传递机制。
-
完成课堂练习中的猜数字游戏,并尝试扩展其功能。
-
预习下一节课的内容:函数进阶(包括递归函数、匿名函数等)。
下一步学习建议:
-
练习将常用的代码块封装成函数,提高代码的复用性。
-
尝试使用不同类型的参数(如默认参数、可变参数)编写更灵活的函数。
-
学习使用文档字符串和函数注释,提高代码的可读性和可维护性。
-
尝试编写一些简单的递归函数,理解递归的基本思想。
函数是编程的核心概念,掌握函数的使用是成为一名优秀程序员的必经之路。通过不断练习和实践,你将能够更加熟练地使用函数来组织和管理代码,编写更高效、更清晰的程序。
如果你在学习过程中遇到任何问题,请随时提问。
更多推荐
所有评论(0)