摘要:本文系统讲解了递归的核心概念、经典案例(阶乘、斐波那契、嵌套列表展平)及其优缺点,并深入探讨了 python 中函数作为一等公民的多种特性——函数是对象、可动态添加属性、可赋值给变量、可作为参数和返回值。在此基础上,进一步介绍了匿名函数 lambda 与高阶函数,并详细演示了 map、reduce、filter、sorted 四个内置高阶函数的用法,帮助读者掌握递归思维与函数式编程风格。
1. 什么是递归
递归是一种编程技巧,指的是函数在其定义中直接或间接地调用自身的过程。简单来说,就是一个函数在自己内部调用自己。递归的思想来源于数学中的归纳法——把一个大问题逐步分解为规模更小、结构相同的子问题,直到子问题简单到可以直接求解。
一个标准的递归函数通常包含两个核心部分:
- 递归终止条件(基线条件):定义什么时候停止递归,防止无限循环。没有终止条件的递归会导致栈溢出。
- 递归表达式(递归步骤):将原问题分解为更小的子问题,并通过调用自身来求解。
递归的经典类比是俄罗斯套娃——打开一个娃娃,里面还有一个更小的娃娃,一直打开到最小的那个为止(基线条件),然后再一层层合回去。
2. 递归的经典案例
下面通过几个经典例子来理解递归的运作方式。
2.1 计算阶乘
阶乘的定义:n! = n × (n-1) × (n-2) × ... × 1,且 0! = 1。这正是递归的天然应用场景。
def factorial(n):
"""递归计算阶乘"""
# 终止条件:0! = 1
if n == 0:
return 1
# 递归步骤:n! = n × (n-1)!
return n * factorial(n - 1)
print(factorial(5)) # 输出:120执行过程分析(以 factorial(5) 为例):
- factorial(5) → 5 × factorial(4)
- factorial(4) → 4 × factorial(3)
- factorial(3) → 3 × factorial(2)
- factorial(2) → 2 × factorial(1)
- factorial(1) → 1 × factorial(0)
- factorial(0) → 1(到达终止条件,开始逐层返回)
然后从最底层依次返回计算结果,最终得到 5 × 4 × 3 × 2 × 1 × 1 = 120。
2.2 斐波那契数列
斐波那契数列的定义:f(0) = 0,f(1) = 1,f(n) = f(n-1) + f(n-2)。
def fibonacci(n):
"""递归计算第 n 个斐波那契数"""
if n <= 0:
return 0
if n == 1:
return 1
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 输出:552.3 遍历嵌套列表
当数据结构本身具有递归特性时,递归是处理它们最自然的方式。
def flatten(nested_list):
"""递归展平嵌套列表"""
result = []
for item in nested_list:
if isinstance(item, list):
# 如果是列表,递归展平
result.extend(flatten(item))
else:
result.append(item)
return result
data = [1, [2, [3, 4], 5], 6, [7, 8]]
print(flatten(data)) # 输出:[1, 2, 3, 4, 5, 6, 7, 8]3. 递归的好处与优缺点
3.1 递归的优点
- 代码简洁优雅:递归能将复杂的问题用极少量的代码表达出来。比如汉诺塔问题,用递归只需几行代码,而迭代版本要复杂得多。
- 符合人类思维方式:许多问题天然具有递归结构(如树的遍历、分治算法),递归解法与问题定义高度一致,可读性强。
- 便于处理嵌套结构:对于树形结构、图遍历、嵌套列表等具有自相似特征的数据,递归几乎是必选方案。
3.2 递归的缺点
- 性能开销较大:每次函数调用都需要在调用栈上分配栈帧,保存局部变量和返回地址,递归深度过大时会消耗大量内存。
- 可能导致栈溢出:python 默认递归深度限制约为 1000 层,超过会抛出
recursionerror。可以通过sys.setrecursionlimit()调整,但治标不治本。 - 存在重复计算:以斐波那契数列为例,fibonacci(5) 会重复计算 fibonacci(3) 两次、fibonacci(2) 三次,造成指数级的时间复杂度。可以通过记忆化(memoization) 或者改用迭代来解决。
- 调试难度较高:递归的多层调用关系使得追踪执行流程和定位错误比迭代更困难。
3.3 何时使用递归
递归适合以下场景:问题本身具有明显的递归定义、数据结构是树或图、需要回溯搜索(如八皇后、迷宫问题)、分治算法(如归并排序、快速排序)。对于简单的线性问题,优先考虑迭代解法。
4. 深入理解函数——函数是"一等公民"
在 python 中,函数不仅是组织代码的基本单元,更是一等公民(first-class citizen)。这意味着函数可以像普通数据(如整数、字符串)一样被操作和使用。理解这一点是掌握 python 高级编程的关键。
4.1 函数也是对象
在 python 中,万物皆对象——函数也不例外。每个函数实际上都是 function 类的实例,拥有自己的属性和方法。
def greet(name):
"""一个简单的问候函数"""
return f"你好,{name}!"
函数是一个对象
print(type(greet)) # 输出:<class 'function'>
print(isinstance(greet, object)) # 输出:true
print(greet.name) # 输出:'greet'
print(greet.doc) # 输出:'一个简单的问候函数'4.2 函数可以动态添加属性
既然函数是对象,就可以像普通对象一样动态添加属性。这在需要为函数附加额外信息(如调用次数、配置参数等)时非常实用。
def process_data(data):
"""处理数据"""
process_data.call_count += 1
return [x * 2 for x in data]
动态添加属性
process_data.call_count = 0
process_data.author = "张三"
process_data.version = "1.0.0"
print(process_data([1, 2, 3])) # 输出:[2, 4, 6]
print(process_data.call_count) # 输出:1
print(process_data.author) # 输出:张三
process_data([4, 5, 6])
print(process_data.call_count) # 输出:2动态属性在实现装饰器和缓存机制时特别有用,可以为函数附加缓存字典、元数据或配置项。
4.3 函数可以赋值给变量
函数名本质上只是一个指向函数对象的引用,因此可以将函数赋值给另一个变量,通过新变量名来调用它。
def say_hello(name):
return f"hello, {name}!"
将函数赋值给变量(注意:不要加括号,加括号表示调用)
greeting = say_hello
welcome = say_hello
print(greeting("alice")) # 输出:hello, alice!
print(welcome("bob")) # 输出:hello, bob!
print(greeting is say_hello) # 输出:true,指向同一个对象这种特性使得我们可以灵活地为函数起别名,或者在运行时根据条件选择不同的函数实现。
4.4 函数可以作为参数传递
能够接受其他函数作为参数,或者将函数作为返回值返回的函数,称为高阶函数。这是函数式编程的核心思想。
def apply_twice(func, value):
"""将函数应用到值上两次"""
return func(func(value))
def add_three(x):
return x + 3
def multiply_two(x):
return x * 2
print(apply_twice(add_three, 5)) # 输出:11(5+3=8, 8+3=11)
print(apply_twice(multiply_two, 3)) # 输出:12(3×2=6, 6×2=12)这种模式让代码具有极高的灵活性和复用性——我们可以把行为(函数)作为参数传入,而不需要为每种场景写一套新代码。常见应用包括回调函数、事件处理器和排序时的 key 参数。
4.5 函数可以作为返回值
函数可以在内部定义另一个函数并返回它,这种技术通常用于创建闭包和函数工厂。
def make_multiplier(factor):
"""返回一个将输入乘以 factor 的函数"""
def multiplier(x):
return x * factor
return multiplier # 返回内部函数
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10)) # 输出:20
print(triple(10)) # 输出:30这里 make_multiplier 就像一个"函数工厂",根据不同的参数生产出行为不同的函数。multiplier 函数记住了外层函数中的变量 factor,即使在外层函数已经返回之后仍然可以访问——这就是闭包的机制。
5. 匿名函数与高阶函数
5.1 匿名函数(lambda 表达式)
匿名函数使用 lambda 关键字定义,语法为 lambda 参数: 表达式。它不需要函数名,只能包含单个表达式,适用于简单的、一次性的操作。
# 普通函数
def square(x):
return x * x
等价的匿名函数
square_lambda = lambda x: x * x
print(square(5)) # 输出:25
print(square_lambda(5)) # 输出:25
匿名函数最常见的场景:作为高阶函数的参数
numbers = [1, 2, 3, 4, 5]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # 输出:[2, 4]使用建议:lambda 适合简短的单行逻辑。如果逻辑复杂、需要多行代码或包含循环/异常处理,应使用普通命名函数,以保证可读性和可维护性。
5.2 高阶函数
高阶函数是指至少满足以下一个条件的函数:
- 接受一个或多个函数作为参数
- 返回一个函数作为结果
高阶函数是函数式编程的基石,它让代码更抽象、更模块化。前面 apply_twice 和 make_multiplier 都是高阶函数的例子。接下来,我们重点看看 python 内置的几个高阶函数。
6. python 内置的高阶函数
python 提供了四个非常实用的内置高阶函数:map、reduce、filter 和 sorted。它们配合 lambda 表达式,可以写出简洁而强大的数据处理流水线。
6.1 map 函数
map(func, iterable) 将函数 func 应用到可迭代对象的每一个元素上,返回一个迭代器,包含所有元素经过函数处理后的结果。
# 将列表中的每个数字平方 numbers = [1, 2, 3, 4, 5] squared = list(map(lambda x: x ** 2, numbers)) print(squared) # 输出:[1, 4, 9, 16, 25] 结合命名函数,将温度从摄氏度转为华氏度 celsius = [0, 10, 20, 30, 40] fahrenheit = list(map(lambda c: c * 9/5 + 32, celsius)) print(fahrenheit) # 输出:[32.0, 50.0, 68.0, 86.0, 104.0] map 可以接受多个可迭代对象,func 需要接受对应数量的参数 a = [1, 2, 3] b = [4, 5, 6] sums = list(map(lambda x, y: x + y, a, b)) print(sums) # 输出:[5, 7, 9]
适用场景:对序列中每个元素执行相同的转换操作,如类型转换、数学运算、格式规范化等。
6.2 filter 函数
filter(func, iterable) 使用函数 func 对可迭代对象的每个元素进行筛选,保留 func 返回 true 的元素,返回一个迭代器。
# 筛选出偶数 numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] evens = list(filter(lambda x: x % 2 == 0, numbers)) print(evens) # 输出:[2, 4, 6, 8, 10] 筛选出长度大于 3 的字符串 words = ["hi", "hello", "sun", "python", "go", "world"] long_words = list(filter(lambda w: len(w) > 3, words)) print(long_words) # 输出:['hello', 'python', 'world']
适用场景:根据条件过滤数据,如去除空值、筛选符合条件的记录等。
6.3 reduce 函数
reduce(func, iterable[, initial]) 位于 functools 模块中,它将一个接受两个参数的函数累积地应用到序列的元素上,将序列"归约"为一个单一值。工作方式是:先对前两个元素执行函数,得到结果后再与第三个元素执行函数,以此类推。
from functools import reduce 计算列表所有元素的乘积 numbers = [1, 2, 3, 4, 5] product = reduce(lambda x, y: x * y, numbers) print(product) # 输出:120(即 1×2×3×4×5) 找出列表中的最大值 values = [23, 45, 12, 67, 34, 89, 5] max_value = reduce(lambda x, y: x if x > y else y, values) print(max_value) # 输出:89 使用 initial 参数指定初始值 numbers = [1, 2, 3] total = reduce(lambda x, y: x + y, numbers, 10) print(total) # 输出:16(即 10+1+2+3)
执行过程详解(以 product 为例):
- 第一步:lambda(1, 2) → 2
- 第二步:lambda(2, 3) → 6
- 第三步:lambda(6, 4) → 24
- 第四步:lambda(24, 5) → 120
适用场景:累积计算(求和、求积)、合并数据、构建嵌套结构等需要将序列归约为单一值的操作。
6.4 sorted 函数
sorted(iterable, key=none, reverse=false) 返回一个新的排序后的列表。它虽然不是严格意义上的"接收函数作为参数"的高阶函数形式,但其 key 参数接受一个函数,用于指定排序的依据——这使它具备了高阶函数的特性。
# 按绝对值排序
numbers = [-5, 3, -1, 4, -2]
sorted_by_abs = sorted(numbers, key=lambda x: abs(x))
print(sorted_by_abs) # 输出:[-1, -2, 3, 4, -5]
按字符串长度排序
words = ["python", "go", "java", "c", "rust", "javascript"]
sorted_by_len = sorted(words, key=lambda w: len(w))
print(sorted_by_len) # 输出:['c', 'go', 'java', 'rust', 'python', 'javascript']
多条件排序:先按成绩降序,再按姓名升序
students = [
{"name": "张三", "score": 85},
{"name": "李四", "score": 92},
{"name": "王五", "score": 85},
{"name": "赵六", "score": 78},
]
sorted_students = sorted(students, key=lambda s: (-s["score"], s["name"]))
print(sorted_students)
输出:[{'name': '李四', 'score': 92}, {'name': '张三', 'score': 85},
{'name': '王五', 'score': 85}, {'name': '赵六', 'score': 78}]sorted 和列表的 list.sort() 方法功能相似,但 sorted 返回新列表且适用于任意可迭代对象,而 sort 在原地修改列表。
适用场景:对复杂数据结构按自定义规则排序,如按对象属性、按计算结果或按多个条件排序。
7. 总结
本文从递归的基础概念出发,通过阶乘、斐波那契和嵌套列表展平等案例展示了递归的适用场景和运作原理,并客观分析了递归的优缺点。接着深入探讨了 python 中函数作为"一等公民"的多种特性——函数是对象、可以动态添加属性、可以赋值给变量、可以作为参数和返回值——这些特性构成了 python 函数式编程和装饰器等高级特性的基础。最后,我们系统介绍了匿名函数 lambda 以及 map、reduce、filter、sorted 四个内置高阶函数,通过丰富的代码示例展示了它们在实际开发中的用法。
掌握递归和高阶函数,不仅能让你的代码更加简洁优雅,更能帮助你从更高的抽象层次思考和解决问题。建议读者在理解这些概念的基础上,多动手实践,将递归思维和函数式编程风格融入到日常编码中。
到此这篇关于python 递归与高阶函数从基础概念到实战应用指南的文章就介绍到这了,更多相关python 递归与高阶函数内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论