**递归函数**
在上一章嵌套函数拆分之后,我们在函数内部调用了外部其他函数。如果一个函数在内部调用自身本身,那么这个函数就被称为递归函数。
递归函数定义简单,逻辑清晰。理论上,所有的递归函数都可以改写成循环结构。
在数学上我们求阶乘
~~~
n! = n * (n-1) * ...*3*2*1
~~~
我们用函数 myFact(n) 表示
~~~
myFact(n) = n! = n * (n-1) * ...*3*2*1 = n * (n-1)! = n * myFact(n-1)
~~~
我们知道 1! 阶乘是 1 ,0! 阶乘也是 1,所以当 n = 1 与 0 时,就需要进行特殊处理了
函数如下
~~~
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
def myFact(n):
if not isinstance(n,int) :
return '%s is error type' % n
if n < 0 :
return 'parameter is not less than 0'
if n < 2 :
return 1
return n * myFact(n-1)
print(myFact('123')) #第一次调用
print(myFact(-1)) #第二次调用
print(myFact(0)) #第三次调用
print(myFact(5)) #第四次调用
~~~
调用结果
~~~
123 is error type #第一次调用结果
parameter is not less than 0 #第二次调用结果
1 #第三次调用结果
120 #第四次调用结果
~~~
递归特性
1. 必须有一个明确的结束条件。
2. 递归每深入一层,问题规模相比上次递归都应有所减少。
3. 递归效率不高,程序中能不用递归就不用。
**递归函数需要注意防止栈溢出。**
什么叫栈:又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。
栈,我们可以通俗理解为水桶,只能从一端进出水,"先进后出" 原则。
图片示意
![栈](https://box.kancloud.cn/710dff3d169fc8fd1fe5f245a4e1041b_511x151.png)
在计算机中,函数调用是通过栈(stack)这种数据结构实现的,在调用一个函数的时候栈就会加一层,在函数返回的时候,栈就会减一层。由于栈的大小不是无限的,所以,如果递归调用的次数过多,就会导致栈溢出。
当调用并打印 print(myFact(999)),就出现了栈溢出
~~~
airvip@airvip:/mnt/e/workspace/pylearn$ ./test.py
Traceback (most recent call last):
File "./test.py", line 18, in <module>
print(myFact(999))
...
File "./test.py", line 12, in myFact
return n * myFact(n-1)
File "./test.py", line 8, in myFact
if n < 0 :
RecursionError: maximum recursion depth exceeded in comparison
~~~
那如何解决因多次调用造成的栈溢出呢?
我们可以通过 **尾递归** 进行优化,实际上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归:在函数返回的时候,只是调用自己本身,并且 return 语句不能包含表达式。这样编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
上面的 myFact(n) 函数由于 return n * myFact(n-1) 引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,主要是要把每一步的乘积传入到递归函数中
~~~
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
def fact(n):
if not isinstance(n,int) :
return '%s is error type' % n
if n < 0 :
return 'parameter is not less than 0'
return fact_iter( n)
def fact_iter(count,res=1):
if count < 2:
return res
return fact_iter(count - 1,res * count,)
~~~
可以看到,return fact_iter(res * count, count - 1) 返回的是递归函数本身,每个参数在函数调用前就会被计算,不影响函数调用。
fact(5) 对应的 fact_iter(5, 1) 的调用如下:
~~~
第一次调用 : fact_iter(5,1)
第二次调用 : fact_iter(4,5)
第三次调用 : fact_iter(3,20)
第四次调用 : fact_iter(2,60)
第五次调用 : fact_iter(1,120)
第六次调用 :120
~~~
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的 fact(n) 函数改成尾递归方式,也会导致栈溢出。
有一个针对尾递归优化的装饰器 (decorator),大家可以参考下
~~~
#!/usr/bin/env python2.4
# This program shows off a python decorator(
# which implements tail call optimization. It
# does this by throwing an exception if it is
# it's own grandparent, and catching such
# exceptions to recall the stack.
import sys
class TailRecurseException:
def __init__(self, args, kwargs):
self.args = args
self.kwargs = kwargs
def tail_call_optimized(g):
"""
This function decorates a function with tail call
optimization. It does this by throwing an exception
if it is it's own grandparent, and catching such
exceptions to fake the tail call optimization.
This function fails if the decorated
function recurses in a non-tail context.
"""
def func(*args, **kwargs):
f = sys._getframe()
if f.f_back and f.f_back.f_back \
and f.f_back.f_back.f_code == f.f_code:
raise TailRecurseException(args, kwargs)
else:
while 1:
try:
return g(*args, **kwargs)
except TailRecurseException, e:
args = e.args
kwargs = e.kwargs
func.__doc__ = g.__doc__
return func
@tail_call_optimized
def factorial(n, acc=1):
"calculate a factorial"
if n == 0:
return acc
return factorial(n-1, n*acc)
print factorial(10000)
# prints a big, big number,
# but doesn't hit the recursion limit.
@tail_call_optimized
def fib(i, current = 0, next = 1):
if i == 0:
return current
else:
return fib(i - 1, next, current + next)
print fib(10000)
# also prints a big number,
# but doesn't hit the recursion limit.
~~~
- 跟老司机学Python
- 了解Python
- 1、python简介
- 2、Python发展史
- 3、Python特点
- 4、Python解释器
- 入坑Python
- 1、Python环境搭建
- 2、设置环境变量
- 3、开始使用Python
- 4、运行Python程序
- 掌握Python基础
- Python基础知识
- Python运算符
- 算术运算符
- 比较运算符
- 赋值运算符
- 逻辑运算符
- 位运算符
- 成员运算符
- 身份运算符
- 运算符优先级
- Python的变量与常量
- Python数据类型
- Number数值
- String字符串
- List列表
- Tuple元组
- Dictionary字典
- Set集合
- Python流程控制语句
- 条件判断
- 循环控制
- Python函数
- 1、函数是什么
- 2、函数的定义
- 3、函数的参数
- 4、函数的调用
- 5、嵌套函数
- 6、递归函数
- 7、匿名函数
- 8、函数式编程
- 9、高阶函数
- 1、map
- 2、reduce
- 3、filter
- 4、sorted
- 10、返回函数
- 11、偏函数
- Python文件IO操作
- 标准输入输出
- 读写文件
- with读写文件
- Python高级特性
- 1、列表生成式
- 2、迭代器
- 3、生成器
- 4、装饰器
- Python模块初探
- 1、模块与包
- 2、创建模块
- 3、模块中的作用域
- 4、模块的导入
- Python面向对象编程
- 类与对象
- 类的定义及使用
- 面向对象特性
- 类中的访问域
- 查看对象详情
- Python面向对象进阶
- 类中的方法
- 类中的特殊成员
- 限制对象的属性
- 再聊多继承
- 装x式创建类
- 创建元类
- Python内置模块
- os模块
- sys模块
- random模块
- time模块
- datetime模块
- shutil模块
- collections模块
- base64模块
- json模块
- hashlib模块
- xml模块
- 1. SAX解析XML
- 2. DOM解析XML
- 3. ElementTree解析XML
- urllib模块
- logging模块
- re模块
- Python第三方模块
- Pillow与PIL
- Requests
- Tablib
- Xpinyin
- Chardet
- Pycurl
- Virtualenv
- Python操作数据库
- Mysql操作
- Sqllite操作
- Mongodb操作
- Redis操作
- Python的GUI编程
- Python中GUI的选择
- Tkinter
- wxPython
- Socket网络编程
- Socket网络编程简聊
- Socket内建方法
- TCP协议编程
- UDP协议编程
- TCP与UDP
- Python电子邮件
- SMTP发邮件
- POP3收邮件
- IMAP收邮件
- 进程线程
- 进程与线程
- 进程编程
- 使用进程池
- 进程间的通信
- 线程编程
- 使用线程锁
- Python的GIL
- Web编程
- WSGI介绍
- Web框架
- Flask使用
- 模板jinjia2使用
- 项目结构规划
- 异步与协程
- 概念扫盲
- 异步IO进化之路
- 协程是什么
- yield协程编程
- yield from
- asyncio
- async与await
- aiohttp之client
- aiphttp之server
- 网络爬虫
- 网络爬虫简聊
- Beautiful Soup模块的使用
- pyquery 模块的使用
- selenium模块的使用
- 爬取段子实例
- 错误与调试
- 错误
- 错误处理
- 抛出错误
- 高效的调试
- Python重要语句
- pass语句
- return语句
- Python开发必知
- pip使用详解
- 如何对列表进行迭代
- 如何对字典进行迭代
- 字符串格式化
- 知识扩展
- 网络模型
- GUI是什么