Python函数:定义、调用与参数传递
函数是Python编程中的核心概念,它允许我们将代码组织成可重用的模块。本章将深入探讨Python函数的定义、调用、参数传递和返回值,以及一些高级函数特性。
函数基础
什么是函数?
函数是一组执行特定任务的代码块,它可以接收输入(参数),执行一系列操作,并返回结果。函数的主要目的是提高代码的模块化和可重用性。
函数的优势
- 代码重用:避免重复编写相同的代码
- 模块化设计:将复杂问题分解为更小的部分
- 提高可读性:使代码结构更清晰
- 易于维护:修改函数只需要在一个地方进行
函数定义与调用
定义函数
在Python中,使用def
关键字定义函数:
def 函数名(参数1, 参数2, ...):
"""函数文档字符串(docstring):描述函数的功能"""
# 函数体
# 可以包含任意Python代码
return 返回值 # 可选
例如,定义一个简单的求和函数:
def add_numbers(a, b):
"""返回两个数的和"""
sum = a + b
return sum
调用函数
定义函数后,可以通过函数名加括号来调用它,并传入所需的参数:
# 调用add_numbers函数
result = add_numbers(5, 3)
print(result) # 输出: 8
# 直接打印函数调用的结果
print(add_numbers(10, 20)) # 输出: 30
函数文档字符串(Docstring)
函数文档字符串是放在函数定义后的第一行的字符串,用于描述函数的功能、参数和返回值。它可以帮助其他开发者理解函数的用途。
def calculate_area(radius):
"""
计算圆的面积。
参数:
radius (float): 圆的半径
返回:
float: 圆的面积
"""
return 3.14159 * radius ** 2
可以使用help()
函数或直接访问函数的__doc__
属性来查看文档字符串:
# 查看函数的文档字符串
help(calculate_area)
# 直接访问文档字符串
print(calculate_area.__doc__)
函数参数
Python支持多种参数传递方式,提供了灵活的函数定义方法。
位置参数
位置参数是最基本的参数形式,参数值根据定义顺序被传递:
def greet(name, message):
"""根据给定的名字和消息打印问候语"""
print(f"{message}, {name}!")
# 调用函数时,参数按位置顺序传递
greet("张三", "早上好") # 输出: 早上好, 张三!
关键字参数
关键字参数允许通过参数名来指定参数,无需按照定义的顺序传递:
def greet(name, message):
print(f"{message}, {name}!")
# 使用关键字参数
greet(message="晚上好", name="李四") # 输出: 晚上好, 李四!
默认参数
默认参数在定义函数时指定默认值,如果调用函数时未提供该参数的值,则使用默认值:
def greet(name, message="你好"):
print(f"{message}, {name}!")
# 只提供必需的参数,使用默认消息
greet("王五") # 输出: 你好, 王五!
# 提供所有参数,覆盖默认值
greet("赵六", "下午好") # 输出: 下午好, 赵六!
重要提示:默认参数值在函数定义时计算一次,而不是在每次调用时重新计算。对于可变对象(如列表、字典),这可能导致意外行为:
# 错误示例:使用可变对象作为默认参数
def add_item(item, items=[]): # 默认空列表在函数定义时创建一次
items.append(item)
return items
print(add_item("apple")) # 输出: ['apple']
print(add_item("banana")) # 输出: ['apple', 'banana'] - 可能不是预期结果!
# 正确做法
def add_item_correct(item, items=None):
if items is None:
items = [] # 每次调用都创建新的空列表
items.append(item)
return items
print(add_item_correct("apple")) # 输出: ['apple']
print(add_item_correct("banana")) # 输出: ['banana']
可变参数(*args)
可变参数允许函数接收任意数量的位置参数,这些参数被打包成一个元组:
def sum_all(*numbers):
"""计算所有传入参数的和"""
total = 0
for num in numbers:
total += num
return total
# 传递不同数量的参数
print(sum_all(1, 2)) # 输出: 3
print(sum_all(1, 2, 3, 4, 5)) # 输出: 15
print(sum_all()) # 输出: 0 (没有传参数)
关键字可变参数(**kwargs)
关键字可变参数允许函数接收任意数量的关键字参数,这些参数被打包成一个字典:
def print_info(**kwargs):
"""打印所有传入的关键字参数"""
for key, value in kwargs.items():
print(f"{key}: {value}")
# 传递不同的关键字参数
print_info(name="张三", age=25, city="北京")
# 输出:
# name: 张三
# age: 25
# city: 北京
混合使用不同类型的参数
在定义函数时,参数必须按照以下顺序:
- 位置参数
- 默认参数
- 可变参数 (*args)
- 关键字参数
- 关键字可变参数 (**kwargs)
def example_function(pos1, pos2, default1="默认值1", default2="默认值2", *args, key1, key2="值2", **kwargs):
print(f"位置参数: {pos1}, {pos2}")
print(f"默认参数: {default1}, {default2}")
print(f"可变参数: {args}")
print(f"关键字参数: {key1}, {key2}")
print(f"关键字可变参数: {kwargs}")
# 调用混合了各种参数类型的函数
example_function(1, 2, "自定义1", *[10, 20, 30], key1="必须的关键字参数", extra1="额外1", extra2="额外2")
强制关键字参数
在Python 3中,可以使用*
分隔符后的参数必须通过关键字指定,而不能通过位置传递:
def greet(name, *, message="你好"):
print(f"{message}, {name}!")
# 正确调用
greet("张三", message="早上好") # 输出: 早上好, 张三!
# 错误调用
# greet("张三", "早上好") # 这会引发TypeError错误
函数返回值
基本返回值
函数可以使用return
语句返回值:
def square(number):
"""返回数字的平方"""
return number ** 2
result = square(4)
print(result) # 输出: 16
返回多个值
Python函数可以同时返回多个值,这些值实际上被打包成一个元组:
def calculate_statistics(numbers):
"""计算一组数的和、平均值和最大值"""
total = sum(numbers)
average = total / len(numbers)
maximum = max(numbers)
return total, average, maximum
# 接收多个返回值
nums = [1, 2, 3, 4, 5]
sum_result, avg_result, max_result = calculate_statistics(nums)
print(f"总和: {sum_result}, 平均值: {avg_result}, 最大值: {max_result}")
# 输出: 总和: 15, 平均值: 3.0, 最大值: 5
# 也可以作为元组接收
stats = calculate_statistics(nums)
print(stats) # 输出: (15, 3.0, 5)
print(type(stats)) # 输出: <class 'tuple'>
返回None
如果函数没有明确的return
语句,或者只有return
而没有返回值,则默认返回None
:
def greet(name):
print(f"你好, {name}!")
# 没有return语句
result = greet("张三") # 打印: 你好, 张三!
print(result) # 输出: None
def early_return(x):
if x < 0:
return # 提前返回,没有返回值
print(f"x是非负数: {x}")
early_return(-5) # 什么都不打印
early_return(10) # 打印: x是非负数: 10
变量作用域和命名空间
局部作用域和全局作用域
Python中的变量有不同的作用域(可见范围):
- 局部作用域:在函数内部定义的变量,只在函数内部可见
- 全局作用域:在函数外部定义的变量,可以在整个模块中访问
# 全局变量
global_var = "我是全局变量"
def function_demo():
# 局部变量
local_var = "我是局部变量"
print(global_var) # 可以访问全局变量
print(local_var) # 可以访问局部变量
function_demo()
print(global_var) # 可以访问全局变量
# print(local_var) # 错误:local_var不在全局作用域中
global和nonlocal关键字
如果需要在函数内部修改全局变量,需要使用global
关键字:
counter = 0
def increment():
global counter # 声明counter是全局变量
counter += 1
print(f"计数器: {counter}")
increment() # 输出: 计数器: 1
increment() # 输出: 计数器: 2
print(counter) # 输出: 2
nonlocal
关键字用于在嵌套函数中修改外层函数的变量:
def outer_function():
outer_var = "外层变量"
def inner_function():
nonlocal outer_var # 声明outer_var是外层函数的变量
outer_var = "修改后的外层变量"
print(f"内层函数: {outer_var}")
print(f"调用前: {outer_var}")
inner_function()
print(f"调用后: {outer_var}")
outer_function()
# 输出:
# 调用前: 外层变量
# 内层函数: 修改后的外层变量
# 调用后: 修改后的外层变量
高级函数特性
匿名函数(Lambda)
Lambda函数是一种简单的、单行的、匿名函数,使用lambda
关键字定义:
# 普通函数
def square(x):
return x ** 2
# 等价的lambda函数
square_lambda = lambda x: x ** 2
print(square(5)) # 输出: 25
print(square_lambda(5)) # 输出: 25
# lambda函数通常用于需要函数对象的场合
numbers = [1, 5, 3, 9, 2, 6]
sorted_numbers = sorted(numbers, key=lambda x: abs(x - 5))
print(sorted_numbers) # 输出按与5的差值排序的结果: [5, 6, 3, 9, 2, 1]
Lambda函数最适合用于简单的单行表达式,对于复杂逻辑应该使用常规函数。
函数作为参数
在Python中,函数是一等公民,可以作为参数传递:
def apply_operation(x, y, operation):
"""应用给定的操作函数到x和y上"""
return operation(x, y)
def add(a, b):
return a + b
def multiply(a, b):
return a * b
# 传递函数作为参数
result1 = apply_operation(5, 3, add)
print(result1) # 输出: 8
result2 = apply_operation(5, 3, multiply)
print(result2) # 输出: 15
# 使用lambda表达式
result3 = apply_operation(5, 3, lambda a, b: a - b)
print(result3) # 输出: 2
闭包(Closure)
闭包是一个函数,它记住了创建它的环境中的变量,即使那些变量的作用域已经结束:
def create_multiplier(factor):
"""创建一个乘以指定因子的函数"""
def multiplier(x):
return x * factor # 使用外部函数的变量factor
return multiplier # 返回内部函数
# 创建乘以2和乘以5的函数
double = create_multiplier(2)
quintuple = create_multiplier(5)
print(double(7)) # 输出: 14
print(quintuple(7)) # 输出: 35
闭包常用于创建工厂函数、回调等场景。
递归函数
递归函数是调用自身的函数。在使用递归时,必须定义一个终止条件以避免无限递归:
def factorial(n):
"""计算n的阶乘"""
# 基本情况(终止条件)
if n == 0 or n == 1:
return 1
# 递归调用
else:
return n * factorial(n - 1)
print(factorial(5)) # 输出: 120 (5 * 4 * 3 * 2 * 1)
递归函数适合解决能够分解为子问题的问题,如树遍历、分治算法等。但要注意Python的递归深度限制(默认为1000)。
内置函数
Python提供了许多有用的内置函数,以下是一些常用的例子:
map(), filter(), reduce()
这些函数接受一个函数和一个或多个可迭代对象作为参数,用于数据转换和处理:
# map() - 将函数应用于可迭代对象的每个元素
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, numbers))
print(squared) # 输出: [1, 4, 9, 16, 25]
# filter() - 筛选符合条件的元素
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # 输出: [2, 4]
# reduce() - 累积应用函数到序列的元素
from functools import reduce
product = reduce(lambda x, y: x * y, numbers)
print(product) # 输出: 120 (1 * 2 * 3 * 4 * 5)
zip()
zip()
函数将多个可迭代对象的元素配对:
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
cities = ["New York", "London", "Paris"]
# 将三个列表"压缩"在一起
combined = list(zip(names, ages, cities))
print(combined)
# 输出: [('Alice', 25, 'New York'), ('Bob', 30, 'London'), ('Charlie', 35, 'Paris')]
# 解压缩
unzipped_names, unzipped_ages, unzipped_cities = zip(*combined)
print(unzipped_names) # 输出: ('Alice', 'Bob', 'Charlie')
enumerate()
enumerate()
函数将一个可计数的对象组合为一个索引序列:
fruits = ["苹果", "香蕉", "橙子"]
# 使用enumerate获取索引和值
for index, fruit in enumerate(fruits):
print(f"索引 {index}: {fruit}")
# 输出:
# 索引 0: 苹果
# 索引 1: 香蕉
# 索引 2: 橙子
# 从指定索引开始
for index, fruit in enumerate(fruits, start=1):
print(f"第 {index} 个水果: {fruit}")
# 输出:
# 第 1 个水果: 苹果
# 第 2 个水果: 香蕉
# 第 3 个水果: 橙子
编程实践
用函数解决实际问题
让我们看一些使用函数解决实际问题的例子:
示例1:温度转换器
def celsius_to_fahrenheit(celsius):
"""将摄氏温度转换为华氏温度"""
return (celsius * 9/5) + 32
def fahrenheit_to_celsius(fahrenheit):
"""将华氏温度转换为摄氏温度"""
return (fahrenheit - 32) * 5/9
# 测试温度转换函数
celsius_temp = 25
fahrenheit_temp = celsius_to_fahrenheit(celsius_temp)
print(f"{celsius_temp}°C = {fahrenheit_temp:.2f}°F")
fahrenheit_temp = 98.6
celsius_temp = fahrenheit_to_celsius(fahrenheit_temp)
print(f"{fahrenheit_temp}°F = {celsius_temp:.2f}°C")
示例2:密码检查器
def is_strong_password(password):
"""
检查密码是否强壮
强密码的标准:
- 至少8个字符
- 包含至少一个数字
- 包含至少一个大写字母
- 包含至少一个小写字母
- 包含至少一个特殊字符
"""
if len(password) < 8:
return False
has_digit = False
has_upper = False
has_lower = False
has_special = False
special_chars = "!@#$%^&*()-+?_=,<>/"
for char in password:
if char.isdigit():
has_digit = True
elif char.isupper():
has_upper = True
elif char.islower():
has_lower = True
elif char in special_chars:
has_special = True
return has_digit and has_upper and has_lower and has_special
# 测试密码强度检查器
passwords = ["password", "Password1", "P@ssw0rd", "12345", "P@ssw0rd!"]
for pwd in passwords:
if is_strong_password(pwd):
print(f"'{pwd}' 是一个强密码")
else:
print(f"'{pwd}' 不是强密码")
编程练习
为了巩固本章所学内容,尝试完成以下练习:
- 编写一个函数,接受一个正整数n作为参数,返回1到n的所有素数。
- 创建一个函数,接受一个字符串,返回这个字符串中单词的数量。
- 编写一个函数,接受任意数量的数字,返回这些数字的平均值。
- 创建一个计算器函数,能够根据操作符(+, -, *, /)执行两个数的运算。
- 实现一个递归函数来计算斐波那契数列的第n项(F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2))。
小结
本章我们学习了Python函数的多个方面:
- 函数的定义与调用
- 不同类型的参数(位置参数、关键字参数、默认参数、可变参数等)
- 函数的返回值
- 变量作用域和命名空间
- 高级函数特性(lambda函数、函数作为参数、闭包、递归函数)
- 有用的内置函数和实际应用示例
函数是Python编程的基本构建块,也是代码复用和模块化的关键。掌握函数的使用将使你能够编写更简洁、更可维护的代码。在下一章中,我们将学习Python的模块和包,了解如何组织更大规模的Python程序。