Python3 基础:函数定义与调用
Python3 基础:函数定义与调用
- 什么是函数?
- 一、函数的定义
- 二、函数的调用
- 三、函数参数
- 3.1位置参数
- 3.2关键字参数
- 3.3默认参数值
- 3.4可变数量的参数
- args:接收任意数量的位置参数
- **kwargs:接收任意数量的关键字参数
- 3.5 综合使用各种参数
- 四、返回值
- 4.1 返回多个值
- 4.2没有返回值的函数
- 五、函数作用域与变量
- 5.1 局部变量
- 5.2全局变量
- 5.3修改全局变量
- 六、函数是一等公民
- 6.1函数赋值给变量
- 6.2函数作为参数
- 6.3函数作为返回值
- 七、匿名函数(Lambda 函数)
- 八、递归函数
- 九、文档字符串(Docstring)
- 十、函数的最佳实践
- 十一、实用示例
- 示例1:基于函数的简单计算器
- 示例2:文本处理函数
- 示例3:使用高阶函数处理数据
- 小结
- 问题1: 编写一个函数,接受一个列表和一个数字n作为参数,返回列表中所有大于n的元素。
- 问题2: 使用递归函数计算一个数字的所有位数之和
- 问题3: 编写一个装饰器函数,可以计算任何函数的执行时间并打印出来。
函数是Python编程中最强大的工具之一,它允许我们将代码组织成可重用的块。想象一下,函数就像是一个专门完成特定任务的机器:你给它一些原材料(参数),它按照预设的流程处理这些材料,然后给你一个成品(返回值)。
什么是函数?
函数是一段执行特定任务的代码块,可以在程序中多次调用。函数的优点包括:
- 代码复用:一次编写,多次使用
- 模块化:将复杂问题分解成小任务
- 可维护性:修改函数不影响使用该函数的代码
- 抽象:使用者只需知道函数做什么,不需要知道如何做
一、函数的定义
在Python中,我们使用def
关键字定义函数:
def greet():"""打印一条问候信息"""print("你好,欢迎学习Python函数!")
以上代码定义了一个名为greet
的函数,它不接受任何参数,执行时会打印一条问候信息。引号之间的内容是函数的文档字符串(docstring),用于说明函数的用途。
二、函数的调用
定义函数后,我们可以通过函数名加括号来调用它:
# 调用greet函数
greet() # 输出:你好,欢迎学习Python函数!
三、函数参数
函数可以接受输入值,这些值称为参数或形参。
3.1位置参数
最简单的参数形式是位置参数,调用时按照定义顺序传递:
def add(a, b):"""返回两个数的和"""return a + b# 调用add函数
result = add(5, 3)
print(result) # 输出:8
3.2关键字参数
调用函数时可以使用参数名来指定参数,这样参数顺序就不重要了:
def describe_pet(animal_type, pet_name):"""显示宠物的信息"""print(f"我有一只{animal_type},它叫{pet_name}。")# 使用关键字参数调用
describe_pet(animal_type="猫", pet_name="咪咪")
describe_pet(pet_name="旺财", animal_type="狗") # 顺序不重要
3.3默认参数值
定义函数时可以给参数指定默认值,调用函数时如果不提供该参数的值,将使用默认值:
def greet_user(username="用户"):"""向用户发出问候"""print(f"你好,{username}!")greet_user("小明") # 输出:你好,小明!
greet_user() # 输出:你好,用户!
注意:有默认值的参数必须放在没有默认值的参数之后。
# 正确的定义方式
def function(a, b=5, c=10):pass# 错误的定义方式
def function(a=5, b, c=10): # 这会导致语法错误pass
3.4可变数量的参数
args:接收任意数量的位置参数
def sum_numbers(*args):"""计算所有参数的和"""total = 0for num in args:total += numreturn totalprint(sum_numbers(1, 2)) # 输出:3
print(sum_numbers(1, 2, 3, 4, 5)) # 输出:15
args
将接收所有额外的位置参数,并将它们打包成一个元组。
**kwargs:接收任意数量的关键字参数
def build_profile(**kwargs):"""创建一个描述用户的字典"""profile = {}for key, value in kwargs.items():profile[key] = valuereturn profileuser = build_profile(name="小明", age=18, city="北京", hobby="编程")
print(user) # 输出:{'name': '小明', 'age': 18, 'city': '北京', 'hobby': '编程'}
**kwargs
将接收所有额外的关键字参数,并将它们打包成一个字典。
3.5 综合使用各种参数
参数定义的顺序应该是:位置参数、默认参数、*args、**kwargs
def example_function(a, b, c=0, *args, **kwargs):print(f"a = {a}, b = {b}, c = {c}")print(f"args = {args}")print(f"kwargs = {kwargs}")example_function(1, 2, 3, 4, 5, x=6, y=7)
# 输出:
# a = 1, b = 2, c = 3
# args = (4, 5)
# kwargs = {'x': 6, 'y': 7}
四、返回值
函数可以使用return
语句返回值。返回值可以是任何类型的数据。
def square(number):"""返回数字的平方"""return number ** 2result = square(4)
print(result) # 输出:16
4.1 返回多个值
Python函数可以返回多个值,实际上是返回一个元组:
def get_dimensions(width, height):"""计算面积和周长"""area = width * heightperimeter = 2 * (width + height)return area, perimeterarea, perimeter = get_dimensions(5, 3)
print(f"面积:{area},周长:{perimeter}") # 输出:面积:15,周长:16
4.2没有返回值的函数
如果函数没有return
语句,或者return
后面没有表达式,函数会返回None
:
def greet(name):print(f"你好,{name}!")# 没有return语句result = greet("小明")
print(result) # 输出:None
五、函数作用域与变量
Python中的变量有作用域,决定了变量在哪些区域可以被访问。
5.1 局部变量
函数内部定义的变量称为局部变量,只能在函数内部访问:
def my_function():x = 10 # 局部变量print(x)my_function() # 输出:10
# print(x) # 错误:x只存在于函数内部
5.2全局变量
在函数外部定义的变量称为全局变量,可以在整个模块中访问:
y = 20 # 全局变量def print_global():print(y) # 可以访问全局变量print_global() # 输出:20
5.3修改全局变量
要在函数内部修改全局变量,需要使用global
关键字:
counter = 0 # 全局变量def increment():global countercounter += 1print(f"计数器:{counter}")increment() # 输出:计数器:1
increment() # 输出:计数器:2
但要注意,过度使用全局变量会使代码难以理解和维护,一般情况下应该避免修改全局变量。
六、函数是一等公民
在Python中,函数是"一等公民",意味着函数可以:
- 赋值给变量
- 作为参数传递给其他函数
- 作为其他函数的返回值
6.1函数赋值给变量
def say_hello(name):return f"你好,{name}!"# 将函数赋值给变量
greeter = say_hello
result = greeter("小明")
print(result) # 输出:你好,小明!
6.2函数作为参数
def apply_twice(func, arg):"""对参数应用函数两次"""return func(func(arg))def add_five(x):return x + 5result = apply_twice(add_five, 10)
print(result) # 输出:20 (10+5+5)
6.3函数作为返回值
def get_math_function(operation):"""返回一个数学函数"""def add(x, y):return x + ydef subtract(x, y):return x - yif operation == "add":return addelse:return subtract# 获取加法函数
add_function = get_math_function("add")
result = add_function(5, 3)
print(result) # 输出:8
七、匿名函数(Lambda 函数)
当需要一个简单的函数,又不想正式定义一个函数时,可以使用lambda表达式:
# 常规函数
def square(x):return x ** 2# 等价的lambda函数
square_lambda = lambda x: x ** 2print(square(5)) # 输出:25
print(square_lambda(5)) # 输出:25
lambda函数常用于需要函数对象的场合,如排序或过滤:
# 按照姓名长度排序
names = ["张三", "李四", "王小明", "赵云"]
names.sort(key=lambda name: len(name))
print(names) # 输出:['张三', '李四', '赵云', '王小明']# 过滤列表中的偶数
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # 输出:[2, 4, 6, 8, 10]
八、递归函数
递归是一种函数调用自身的技术。递归需要有基本情况(停止条件)和递归情况:
def factorial(n):"""计算阶乘 n!"""if n == 0 or n == 1: # 基本情况return 1else: # 递归情况return n * factorial(n-1)print(factorial(5)) # 输出:120 (5*4*3*2*1)
递归函数的另一个例子是计算斐波那契数列:
def fibonacci(n):"""返回斐波那契数列的第n个数"""if n <= 0:return "输入必须是正整数"elif n == 1:return 0elif n == 2:return 1else:return fibonacci(n-1) + fibonacci(n-2)for i in range(1, 11):print(f"fibonacci({i}) = {fibonacci(i)}")
注意:Python的默认递归深度限制较小(通常是1000),深递归可能导致栈溢出。对于复杂的递归问题,考虑使用迭代方法或记忆化技术优化。
九、文档字符串(Docstring)
良好的函数应该有文档字符串,说明函数的功能、参数和返回值:
def calculate_area(length, width):"""计算矩形的面积。参数:length (float): 矩形的长度width (float): 矩形的宽度返回:float: 矩形的面积"""return length * width
文档字符串可以通过help()
函数或__doc__
属性查看:
help(calculate_area)
print(calculate_area.__doc__)
十、函数的最佳实践
- 单一职责原则:每个函数应该只做一件事,并且做好
- 函数名应清晰表明其功能:如
calculate_tax
比calc_t
更好 - 参数数量适中:通常不超过5个参数
- 提供适当的默认值:让常用情况调用更简单
- 返回值与函数名一致:如
get_
开头的函数应该返回值而不是修改参数 - 添加文档字符串:说明函数的用途、参数和返回值
- 异常处理:合理处理错误情况
- 避免副作用:尽量不要在函数内修改全局状态
十一、实用示例
示例1:基于函数的简单计算器
def add(x, y):"""将两个数相加"""return x + ydef subtract(x, y):"""从第一个数中减去第二个数"""return x - ydef multiply(x, y):"""将两个数相乘"""return x * ydef divide(x, y):"""将第一个数除以第二个数"""if y == 0:return "错误:除数不能为零"return x / ydef calculator():"""简单计算器函数"""print("选择操作:")print("1. 加法")print("2. 减法")print("3. 乘法")print("4. 除法")choice = input("输入选项(1/2/3/4): ")num1 = float(input("输入第一个数字: "))num2 = float(input("输入第二个数字: "))if choice == '1':print(f"{num1} + {num2} = {add(num1, num2)}")elif choice == '2':print(f"{num1} - {num2} = {subtract(num1, num2)}")elif choice == '3':print(f"{num1} * {num2} = {multiply(num1, num2)}")elif choice == '4':print(f"{num1} / {num2} = {divide(num1, num2)}")else:print("无效的输入")# 运行计算器
calculator()
示例2:文本处理函数
def word_counter(text):"""计算文本中的单词数量"""if not text:return 0words = text.split()return len(words)def character_frequency(text):"""计算每个字符在文本中出现的频率"""freq = {}for char in text:if char in freq:freq[char] += 1else:freq[char] = 1return freqdef is_palindrome(text):"""检查文本是否为回文"""# 移除空格并转换为小写text = text.lower().replace(" ", "")return text == text[::-1]# 测试这些函数
sample_text = "Madam, I'm Adam"
print(f"单词数: {word_counter(sample_text)}")
print(f"字符频率: {character_frequency(sample_text)}")
print(f"是否为回文: {is_palindrome(sample_text)}")
示例3:使用高阶函数处理数据
def process_data(data, operations):"""对数据应用一系列操作参数:data: 要处理的数据operations: 要应用的函数列表返回:处理后的数据"""result = datafor operation in operations:result = operation(result)return result# 定义一些操作函数
def remove_duplicates(items):"""移除列表中的重复元素"""return list(set(items))def sort_items(items):"""对列表元素排序"""return sorted(items)def keep_positive(numbers):"""只保留正数"""return [n for n in numbers if n > 0]# 测试数据处理
data = [3, -1, 7, 2, -5, 7, 3, 6, -8, 2]
operations = [remove_duplicates, sort_items, keep_positive]
result = process_data(data, operations)
print(f"原始数据: {data}")
print(f"处理后: {result}")
小结
函数是Python编程的基石,是代码复用和模块化的关键机制。通过合理设计和使用函数,你可以使代码更加清晰、简洁和可维护。本文介绍了函数的基本概念、参数传递、返回值、作用域和高级技巧,涵盖了Python函数编程的核心内容。
随着编程经验的积累,你会逐渐掌握如何设计好的函数和如何有效组合函数来解决复杂问题。函数不仅是处理代码重复的工具,更是表达程序逻辑和构建软件架构的重要组成部分。
思考题:
- 编写一个函数,接受一个列表和一个数字n作为参数,返回列表中所有大于n的元素。
- 使用递归函数计算一个数字的所有位数之和(例如:123的位数之和是1+2+3=6)。
- 编写一个装饰器函数,可以计算任何函数的执行时间并打印出来。
问题1: 编写一个函数,接受一个列表和一个数字n作为参数,返回列表中所有大于n的元素。
方法一:使用循环
def get_greater_than(numbers, n):"""返回列表中所有大于n的元素Args:numbers: 数字列表n: 比较基准值Returns:包含所有大于n的元素的列表"""result = []for num in numbers:if num > n:result.append(num)return result# 测试
numbers = [1, 5, 10, 3, 7, 2, 9]
print(get_greater_than(numbers, 5)) # 输出: [10, 7, 9]
方法二:使用列表推导式
def get_greater_than(numbers, n):"""返回列表中所有大于n的元素Args:numbers: 数字列表n: 比较基准值Returns:包含所有大于n的元素的列表"""return [num for num in numbers if num > n]# 测试
numbers = [1, 5, 10, 3, 7, 2, 9]
print(get_greater_than(numbers, 5)) # 输出: [10, 7, 9]
方法三:使用filter函数
def get_greater_than(numbers, n):"""返回列表中所有大于n的元素Args:numbers: 数字列表n: 比较基准值Returns:包含所有大于n的元素的列表"""return list(filter(lambda x: x > n, numbers))# 测试
numbers = [1, 5, 10, 3, 7, 2, 9]
print(get_greater_than(numbers, 5)) # 输出: [10, 7, 9]
问题2: 使用递归函数计算一个数字的所有位数之和
方法一:基本递归实现
def sum_digits(n):"""计算一个数字的所有位数之和Args:n: 要计算的数字Returns:所有位数之和"""# 处理负数if n < 0:n = -n# 基本情况:当n小于10时,直接返回nif n < 10:return n# 递归情况:返回最后一位加上其余位数的和return n % 10 + sum_digits(n // 10)# 测试
print(sum_digits(123)) # 输出: 6 (1+2+3)
print(sum_digits(9876)) # 输出: 30 (9+8+7+6)
print(sum_digits(-123)) # 输出: 6 (1+2+3)
方法二:使用字符串转换
def sum_digits(n):"""计算一个数字的所有位数之和Args:n: 要计算的数字Returns:所有位数之和"""# 处理负数if n < 0:n = -n# 转换为字符串n_str = str(n)# 基本情况:只有一位数if len(n_str) == 1:return n# 递归情况:第一位加上剩余位数的和return int(n_str[0]) + sum_digits(int(n_str[1:]))# 测试
print(sum_digits(123)) # 输出: 6 (1+2+3)
print(sum_digits(9876)) # 输出: 30 (9+8+7+6)
问题3: 编写一个装饰器函数,可以计算任何函数的执行时间并打印出来。
import time
import functoolsdef timing_decorator(func):"""装饰器函数,用于计算被装饰函数的执行时间Args:func: 被装饰的函数Returns:包装后的函数"""@functools.wraps(func) # 保留原函数的元数据def wrapper(*args, **kwargs):start_time = time.time() # 记录开始时间result = func(*args, **kwargs) # 执行被装饰的函数end_time = time.time() # 记录结束时间execution_time = end_time - start_time # 计算执行时间print(f"函数 '{func.__name__}' 的执行时间: {execution_time:.6f} 秒")return result # 返回原函数的结果return wrapper# 测试装饰器
@timing_decorator
def slow_function(delay):"""一个模拟耗时操作的函数"""time.sleep(delay) # 模拟耗时操作return f"完成了 {delay} 秒的操作"@timing_decorator
def calculate_sum(n):"""计算从1到n的和"""total = 0for i in range(1, n + 1):total += ireturn total# 测试函数
print(slow_function(1)) # 会输出执行时间约1秒
print(calculate_sum(1000000)) # 会输出计算结果和执行时间
高级版本:支持参数的装饰器
import time
import functoolsdef timing_decorator(print_args=False):"""带参数的装饰器工厂函数,用于计算被装饰函数的执行时间Args:print_args: 是否打印函数参数Returns:装饰器函数"""def decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):start_time = time.time()result = func(*args, **kwargs)end_time = time.time()execution_time = end_time - start_timeif print_args:args_repr = [repr(a) for a in args]kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]signature = ", ".join(args_repr + kwargs_repr)print(f"函数 '{func.__name__}({signature})' 的执行时间: {execution_time:.6f} 秒")else:print(f"函数 '{func.__name__}' 的执行时间: {execution_time:.6f} 秒")return resultreturn wrapper# 处理不带参数的调用方式 @timing_decoratorif callable(print_args):func = print_argsprint_args = Falsereturn decorator(func)# 处理带参数的调用方式 @timing_decorator(print_args=True)return decorator# 测试不同的调用方式
@timing_decorator
def function1(n):"""测试基本装饰器"""time.sleep(n)return n@timing_decorator(print_args=True)
def function2(n, m=1):"""测试带参数的装饰器"""time.sleep(n)return n * m# 测试函数
print(function1(0.5)) # 输出执行时间但不显示参数
print(function2(0.5, m=2)) # 输出执行时间和参数
这个高级版本的装饰器支持两种调用方式:
- 简单方式:
@timing_decorator
- 带参数方式:
@timing_decorator(print_args=True)
同时,当print_args=True
时,它还会打印出函数调用的参数,便于更详细的性能分析。