一、为什么需要函数?
- 代码复用:写一次,用无数次。
- 模块化:将复杂问题分解为小的、可管理的部分。
- 易于维护:修改一个功能,只需修改对应的函数。
- 提高可读性:好的函数名本身就是一种注释。
二、函数参数
函数大家应该都会,就不从头细讲了,说一些容易忽略的点
2.1 位置参数和关键字参数
python
def describe_person(name, age, city):
"""描述一个人的信息"""
print(f"姓名: {name}, 年龄: {age}, 城市: {city}")
# 混合使用:位置参数必须在关键字参数之前
describe_person("王五", city="广州", age=28) # 输出: 姓名: 王五, 年龄: 28, 城市: 广州
describe_person(name="王五", 28, city="广州") # 这会报错!
2.2 重要陷阱:可变默认参数
永远不要将可变对象(如列表、字典)作为默认参数!
python
# 错误示范 - 可变默认参数
def add_item_to_list(item, my_list=[]):
"""向列表中添加一个项目(错误示范)。"""
my_list.append(item)
return my_list
print(add_item_to_list(1)) # 输出: [1]
print(add_item_to_list(2)) # 输出: [1, 2] <- 意想不到!我们期望的是 [2]
print(add_item_to_list(3)) # 输出: [1, 2, 3] <- 问题持续恶化
原因: 默认参数 my_list=[] 在函数定义时只被创建一次 。后续所有调用,如果不提供 my_list,都会使用同一个列表对象。
正确做法: 使用 None 作为默认值,然后在函数内部创建新对象。
python
# 代码 2.4: 正确示范
def add_item_to_list_correctly(item, my_list=None):
"""向列表中添加一个项目(正确示范)。"""
if my_list is None:
my_list = [] # 每次调用都创建一个新的列表
my_list.append(item)
return my_list
print(add_item_to_list_correctly(1)) # 输出: [1]
print(add_item_to_list_correctly(2)) # 输出: [2]
print(add_item_to_list_correctly(3)) # 输出: [3]
2.3 可变长度参数
当不确定会传递多少个参数时,可变长度参数就派上用场了。
*args:接收任意数量的位置 参数,它们被组装成一个元组。**kwargs:接收任意数量的关键字 参数,它们被组装成一个字典。
python
# *args 和 **kwargs
def log_message(*args, **kwargs):
"""记录任意数量的消息和配置信息。"""
print("--- 位置参数 ---")
for arg in args:
print(f"- {arg}")
print("\n--- 关键字参数 ---")
for key, value in kwargs.items():
print(f"- {key}: {value}")
log_message("系统启动", "加载模块", level="INFO", user="admin")
2.4 参数解包
2.4.1 函数定义与参数
python
def introduce(name, age, city):
print(f"我叫{name},今年{age}岁,来自{city}。")
2.4.2 列表/元组解包 (*args)
接下来,我们看这段代码:
python
args = ["李四", 28, "广州"] # args 是一个列表
introduce(*args) # 使用 * 解包列表
args是一个列表 (或者也可以是元组),它包含了三个元素:"李四"、28、"广州"。introduce(*args)中的*是解包操作符。*args的作用是把列表args解开 ,把里面的元素依次 作为位置参数传递给introduce函数。- 所以,
introduce(*args)这行代码,等价于introduce("李四", 28, "广州")。 - 这样就成功地把列表里的数据传递给了函数,而不需要手动写三个参数。
2.4.3 字典解包 (**kwargs)
最后,我们看这段代码:
python
kwargs = {"name": "王五", "age": 32, "city": "深圳"} # kwargs 是一个字典
introduce(**kwargs) # 使用 ** 解包字典
kwargs是一个字典 。它的键 (key)分别是name,age,city,这正好与introduce函数的参数名完全一致。introduce(**kwargs)中的**是字典解包操作符。**kwargs的作用是把字典kwargs解开 ,把里面的键值对 作为关键字参数 (keyword arguments)传递给introduce函数。- 所以,
introduce(**kwargs)这行代码,等价于introduce(name="王五", age=32, city="深圳")。 - 同样,这也成功地把字典里的数据传递给了函数。
总结
- 普通调用 :
introduce("张三", 25, "北京")- 直接传入三个值。 - 列表/元组解包 (
*) :当数据存储在一个序列 (如列表或元组)中,并且顺序与函数参数顺序一致 时,可以使用*来解包,方便地将序列中的元素作为位置参数传递。 - 字典解包 (
**) :当数据存储在一个字典 中,并且字典的键与函数参数名一致 时,可以使用**来解包,方便地将字典中的键值对作为关键字参数传递。
这两种解包方式在处理动态数据或使代码更简洁时非常有用!
三、返回值
3.1 返回多个值
python
def calculate(a, b):
"""返回多个值(实际上是返回元组)"""
sum_result = a + b
difference = a - b
product = a * b
quotient = a / b if b != 0 else None
return sum_result, difference, product, quotient
result = calculate(10, 5)
print(result) # 输出: (15, 5, 50, 2.0)
# 解包多个返回值
sum_val, diff_val, prod_val, quot_val = calculate(8, 4)
print(f"和: {sum_val}, 差: {diff_val}, 积: {prod_val}, 商: {quot_val}")
在 Python 中,当在 return 语句后面用逗号 , 分隔多个值时,Python 会自动将它们打包成一个元组(tuple) 再返回。
所以,return sum_result, difference, product, quotient这行代码等价于:
python
return (sum_result, difference, product, quotient)
括号 () 是可选的,不写括号 Python 也会创建元组。
四、匿名函数
lambda 函数是一种匿名函数 (anonymous function)。它是一种非常简洁的创建简单函数 的方式,通常用于需要一个一次性、简单 的函数,而不想正式定义一个完整函数(使用 def)的场合。
4.1 lambda 函数的基本语法
python
lambda 参数1, 参数2, ... : 表达式
lambda:定义匿名函数的关键字。参数1, 参数2, ...:函数接收的参数,可以没有,也可以有一个或多个。::分隔参数和函数体。表达式:一个 表达式,lambda函数会自动返回这个表达式的结果 。注意:这里不能是语句 (如print,for,while等),只能是一个计算结果的表达式。
4.2 lambda 函数 vs 普通函数 (def)
普通函数 (def)
python
def add(x, y):
return x + y
result = add(3, 5)
print(result) # 输出: 8
lambda 函数
python
# 创建一个 lambda 函数,并将其赋值给变量 add
add = lambda x, y: x + y
result = add(3, 5)
print(result) # 输出: 8
- 我们将这个匿名函数赋值给了变量
add,然后就可以像普通函数一样调用它了。
4.3 排序中的应用
python
students = [("Alice", 88), ("Bob", 95), ("Charlie", 78)]
# 按照元组的第二个元素(成绩)进行排序
sorted_by_grade = sorted(students, key=lambda student: student[1])
print(sorted_by_grade) # 输出: [('Charlie', 78), ('Alice', 88), ('Bob', 95)]
sorted() 是 Python 的一个内置函数,它的作用是对一个可迭代对象(如列表)进行排序,并返回一个新的、已排序的列表。
它的基本语法是:
ini
sorted(要排序的列表, key=一个函数)
要排序的列表:在这里就是students。key=:这是一个关键字参数 。它的值是一个函数 。这个函数告诉sorted应该按照什么规则来排序。
五、作用域
5.1 局部作用域
python
def my_function():
local_var = "我是局部变量"
print(local_var) # 在函数内部可以访问
my_function()
# print(local_var) # 报错: NameError,在函数外部无法访问局部变量
5.2 全局作用域
python
global_var = "我是全局变量"
def access_global():
"""访问全局变量"""
print(global_var) # 可以读取全局变量
def modify_global():
"""修改全局变量"""
global global_var # 使用global关键字声明
global_var = "全局变量已被修改"
access_global() # 输出: 我是全局变量
modify_global()
access_global() # 输出: 全局变量已被修改
当 Python 在函数内部遇到 variable_name = value 这样的赋值语句时,它会默认 认为你想要创建一个局部变量 variable_name。
使用 global 关键字
-
global global_var这一行代码告诉 Python:- "嘿,Python,我在函数内部要使用的
global_var,不是 一个新的局部变量,而是那个在全局作用域 下定义的global_var。" - "当我对
global_var进行赋值时,请直接修改全局 的那个global_var。"
- "嘿,Python,我在函数内部要使用的
-
因此,
global_var = "全局变量已被修改"这行代码现在的作用是:- 修改 全局作用域下的
global_var变量的值。
- 修改 全局作用域下的
5.3 嵌套函数与非局部变量
python
def outer_function():
outer_var = 1
def inner_function():
nonlocal outer_var # 使用nonlocal关键字
outer_var = 2
inner_var = 3
print(f"内部函数: {outer_var}")
inner_function()
print(f"外部函数: {outer_var}")
# print(inner_var) # 报错: 无法访问内部函数的变量
outer_function()
5.4 闭包
python
def make_multiplier(factor):
"""创建乘法器函数"""
def multiplier(x):
return x * factor
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 输出: 10
print(triple(5)) # 输出: 15
闭包(Closure)的概念
当内部函数 multiplier 记住了它外部的变量 factor(即使 make_multiplier 已经运行结束),我们就说 multiplier 函数形成了一个闭包。
- 闭包 = 函数 + 函数定义时的环境(变量)
multiplier不仅是一个函数,它还"封闭"了factor这个变量。
这种模式叫做工厂模式 或函数工厂,它有很多用途,具体用途略~