📋 前言
各位伙伴们,大家好!从今天开始,我们将进入一个全新的专题------函数。如果说之前我们学习的变量、列表、循环是制造零件,那么函数就是教会我们如何设计和组装这些零件,构建出复杂而强大的机器。
在此之前,我们一直在 使用 函数,如 print()、len()、pd.read_csv()。今天,我们将掌握 创造 函数的能力。这不仅是代码复用的开始,更是我们程序设计思维的一次巨大飞跃。我们将学习:
- 如何定义自己的函数?
- 变量在函数内外的"生命周期"(作用域)。
- 如何设计灵活的函数参数,应对各种输入情况(位置参数、默认参数、可变参数)。
让我们一起揭开函数参数的"魔法",打造属于自己的函数工具箱!
一、函数的核心:定义与作用域
1.1 函数的定义
函数就是一段被命名、可重复使用的代码块,用于执行特定的任务。基本语法如下:
python
def function_name(parameter1, parameter2):
"""文档字符串 (Docstring): 解释函数的功能、参数和返回值"""
# 函数体 (indented code block)
result = parameter1 + parameter2
return result # 使用 return 关键字返回值
def: 定义函数的关键字。function_name: 函数名,遵循变量命名规则。parameters: 传入函数的输入值。return: 将函数执行的结果返回给调用者。如果省略,函数默认返回None。
1.2 变量作用域
- 局部变量 (Local Scope): 在函数内部定义的变量,只在该函数内部有效。函数执行结束,它就消失了。
- 全局变量 (Global Scope): 在所有函数之外定义的变量,在整个程序中都可访问。
原则:函数应尽量使用自己的局部变量和参数,避免修改全局变量,这会让代码更清晰、更易于维护。
二、参数的魔法:玩转函数输入
函数的强大之处在于其灵活性,而这份灵活性主要源于其多样的参数类型。
2.1 位置参数 (Positional Arguments)
最常见的参数类型,调用时必须按顺序提供值。
python
def greet(name, message):
print(f"Hello {name}, {message}")
greet("Alice", "Good morning!") # 'Alice' 对应 name, 'Good morning!' 对应 message
2.2 默认参数 (Default Arguments)
在定义函数时,可以为参数提供一个默认值。如果调用时不提供该参数,它就会使用这个默认值。
python
def greet(name, message="How are you?"):
print(f"Hello {name}, {message}")
greet("Bob") # message 使用默认值
greet("Charlie", "Nice to meet you!") # message 使用提供的新值
注意:默认参数必须放在所有位置参数的后面。
2.3 可变位置参数 (*args)
当你不知道函数会接收多少个位置参数时,可以使用 *args。它会将所有多余的位置参数收集到一个元组 (tuple) 中。
python
def calculate_sum(*numbers):
print(f"Received numbers: {numbers}") # numbers 是一个元组
return sum(numbers)
calculate_sum(1, 2, 3) # 输出: Received numbers: (1, 2, 3)
calculate_sum(10, 20, 30, 40, 50)
2.4 可变关键字参数 (**kwargs)
当你希望函数能处理任意数量的关键字参数时,使用 **kwargs。它会将这些参数收集到一个字典 (dict) 中。
python
def display_user_profile(**details):
print("User Profile:")
for key, value in details.items():
print(f" - {key.title()}: {value}")
display_user_profile(name="Eve", age=30, city="New York")
2.5 终极组合与顺序
当所有参数类型同时出现时,必须遵循以下严格的顺序:
位置参数, 默认参数, *args, **kwargs
python
def master_function(pos1, pos2, default1="default", *args, **kwargs):
print(f"Positionals: {pos1}, {pos2}")
print(f"Default: {default1}")
print(f"Args: {args}")
print(f"Kwargs: {kwargs}")
编程好习惯:尾随逗号 (Trailing Comma)
在函数调用、列表、字典中,最后一个元素后面加一个逗号是完全合法的,比如
model(source=0, stream=True,)。
好处:当未来增加新参数时,版本控制系统(如 Git)只会显示新增了一行,而不是修改了上一行,让代码变更历史更清晰。
三、作业实战:打造你的函数工具箱
现在,让我们用今天学到的知识完成所有练习题!
题目一:计算圆的面积 (位置参数 + 异常处理)
python
import math
def calculate_circle_area(radius):
"""
计算圆的面积,并处理无效的半径值。
"""
# 使用 try-except 增加健壮性
try:
# 检查半径是否为负数
if radius < 0:
print("错误:半径不能为负数。")
return 0
area = math.pi * radius**2
return area
except TypeError:
# 如果传入的不是数字类型,比如字符串
print("错误:半径必须是一个数字。")
return 0
# --- 测试 ---
print(f"半径为 5 的圆面积是: {calculate_circle_area(5):.2f}")
print(f"半径为 0 的圆面积是: {calculate_circle_area(0):.2f}")
print(f"半径为 -1 时返回: {calculate_circle_area(-1)}")
print(f"半径为 'text' 时返回: {calculate_circle_area('text')}")
输出:
半径为 5 的圆面积是: 78.54
半径为 0 的圆面积是: 0.00
错误:半径不能为负数。
半径为 -1 时返回: 0
错误:半径必须是一个数字。
半径为 'text' 时返回: 0
题目二:计算矩形的面积 (多个位置参数)
python
def calculate_rectangle_area(length, width):
"""计算矩形面积,并检查长宽是否为负。"""
if length < 0 or width < 0:
print("错误:长度和宽度都不能为负数。")
return 0
return length * width
# --- 测试 ---
print(f"长10宽5的矩形面积: {calculate_rectangle_area(10, 5)}")
print(f"长-10宽5的矩形面积: {calculate_rectangle_area(-10, 5)}")
输出:
长10宽5的矩形面积: 50
错误:长度和宽度都不能为负数。
长-10宽5的矩形面积: 0
题目三:计算任意数量数字的平均值 (*args)
python
def calculate_average(*args):
"""使用 *args 计算任意数量数字的平均值。"""
# 处理没有输入任何数字的情况
if not args:
return 0
total = sum(args)
return total / len(args)
# --- 测试 ---
print(f"1, 2, 3 的平均值是: {calculate_average(1, 2, 3)}")
print(f"10, 20, 30, 40 的平均值是: {calculate_average(10, 20, 30, 40)}")
print(f"没有任何输入的平均值是: {calculate_average()}")
输出:
1, 2, 3 的平均值是: 2.0
10, 20, 30, 40 的平均值是: 25.0
没有任何输入的平均值是: 0
题目四:打印用户信息 (**kwargs)
python
def print_user_info(user_id, **kwargs):
"""打印用户ID和任意数量的额外信息。"""
print(f"User ID: {user_id}")
if kwargs:
print("Additional Info:")
for key, value in kwargs.items():
print(f" - {key}: {value}")
else:
print("No additional info provided.")
# --- 测试 ---
print_user_info("user123", name="Alice", age=30)
print("-" * 20)
print_user_info("user456", city="London", status="active", role="admin")
print("-" * 20)
print_user_info("user789")
输出:
User ID: user123
Additional Info:
- name: Alice
- age: 30
--------------------
User ID: user456
Additional Info:
- city: London
- status: active
- role: admin
--------------------
User ID: user789
No additional info provided.
题目五:格式化几何图形描述 (终极组合)
python
def describe_shape(shape_name, color="black", **kwargs):
"""
生成几何图形的描述字符串,结合了位置、默认和可变关键字参数。
"""
# 描述尺寸的部分
if kwargs:
# 使用列表推导式和 join 方法优雅地格式化 kwargs
dims_parts = [f"{key}={value}" for key, value in kwargs.items()]
dims_str = " with dimensions: " + ", ".join(dims_parts)
else:
dims_str = " with no specific dimensions."
# 组合成最终的描述
description = f"A {color} {shape_name}{dims_str}"
return description
# --- 测试 (与题目要求完全一致) ---
desc1 = describe_shape("circle", radius=5, color="red")
print(desc1)
desc2 = describe_shape("rectangle", length=10, width=4)
print(desc2)
desc3 = describe_shape("triangle", base=6, height=8, color="blue")
print(desc3)
desc4 = describe_shape("point", color="green")
print(desc4)
输出:
A red circle with dimensions: radius=5
A black rectangle with dimensions: length=10, width=4
A blue triangle with dimensions: base=6, height=8
A green point with no specific dimensions.
四、总结与心得
Day 26 的学习是编程思维的一次重要转变。
- 从"重复"到"抽象" :我不再需要为计算不同尺寸的矩形面积而复制粘贴代码。通过定义一个
calculate_rectangle_area函数,我将"计算矩形面积"这个概念抽象了出来,实现了代码复用。 - 拥抱"不确定性" :
*args和**kwargs给予了函数极大的灵活性。describe_shape函数完美地展示了这一点,它不关心一个图形到底需要多少个维度参数(半径、长、宽、高...),它只负责接收并优雅地展示它们。这正是编写通用、可扩展接口的核心思想。 - 健壮性是一种习惯:结合 Day 25 的异常处理,我们可以在函数内部构建"安全区",使得函数在面对非预期输入时(如负半径),能给出友好反馈而不是程序崩溃。
- 代码即"文档" :一个命名良好、参数清晰的函数本身就是最好的文档。当我看到
calculate_average(*args)时,我立刻就能理解它的用途。
函数是组织代码、管理复杂度的第一道防线。今天掌握的参数技巧,将成为我们未来构建任何复杂程序(包括机器学习模型和数据处理流程)的坚实基础。
再次感谢 @浙大疏锦行 老师设计的这套由浅入深的练习,让我对函数的理解从"知道"变成了"掌握"。