如果在程序的不同地方,都需要对数据进行相同的清洗或计算(如计算商品打折后的总价、求一组数字的平均值),不断复制粘贴相同代码会导致程序冗长且难以维护。
一旦计算逻辑需要修改,你就必须手动修改几十处重复的代码,极易产生遗漏报错。
函数 (Function) 就是解决这个问题的金钥匙。它可以将实现特定功能的代码打包成一个独立的模块,赋予其一个名字。在需要时,只需调用名字即可重复使用这段代码。
完成本章学习后,你将能够:
def 定义函数与使用 return 返回计算结果的标准语法;*args 与 **kwargs 参数打包与解包的工作原理;global 关键字;在 Python 中,使用 def 关键字来定义一个函数。
def 函数名(参数1, 参数2, ...):
"""函数文档说明(可选)"""
# 函数体代码
return 返回值
# 1. 无参数无返回值的简单函数
def greet():
print("欢迎来到 Python 编程世界!")
greet() # 调用函数
# 2. 有参数有返回值的计算函数
def add_numbers(a, b):
result = a + b
return result # 向调用者返回计算结果
sum_val = add_numbers(10, 20)
print(f"计算结果: {sum_val}") # 输出: 计算结果: 30
为了让函数适应不同的场景,Python 提供了多种传参方式。
def describe_person(name, age):
print(f"姓名: {name}, 年龄: {age}")
# 1. 位置参数:按照形参位置顺序一一对应传入
describe_person("小明", 18)
# 2. 关键字参数:明确指定 参数名=数值,无需关心传入顺序
describe_person(age=20, name="小红")
可以为形参设定默认值。当调用者没有传入该参数时,函数会自动使用默认值:
def calculate_price(price, discount=0.9):
"""计算折扣价格,默认给予 9 折优惠"""
return price * discount
print(calculate_price(100)) # 未传 discount,使用默认 9 折,输出: 90.0
print(calculate_price(100, 0.8)) # 传入 0.8,使用 8 折,输出: 80.0
语法限制:在定义函数时,默认参数必须放在非默认参数的后面。如
def func(a, b=10)是合法的,但def func(b=10, a)会报SyntaxError。
在定义函数时,切记不要使用可变对象(如列表 [] 或字典 {})作为默认参数!
点击放大查看图:可变默认参数陷阱图解
# 错误示范:默认参数使用可变列表 []
def add_item_bad(item, target_list=[]):
target_list.append(item)
return target_list
# 多次调用会共享同一个默认列表!
print(add_item_bad("苹果")) # 输出: ['苹果']
print(add_item_bad("香蕉")) # 输出: ['苹果', '香蕉'] (产生了意外累加副作用!)
# 正确写法:使用 None 作为默认值标志
def add_item_good(item, target_list=None):
if target_list is None:
target_list = [] # 在函数内部创建新列表
target_list.append(item)
return target_list
如果你在定义函数时,无法提前确定调用者会传入多少个参数,可以使用 *args 和 **kwargs。
点击放大查看图:*args 与 **kwargs 参数打包解包图解
*args:收集多余的位置参数为元组*args 用于接收任意数量的非关键字位置参数,在函数内部会被打包为一个元组 (tuple):
def calculate_sum(*args):
# args 是一个包含了所有传入位置参数的元组
total = 0
for num in args:
total += num
return total
print(calculate_sum(1, 2, 3)) # 输出: 6
print(calculate_sum(10, 20, 30, 40)) # 输出: 100
**kwargs:收集多余的关键字参数为字典**kwargs 用于接收任意数量的 key=value 关键字参数,在函数内部会被打包为一个字典 (dict):
def print_user_info(**kwargs):
# kwargs 是一个包含了所有关键字参数的字典
for key, value in kwargs.items():
print(f"{key}: {value}")
print_user_info(name="张三", role="管理员", score=98)
作用域 (Scope) 决定了变量在代码的哪些区域是可以被访问到的。
total_count = 100 # 全局变量
def my_func():
local_val = 10 # 局部变量
print(total_count) # 可以读取全局变量
my_func()
# print(local_val) # 报错 NameError!在函数外部无法访问局部变量
global 关键字如果在函数内部尝试对全局变量重新赋值,Python 会默认创建一个同名的局部变量。如果想要直接修改全局变量,需要使用 global 声明:
counter = 0
def increment():
global counter # 声明使用全局变量 counter
counter += 1
increment()
print(counter) # 输出: 1
当你在代码中引用一个变量时,Python 解释器会严格按照 LEGB 的顺序向上逐层查找变量:
点击放大查看图:LEGB 作用域查找顺序图解
len(), print(), str)。高质量的代码不仅要能正确运行,还要易于阅读和维护。
一个良好的函数应当遵循单一职责原则(只做一件事)。使用三引号 """...""" 在函数首行编写 Docstring,能让团队成员及 IDE 快速明白函数的用途与参数约束:
def calculate_discount_price(original_price: float, discount_rate: float = 0.9) -> float:
"""计算商品打折后的实际应付金额。
Args:
original_price: 商品原价 (必须大于 0)
discount_rate: 折扣率,默认为 0.9 (9 折)
Returns:
折后实际金额 (float)
"""
return original_price * discount_rate
在处理异常或边界条件时,推荐使用卫语句:优先拦截非法输入并用 return 或 raise 提前退出,避免深层嵌套的 if-else:
# 不推荐:深层嵌套 if-else
def process_score_bad(score):
if score is not None:
if 0 <= score <= 100:
return score * 1.1
else:
print("分数超出范围")
else:
print("分数不能为空")
# 推荐:卫语句提前拦截,逻辑清晰
def process_score_good(score):
if score is None:
print("分数不能为空")
return None
if not (0 <= score <= 100):
print("分数超出范围")
return None
return score * 1.1
Python 是一门动态类型语言。在传统的 Python 代码中,声明变量或定义函数参数时无需指定数据类型。这带来了极高的书写自由度,但也留下了隐患:
随着代码规模扩大,当你阅读他人编写的函数 process(data) 时,无法一眼判断 data 应该传 list、dict 还是 str,在调用其方法时非常容易出现 AttributeError 或 TypeError 运行期崩溃。
为了解决这一痛点,Python 3.5 引入了静态类型标注 (Type Hints) 机制,允许我们在定义变量和函数时显式地标注“期待的数据类型”。
user_ids: list[int] 后,编辑器 (VS Code / PyCharm) 就能立刻知道 user_ids 是一个列表,并在你敲下 user_ids. 时精准弹出列表的所有方法提示;mypy 或 VS Code 的 Pylance 插件,能在代码运行之前就检测出“企图向要求传入 int 的函数传入 str”的类型错误。在变量名或形参名后使用 : 类型,在函数形参列表后使用 -> 返回值类型:
# 变量类型标注
age: int = 18
name: str = "小明"
is_active: bool = True
# 函数参数与返回值标注
def calculate_bmi(height: float, weight: float) -> float:
"""计算身高体重指数 BMI"""
return weight / (height ** 2)
list, dict, tuple, set)对于组合容器,可以使用方括号 [] 进一步明确容器内部元素的类型:
| 标注语法 | 含义说明 | 示例代码 |
|---|---|---|
list[T] | 内部元素全为类型 T 的列表 | scores: list[int] = [85, 90, 95] |
dict[K, V] | 键为 K 类型、值为 V 类型的字典 | user_scores: dict[str, float] = {"小明": 92.5} |
tuple[T1, T2, ...] | 包含指定位置类型元素的元组 | point: tuple[int, int] = (10, 20) |
set[T] | 内部元素全为类型 T 的集合 | tags: set[str] = {"python", "ai"} |
def process_user_data(user_list: list[dict[str, Any]]) -> dict[str, int]:
"""处理用户列表数据,返回名称与年龄映射字典"""
result = {}
for user in user_list:
result[user["name"]] = user["age"]
return result
Union / | / Optional)在实际开发中,一个变量或函数返回值可能属于多种类型,或者可能返回 None:
|(推荐):使用 A | B 表示类型属于 A 或 B;typing 模块导入 Union 或 Optional。# 1. 多类型联合:既可以接收整数也可以接收浮点数
def parse_number(val: int | float) -> float:
return float(val)
# 2. 可选类型:可能返回字符串,也可能返回 None
def find_user_email(user_id: int) -> str | None:
if user_id == 101:
return "xiaoming@example.com"
return None # 找不到用户时返回 None
# 3. Python 3.7 - 3.9 的兼容写法
from typing import Optional, Union
def legacy_find_user(user_id: int) -> Optional[str]:
# Optional[str] 等价于 str | None
return "xiaoming@example.com" if user_id == 101 else None
Any 与 Callablefrom typing import Any, Callable
# Any: 放弃类型检查,表示可以是任意类型
def log_raw_data(data: Any) -> None:
print(f"日志记录: {data}")
# Callable[[参数类型列表], 返回值类型]: 表示传入的参数本身是一个函数
def apply_operation(x: int, y: int, func: Callable[[int, int], int]) -> int:
return func(x, y)
def add(a: int, b: int) -> int:
return a + b
print(apply_operation(3, 4, add)) # 输出: 7
理解类型标注在 Python 中的执行机制非常关键:
Python 的 Type Hints 属于静态代码提示。Python 解释器在运行时默认不会强行拦截类型不匹配的代码。 例如:即使你定义了 def add(x: int) -> int,当传入 add("abc") 时,Python 解释器在启动运行该行之前不会直接报 TypeError 崩溃。 静态类型标注的使命在于:在编码阶段让 IDE 和类型检查工具 (mypy) 捕获错误,以及生成清晰的工程接口文档。如果需要在运行时强行校验数据类型,可结合 FastAPI 或 Pydantic 等第三方框架。
对于逻辑极其简单、只有一行的临时函数,可以使用 lambda 表达式 简化书写。
lambda 参数1, 参数2 : 单行表达式
# 传统函数写法
def square(x):
return x ** 2
# 简化为 lambda 表达式
square_lambda = lambda x: x ** 2
print(square_lambda(5)) # 输出: 25
# 常用场景:配合列表 sort() 的 key 参数按特定规则排序
students = [("小明", 88), ("小红", 95), ("小刚", 72)]
students.sort(key=lambda student: student[1], reverse=True) # 按成绩降序
print(students) # 输出: [('小红', 95), ('小明', 88), ('小刚', 72)]
点击放大查看图:模块化 AI 数据预处理与评估流水线架构图
请在下方的代码块中,点击 “运行代码”,体验使用 Type Hints、Docstring 与函数解耦搭建的数据处理流水线:
| 错误现象 / 异常类型 | 常见原因 | 标准排查与处理方式 |
|---|---|---|
TypeError: func() missing 1 required positional argument | 调用函数时未提供必须的位置参数 | 检查函数定义中的必填参数,补齐缺少的实参 |
SyntaxError: non-default argument follows default argument | 定义函数时,默认参数写在了普通位置参数的前面 | 重新调整参数顺序,将所有带默认值的参数(如 b=10)统一写在末尾 |
UnboundLocalError: local variable 'x' referenced before assignment | 在函数内部对全局变量进行了修改赋值,但忘记写 global x | 如果需要在函数内修改全局变量,必须在函数首行添加 global 变量名 声明 |
| 函数多次调用时列表产生意外累加 | 默认参数使用了可变对象 [] 或 {} | 将默认参数设为 None,并在函数内部通过 if arg is None: 初始化新列表 |
def 定义带参数与 return 返回值的函数;[] 的危险性,并能使用 None 解决累加陷阱;*args 打包位置参数,使用 **kwargs 打包关键字参数;x: int -> str 静态类型标注 (Type Hints) 基础语法。共 4 道精选测试题 · 答题进度已自动保存
恭喜你掌握了 Python 函数封装、设计规范与类型标注!
下一步我们将深入学习 06. 模块、包与面向对象基础,掌握如何使用 import 导入官方与第三方标准库,以及面向对象类与对象的基本概念。