Skip to content

Python 3 - 函数

函数是命名的可重用代码块,旨在执行特定任务。它们对于构建应用程序、改进代码组织以及促进代码重用(遵循 DRY 原则 - Don’t Repeat Yourself,即不要重复自己)至关重要。

Python 提供了许多内置函数(如 print()、len()、type()),但你也可以定义自己的函数,称为用户定义函数。

在 Python 中定义函数的主要规则:

  • 以关键字 def 开头,后跟函数名和圆括号 ()。函数名遵循与变量名相同的规则(字母、数字、下划线;不能以数字开头)。按照惯例,函数名使用 snake_case(蛇形命名法)。
  • 函数接受的输入参数(arguments)放在圆括号内,用逗号分隔。
  • 函数头行以冒号 : 结束。
  • 冒号后面的缩进代码块是函数的主体(suite)。
  • 可选:函数体中的第一条语句可以是一个字符串字面量,称为 docstring(文档字符串),用三重引号 """Docstring goes here""" 括起来。它描述了函数的作用。
  • 使用 return 语句退出函数,并可选地向调用者返回一个值(或多个值)。不带表达式的 return 返回 None。如果函数体中没有 return 语句,函数执行到末尾时会隐式返回 None。
def function_name(parameter1, parameter2, ...):
"""可选的文档字符串,解释函数的功能。"""
# 函数体(缩进代码)
statement1
statement2
...
return [value] # 可选的 return 语句

类型提示(可选但推荐):现代 Python 鼓励为参数和返回值添加类型提示,以提高代码清晰度和工具支持(如静态分析)。默认情况下,它们不影响运行时行为。

def greet(name: str) -> None:
"""根据名字问候用户。
参数:
name: 被问候的人的名字。
"""
print(f"Hello, {name}!")
def add(a: int, b: int) -> int:
"""返回两个整数的和。"""
return a + b
# 定义函数
def print_message(message: str):
"""将提供的消息打印到控制台。"""
print(message)
# 隐式返回 None

定义函数只是创建了它。要执行其中的代码,你必须调用该函数,方法是使用其名称后跟圆括号,并提供所有必需的参数。

#!/usr/bin/env python3
# 函数定义
def print_message(message: str):
"""将提供的消息打印到控制台。"""
print(message)
# --- 调用函数 ---
print_message("This is the first call!")
print_message("This is the second call.")
result = print_message("Calling and assigning result...") # 函数执行
print(f"The function returned: {result}") # 输出: None

输出:

This is the first call!
This is the second call.
Calling and assigning result...
The function returned: None

Python 的参数传递机制通常被称为“按对象引用传递”或“按赋值传递”。这意味着:

  • 当你将一个对象(如数字、字符串、列表)传递给函数时,函数参数会成为指向该相同对象的新引用(名称)。
  • 如果传递的对象是可变的(例如,列表、字典),则在函数内部对对象内容所做的修改会影响函数外部的原始对象。
  • 如果你在函数内部将参数变量重新赋值给一个新对象,这只会改变函数内部的局部引用;它不会影响函数外部的原始变量。

示例 1:修改可变对象(列表)

#!/usr/bin/env python3
def modify_list(some_list: list):
"""向传递的列表中添加一个元素。"""
print(f"Inside function (before change): {some_list}, ID: {id(some_list)}")
some_list.append(100) # 修改原始列表对象
print(f"Inside function (after change): {some_list}, ID: {id(some_list)}")
my_list = [10, 20, 30]
print(f"Outside function (before call): {my_list}, ID: {id(my_list)}")
modify_list(my_list)
print(f"Outside function (after call): {my_list}, ID: {id(my_list)}")

输出(ID 会有所不同,但显示使用的是同一个列表对象):

Outside function (before call): [10, 20, 30], ID: <某个 ID>
Inside function (before change): [10, 20, 30], ID: <某个 ID>
Inside function (after change): [10, 20, 30, 100], ID: <某个 ID>
Outside function (after call): [10, 20, 30, 100], ID: <某个 ID>

示例 2:重新赋值参数

#!/usr/bin/env python3
def reassign_list(some_list: list):
"""将参数重新赋值为一个新列表。"""
print(f"Inside function (before reassign): {some_list}, ID: {id(some_list)}")
some_list = [1, 2, 3, 4] # 创建一个新的局部列表
print(f"Inside function (after reassign): {some_list}, ID: {id(some_list)}")
my_list = [10, 20, 30]
print(f"Outside function (before call): {my_list}, ID: {id(my_list)}")
reassign_list(my_list)
print(f"Outside function (after call): {my_list}, ID: {id(my_list)}") # 原始列表未更改

输出(ID 会有所不同,但显示参数在重新赋值后获得了新的 ID):

Outside function (before call): [10, 20, 30], ID: <原始 ID>
Inside function (before reassign): [10, 20, 30], ID: <原始 ID>
Inside function (after reassign): [1, 2, 3, 4], ID: <新 ID>
Outside function (after call): [10, 20, 30], ID: <原始 ID>

Python 在参数如何传递给函数方面提供了灵活性:

  • 位置参数(Positional Arguments): 按照函数签名中定义的顺序传递。参数数量必须匹配,除非指定了默认值。
  • 关键字参数(Keyword Arguments): 使用 parameter_name=value 语法传递。顺序无关紧要,并且可以与位置参数混合使用(位置参数必须在前)。
  • 默认参数(Default Arguments): 在函数定义中为参数指定默认值(def func(param=default):)。如果调用者没有为该参数提供值,则使用默认值。
  • 可变长位置参数(*args): 将任何额外的、未被匹配的位置参数收集到一个元组中。
  • 可变长关键字参数(**kwargs): 将任何额外的、未被匹配的关键字参数收集到一个字典中。

更高级(Python 3+):仅位置参数 (/) 和仅关键字参数 (*) 强制规定了参数的传递方式。

这些参数必须按照正确的顺序提供。

#!/usr/bin/env python3
def describe_pet(animal_type: str, pet_name: str):
print(f"I have a {animal_type} named {pet_name}.")
describe_pet("hamster", "Harry") # 正确的调用
# describe_pet("Whiskers") # TypeError: missing 1 required positional argument: 'pet_name'

使用关键字时,顺序不重要。

#!/usr/bin/env python3
def describe_pet(animal_type: str, pet_name: str):
print(f"I have a {animal_type} named {pet_name}.")
describe_pet(pet_name="Willie", animal_type="dog") # 顺序改变了,但仍能工作
describe_pet("cat", pet_name="Whiskers") # 混合使用位置参数和关键字参数

通过使某些参数可选来提供灵活性。默认值在函数定义时被一次性评估。

重要: 避免使用可变对象(如列表或字典)作为默认值,因为它们可能导致意外的行为,原因是在多次调用中会重复使用同一个对象。请使用 None 作为默认值,如果需要,在函数内部创建可变对象。

#!/usr/bin/env python3
def power(base: float, exponent: float = 2.0):
"""计算底数的指数幂。"""
return base ** exponent
print(f"5 squared: {power(5)}") # 使用默认指数=2
print(f"2 cubed: {power(2, 3)}") # 覆盖默认值
print(f"3 squared: {power(base=3)}") # 关键字参数,使用默认指数

当你预先不知道可能传递多少参数时使用。

#!/usr/bin/env python3
def make_pizza(size: str, *toppings: str, **customer_info: str):
"""总结订购的披萨。"""
print(f"\nMaking a {size}-inch pizza with the following toppings:")
if toppings:
for topping in toppings:
print(f"- {topping}")
else:
print("- Plain cheese")
if customer_info:
print("\nCustomer Info:")
for key, value in customer_info.items():
print(f"- {key}: {value}")
# 调用函数
make_pizza("12", "mushrooms", "green peppers", "extra cheese")
make_pizza("16", delivery="yes", name="John Doe")
make_pizza("9", "pepperoni", name="Jane", phone="555-1234", vip=True)

*toppings 将所有额外的、未被匹配的位置参数(‘mushrooms’, ‘green peppers’, …)收集到一个名为 toppings 的元组中。

**customer_info 将所有额外的、未被匹配的关键字参数(delivery='yes', name='John Doe', …)收集到一个名为 customer_info 的字典中。

Lambda 函数提供了一种创建小型、内联匿名函数的方式,通常用于不需要完整 def 语句的简短操作。

  • 语法:lambda arguments: expression
  • 可以接受任意数量的参数,但只能有一个一个表达式。
  • 表达式的值被隐式返回。
  • 不能包含复杂的语句(如赋值、循环、try/except)。
  • 常与 map()、filter()、sorted() 等函数或 GUI 回调函数一起使用。
#!/usr/bin/env python3
# 用于添加两个数字的 Lambda 函数
add = lambda x, y: x + y
print(f"Lambda add(5, 3): {add(5, 3)}")
# 将 lambda 与 sorted() 一起使用
points = [(1, 2), (3, 1), (5, -4), (2, 3)]
# 根据第二个元素(y 坐标)对点进行排序
points_sorted_by_y = sorted(points, key=lambda point: point[1])
print(f"Points sorted by y: {points_sorted_by_y}")

return 语句退出当前函数的执行,并将一个值(或 None)发送回函数被调用的地方。

函数可以返回任何类型的 Python 对象,包括元组、列表、字典,甚至其他函数。要返回多个值,只需用逗号将它们列出;Python 会自动将它们打包成一个元组。

#!/usr/bin/env python3
def calculate_stats(numbers: list) -> tuple:
"""计算列表中数字的总和和平均值。"""
if not numbers:
return (0, 0.0) # 对于空列表返回一个元组
total = sum(numbers)
average = total / len(numbers)
return total, average # 返回一个元组 (total, average)
nums = [10, 20, 30, 40]
stats_result = calculate_stats(nums)
print(f"Stats for {nums}: Sum={stats_result[0]}, Average={stats_result[1]}")
# 或者直接解包返回的元组
sum_val, avg_val = calculate_stats(nums)
print(f"Unpacked: Sum={sum_val}, Average={avg_val}")

作用域决定了程序不同部分中变量的可见性。Python 使用 LEGB 规则来解析名称:

  • Local(局部):在函数内赋值的名称(且未声明为 global 或 nonlocal)。
  • Enclosing function locals(闭包函数局部):任何外部(闭包)函数局部作用域中的名称(用于嵌套函数)。
  • Global(全局):在模块文件的顶层赋值的名称,或在函数中声明为 global 的名称。
  • Built-in(内置):Python 中预先赋值的名称(如 print、len、Exception)。

局部变量仅在其定义的函数内部存在。全局变量在整个模块中都可访问。

#!/usr/bin/env python3
global_var = 100 # 全局作用域
def my_func():
local_var = 50 # 局部作用域
print(f"Inside function: local_var = {local_var}")
print(f"Inside function: global_var = {global_var}") # 可以访问全局变量
my_func()
print(f"Outside function: global_var = {global_var}")
# print(f"Outside function: local_var = {local_var}") # NameError: name 'local_var' is not defined

如果你需要在函数内部修改全局变量,请使用 global 关键字;如果需要修改外部(闭包)函数作用域中的变量(而非全局作用域),请使用 nonlocal。