Python函数
# Python函数
在 Python 编程中,函数 (Function) 是组织代码的基本构建块。它是一段封装了特定任务、可重复使用的代码。掌握函数是编写高效、可读、可维护代码的第一步。
# 1. 什么是函数?为什么使用它?
想象一下,你正在写一个程序,需要在三个不同的地方计算圆的面积。如果没有函数,你可能需要写三遍同样的代码:
# 第一次
pi = 3.14159
radius1 = 5
area1 = pi * (radius1 ** 2)
print(area1)
# 第二次
radius2 = 10
area2 = pi * (radius2 ** 2)
print(area2)
# 第三次
radius3 = 3
area3 = pi * (radius3 ** 2)
print(area3)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这种重复不仅繁琐,而且如果计算公式需要修改(比如提高 pi
的精度),你就得修改所有地方。
函数解决了这个问题。我们可以将计算面积的逻辑封装起来:
def calculate_circle_area(radius):
"""计算并返回圆的面积。"""
pi = 3.14159
return pi * (radius ** 2)
# 现在,只需调用函数即可
print(calculate_circle_area(5))
print(calculate_circle_area(10))
print(calculate_circle_area(3))
2
3
4
5
6
7
8
9
使用函数的核心优势:
- 提高代码复用性 (Reusability):一次定义,多次调用。
- 增强代码可读性 (Readability):函数名(如
calculate_circle_area
)清晰地表达了代码块的功能。 - 简化代码维护 (Maintainability):只需修改函数内部的逻辑,所有调用处都会自动更新。
- 实现代码模块化 (Modularity):将复杂的程序分解为一个个简单的小任务。
# 2. 函数的定义与调用
# 2.1 定义函数
使用 def
关键字来定义一个函数。
- 基本语法:
def function_name(parameter1, parameter2, ...): """ 这里是函数的文档字符串 (docstring),可选,但强烈推荐。 它解释了函数的功能、参数和返回值。 """ # 函数体 (Function Body) # 执行特定任务的代码 return result # 使用 return 语句返回结果,可选
1
2
3
4
5
6
7
8 - 函数命名规范:通常使用蛇形命名法 (snake_case),即全小写字母,单词间用下划线分隔,例如
calculate_sum
。
# 2.2 调用函数
定义函数后,通过 函数名()
的形式来执行(调用)它。
- 示例:
# 定义一个打招呼的函数 def greet(name): """向指定的人打印问候信息。""" print(f"Hello, {name}! 欢迎来到 Python 的世界。") # 调用函数 greet("Alice") greet("Bob") # 输出: # Hello, Alice! 欢迎来到 Python 的世界。 # Hello, Bob! 欢迎来到 Python 的世界。
1
2
3
4
5
6
7
8
9
10
11
12
# 3. 函数的参数
参数是函数与外部世界沟通的桥梁。调用函数时传入的值称为实参 (Argument),函数定义中的变量称为形参 (Parameter)。
# 3.1 位置参数 (Positional Arguments)
调用时,实参的值按位置顺序依次传递给形参。这是最基本的参数类型。
def describe_pet(animal_type, pet_name):
"""显示宠物的信息。"""
print(f"我有一只 {animal_type}。")
print(f"它的名字叫 {pet_name}。")
describe_pet("仓鼠", "皮蛋")
# 输出:
# 我有一只 仓鼠。
# 它的名字叫 皮蛋。
2
3
4
5
6
7
8
9
"仓鼠"
对应 animal_type
,"皮蛋"
对应 pet_name
。顺序不能错。
# 3.2 关键字参数 (Keyword Arguments)
调用时,通过 形参名=值
的形式指定实参。这允许我们不按顺序传递参数,并使代码更清晰。
describe_pet(pet_name="团子", animal_type="猫")
# 输出:
# 我有一只 猫。
# 它的名字叫 团子。
2
3
4
# 3.3 默认参数 (Default Arguments)
定义函数时,可以为形参提供一个默认值。如果在调用时没有为该参数提供实参,它将使用默认值。
def greet_user(username, greeting="你好"):
"""向用户打招呼,可自定义问候语。"""
print(f"{greeting}, {username}!")
greet_user("张三") # 使用默认问候语
greet_user("李四", "早上好") # 提供新的问候语,覆盖默认值
# 输出:
# 你好, 张三!
# 早上好, 李四!
2
3
4
5
6
7
8
9
10
重要陷阱:应避免使用可变对象(如列表 []
或字典 {}
)作为默认参数。因为默认参数在函数定义时只被创建一次,后续调用会共享这同一个对象。
# 3.4 可变位置参数 *args
当不确定函数会接收多少个位置参数时,可以使用 *args
。它会将所有多余的位置参数收集到一个元组 (tuple) 中。
def calculate_sum(*args):
"""计算所有输入数字的和。"""
print(f"接收到的参数 (元组): {args}")
total = 0
for num in args:
total += num
return total
print(calculate_sum(1, 2, 3))
print(calculate_sum(10, 20, 30, 40, 50))
# 输出:
# 接收到的参数 (元组): (1, 2, 3)
# 9
# 接收到的参数 (元组): (10, 20, 30, 40, 50)
# 150
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3.5 可变关键字参数 **kwargs
当不确定会接收多少个关键字参数时,使用 **kwargs
。它会将所有未定义的关键字参数收集到一个字典 (dict) 中。
def build_profile(first, last, **kwargs):
"""创建一个字典,包含我们知道的关于用户的一切。"""
profile = {'first_name': first, 'last_name': last}
print(f"接收到的关键字参数 (字典): {kwargs}")
profile.update(kwargs) # 将 kwargs 字典内容更新到 profile
return profile
user_profile = build_profile("爱因", "斯坦", location="普林斯顿", field="物理")
print(user_profile)
# 输出:
# 接收到的关键字参数 (字典): {'location': '普林斯顿', 'field': '物理'}
# {'first_name': '爱因', 'last_name': '斯坦', 'location': '普林斯顿', 'field': '物理'}
2
3
4
5
6
7
8
9
10
11
12
13
# 3.6 参数的顺序
当组合使用所有参数类型时,必须遵循以下顺序:
- 标准位置参数
*args
- 关键字参数或默认参数
**kwargs
# 4. 函数的返回值 return
return
语句用于从函数中退出,并返回一个值。
# 4.1 返回单个值
def square(number):
return number * number
result = square(4)
print(result) # 输出: 16
2
3
4
5
# 4.2 返回多个值
Python 函数可以一次返回多个值,它们会被打包成一个元组。
def divide_with_remainder(a, b):
"""返回商和余数。"""
quotient = a // b
remainder = a % b
return quotient, remainder # 返回多个值
q, r = divide_with_remainder(10, 3)
print(f"商: {q}, 余数: {r}") # 输出: 商: 3, 余数: 1
result_tuple = divide_with_remainder(10, 3)
print(result_tuple) # 输出: (3, 1)
2
3
4
5
6
7
8
9
10
11
# 4.3 隐式返回 None
如果一个函数没有 return
语句,或者 return
后面没有跟任何值,它会自动返回 None
。
def say_hello(name):
print(f"你好, {name}")
result = say_hello("世界")
print(result)
# 输出:
# 你好, 世界
# None
2
3
4
5
6
7
8
# 5. 函数的文档字符串 (Docstrings)
文档字符串是解释函数用途的三引号字符串。它是函数定义中的第一条语句。良好的文档是高质量代码的标志。
def power(base, exponent):
"""
计算并返回一个数的幂。
Args:
base (int or float): 基数。
exponent (int or float): 指数。
Returns:
int or float: base 的 exponent 次幂的结果。
"""
return base ** exponent
# 查看文档字符串的两种方式
print(help(power))
print(power.__doc__)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 6. 变量作用域 (Scope):变量的生存空间
作用域是编程中一个至关重要的概念,它定义了一个变量能够被访问的区域或上下文。理解作用域有助于避免命名冲突和难以追踪的 Bug。
Python 的变量作用域遵循 LEGB 规则的查找顺序,即当代码引用一个变量时,Python 会按以下顺序搜索该变量:
- L (Local):局部作用域。这是最内层的范围,包含在当前函数内部定义的任何变量。
- E (Enclosing):嵌套作用域。如果一个函数嵌套在另一个函数中,这个作用域包含外部函数中定义的变量。
- G (Global):全局作用域。在 Python 文件的顶层(所有函数之外)定义的变量。
- B (Built-in):内置作用域。Python 预先定义的名称,如
print()
,len()
,str
等,在任何地方都可直接使用。
# 6.1 局部变量 (Local Variables)
局部变量是在函数内部定义的变量,它的生命周期与函数共存。
- 生命周期:当函数被调用时,局部变量被创建;当函数执行完毕并返回时,局部变量被销毁。
- 作用范围:只能在定义它的函数内部被访问。函数的参数也属于局部变量。
def show_local_scope():
# 下面三个都是局部变量
animal = "金毛"
name = "旺财"
age = 3
print(f"函数内部: {name}是一只{age}岁的{animal}。")
show_local_scope()
# 输出:
# 函数内部: 旺财是一只3岁的金毛。
# 在函数外部尝试访问局部变量会导致错误
# print(animal) # 会引发 NameError: name 'animal' is not defined
2
3
4
5
6
7
8
9
10
11
12
13
# 6.2 全局变量 (Global Variables)
全局变量是在模块的顶层定义的变量,可以在文件的任何地方(包括所有函数内部和外部)被访问。
PI = 3.14159 # 这是一个全局变量
app_version = "1.0.2" # 这也是一个全局变量
def calculate_circle_circumference(radius):
# 函数内部可以自由地“读取”全局变量的值
return 2 * PI * radius
print(f"圆周率是: {PI}")
print(f"半径为5的圆周长是: {calculate_circle_circumference(5)}")
print(f"当前应用版本: {app_version}")
# 输出:
# 圆周率是: 3.14159
# 半径为5的圆周长是: 31.4159
# 当前应用版本: 1.0.2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 6.3 LEGB 查找规则示例:同名变量的“遮蔽”
当不同作用域中存在同名变量时,Python 会根据 LEGB 规则使用它找到的第一个变量。这通常意味着局部变量会“遮蔽” (shadow) 全局变量。
x = "我是全局的 x"
def my_func():
x = "我是局部的 x" # 这个局部变量“遮蔽”了全局的 x
print(f"在函数内部,x 是: {x}")
my_func()
print(f"在函数外部,x 依然是: {x}")
# 输出:
# 在函数内部,x 是: 我是局部的 x
# 在函数外部,x 依然是: 我是全局的 x
2
3
4
5
6
7
8
9
10
11
12
在 my_func
内部,Python 在局部作用域 (L) 就找到了 x
,因此不会再继续向外(全局 G)查找。
# 6.4 使用 global
关键字修改全局变量
默认情况下,在函数内部对一个变量进行赋值操作 (=
, +=
等) 会创建一个新的局部变量。如果你的意图是修改全局变量,就必须使用 global
关键字明确告诉 Python。
错误示范(未修改全局变量)
player_score = 100 def failed_increase_score(points): # 下面这行代码没有使用 global, # 因此 Python 会创建一个新的、名为 player_score 的“局部变量”。 player_score = points print(f"函数内部的 '局部' player_score: {player_score}") failed_increase_score(150) print(f"函数外部的 '全局' player_score 并未改变: {player_score}") # 输出: # 函数内部的 '局部' player_score: 150 # 函数外部的 '全局' player_score 并未改变: 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14正确示范
player_score = 100 def increase_score(points): global player_score # 明确声明:我要操作的是全局作用域的 player_score player_score += points print(f"分数增加 {points}!当前分数: {player_score}") increase_score(20) print(f"最终分数: {player_score}") # 输出: # 分数增加 20!当前分数: 120 # 最终分数: 120
1
2
3
4
5
6
7
8
9
10
11
12
13
# 6.5 使用 nonlocal
关键字修改嵌套作用域变量
nonlocal
关键字用于在嵌套函数中,修改外部(但非全局)函数中的变量。
def outer_func():
x = "我是外部变量" # 嵌套作用域 (E)
def inner_func():
nonlocal x # 声明 x 不是局部变量,而是来自外部嵌套作用域 (Enclosing)
x = "我在内部函数中被修改了"
print(f"内部调用: {x}")
inner_func()
print(f"外部调用: {x}")
outer_func()
# 输出:
# 内部调用: 我在内部函数中被修改了
# 外部调用: 我在内部函数中被修改了
2
3
4
5
6
7
8
9
10
11
12
13
14
# 6.6 作用域的最佳实践
- 尽量避免滥用全局变量:全局变量使得代码状态难以追踪,因为任何函数都可能在任何时候修改它。这会增加调试难度并降低代码的可维护性。
- 优先使用参数和返回值:在函数间传递数据的最佳方式是通过函数参数和
return
语句,这使得数据流向清晰可见。 - 仅在必要时使用
global
和nonlocal
:当你需要管理一个跨多个函数调用的共享状态(如配置、缓存、计数器)时,global
是必要的。但使用前请三思是否有更好的设计模式。 - 常量使用大写:对于不希望在程序中被修改的全局变量(即常量),通常使用全大写字母加下划线的命名方式,如
MAX_CONNECTIONS = 10
。这是一种约定,提醒开发者不要修改它。
# 7. 高阶函数 (Higher-Order Functions)
在 Python 中,函数是“一等公民”,这意味着它们可以像任何其他对象(如整数、字符串)一样被处理:
- 可以被赋值给变量。
- 可以作为参数传递给其他函数。
- 可以作为其他函数的返回值。
接收函数作为参数或返回一个函数的函数,被称为高阶函数。
# 7.1 函数作为参数
def square(n):
return n * n
def cube(n):
return n * n * n
def apply_function(func, value):
"""接收一个函数和值,将函数应用于该值。"""
return func(value)
result_sq = apply_function(square, 5) # 将 square 函数传递进去
result_cu = apply_function(cube, 5)
print(result_sq) # 输出: 25
print(result_cu) # 输出: 125
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 7.2 函数作为返回值 (闭包)
一个返回函数的函数。返回的那个内部函数记住了其定义时所在的环境(作用域),这被称为闭包 (Closure)。
def create_multiplier(n):
"""创建一个乘以 n 的函数。"""
def multiplier(x):
return x * n # 这里的 n 来自外部函数的环境
return multiplier
# 创建一个“乘以3”的函数
times3 = create_multiplier(3)
# 创建一个“乘以5”的函数
times5 = create_multiplier(5)
print(times3(10)) # 输出: 30
print(times5(10)) # 输出: 50
2
3
4
5
6
7
8
9
10
11
12
13
# 8. 匿名函数 lambda
lambda
关键字用于创建小型的、没有名字的匿名函数。它特别适用于需要一个简单函数作为参数的场景。
- 语法:
lambda arguments: expression
- 特点: 只能包含一个表达式,该表达式的结果就是返回值。
# 普通函数
def add(x, y):
return x + y
# 等价的 lambda 函数
add_lambda = lambda x, y: x + y
print(add(2, 3)) # 输出: 5
print(add_lambda(2, 3)) # 输出: 5
2
3
4
5
6
7
8
9
lambda
的常见用途: 与 map()
, filter()
, sorted()
等内置函数结合使用。
numbers = [1, 2, 3, 4, 5]
# 使用 map() 将列表中每个元素平方
squares = list(map(lambda x: x**2, numbers))
print(squares) # 输出: [1, 4, 9, 16, 25]
# 使用 filter() 筛选出列表中的偶数
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # 输出: [2, 4]
2
3
4
5
6
7
8
9
# 9. 类型提示 (Type Hinting)
从 Python 3.5 开始,可以为函数参数和返回值添加类型提示。类型提示不影响程序的运行,但能极大地增强代码的可读性,并帮助静态代码分析工具(如 MyPy)和 IDE(如 VS Code, PyCharm)发现潜在的错误。
def greet(name: str) -> str:
"""
接收一个字符串,返回一个带问候语的字符串。
:param name: 用户的名字 (字符串)
:return: 问候语 (字符串)
"""
return f"Hello, {name}"
greeting_message = greet("Guido")
print(greeting_message) # 输出: Hello, Guido
2
3
4
5
6
7
8
9
10
name: str
表示name
参数期望是一个字符串。-> str
表示该函数期望返回一个字符串。