递归函数

函数执行流程

http://pythontutor.com/visualize.html#mode=edit

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

def foo1(b, b1=3):

print('foo1 called', b, b1)

def foo2(c):

foo3(c)

print('foo2 called', c)

def foo3(d):

print('foo3 called', d)

def main():

print('main called')

foo1(100, 101)

foo2(200)

print('main ending')

main()

# 执行结果:

main called

foo1 called 100 101

foo3 called 200

foo2 called 200

main ending

1)  全局帧中生成foo1,foo2,foo3,main函数对象.

2)  main函数调用.

3)main中查找内建函数print压栈,将常量字符串压栈,调用函数,弹出栈顶.

4)main中全局查找函数foo1压栈,将常量100,101压栈,调用函数foo1,创建栈帧.print函数压栈,字符串和变量b,b1压栈,调用函数,弹出栈顶,返回值.

5)main中全局查找foo2函数压栈,将常量200压栈,调用foo2,创建栈帧.foo3函数压栈,变量c引用压栈,调用foo3,创建栈帧.foo3完成print函数调用后返回.foo2恢复调用,执行print后,返回值.main中foo2调用结束弹出栈顶,main继续执行print函数调回,弹出栈顶.main函数返回.

递归Recursion

递归动态图示: http://codingpy.com/article/10-gifs-to-understand-some-programming-concepts/

函数直接或间接调用自身就是递归.

递归需要有边界条件,递归前进段,递归返回段.

递归一定要有边界条件.

当边界条件不满足的时候,递归前进.

当边界条件满足的时候,递归返回.

斐波那契数列:

如果设F(n)为该数列的第n项,(n∈N-1),那么这句话可以写成:F(n) = F(n-1) + F(n-2)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

# F(0) = 0, F(1) = 1, F(n) = F(n-1)+F(n-2)

pre = 0

cur = 1

print(pre, cur, end=' ')

n = 4

for i in range(n-1):

pre, cur = cur, pre + cur

print(cur, end=' ')

# 运行结果:

0 1 1 2 3

# F(0) = 0, F(1) = 1, F(n) = F(n-1)+F(n-2)

def fib(n):

return 1 if n < 2 else fib(n-1) + fib(n-2)

for i in range(4):

print(fib(i), end = ' ')

# 运行结果:

1 1 2 3

# 解析:

fib(3) + fib(2).

fib(3)调用fib(3), fib(2), fib(1).

fib(2)调用fib(2), fib(1).

fib(1)是边界.

递归要求:

递归一定要有退出条件,递归调用一定要执行到这个退出条件.没有退出条件的递归调用,就是无限调用.

递归调用的深度不宜过深:

python对递归调用的深度做了限制.

超过递归深度限制,抛出RecursionError:maxinum recursion depth exceeded超出最大深度.

sys.getrecursionlimit().

递归的性能

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

# for循环.

import datetime

start = datetime.datetime.now()

pre = 0

cur = 1

print(pre, cur, end=' ')

n = 35

for i in range(n-1):

pre, cur = cur, pre + cur

print(cur, end=' ')

delta = (datetime.datetime.now() - start).total_seconds()

print(delta)

# 运行结果:

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 0.0

# 递归:

import datetime

n = 35

start = datetime.datetime.now()

def fib(n):

return 1 if n < 2 else fib(n-1) + fib(n-2)

for i in range(n):

print(fib(i), end=' ')

delta = (datetime.datetime.now() - start).total_seconds()

print(delta)

# 运行结果:

1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 8.26452

循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果.

fib函数代码极简易懂,但是只能获取到最外层的函数调用,内部递归结果都是中间结果,而且给定一个n都要进行2n次递归,深度越深,效率越低.

为了获取斐波那契数列需要外面再套一个n次的循环,效率就更低了.

递归还有深度限制,如果递归复杂,函数反复压栈,栈内存很快就溢出了.

斐波那契数列的改进:

1

2

3

4

5

6

7

8

9

10

11

12

13

pre = 0

cur = 1

print(pre, cur, end = ' ')

def fib(n, pre=0, cur=1):

pre, cur = cur, pre + cur

print(cur, end = ' ')

if n == 2:

return

fib(n-1, pre, cur)

fib(4)

# 改进

1)左边的fib函数和循环的思想类似.

2)参数n是边界条件,用n来计数.

3)上一次的计算结果直接作为函数的实参.

4)效率很高.

5)和循环相比,性能近似,所以并不是说递归一定效率低下,但是递归有深度限制.

间接递归

1

2

3

4

5

6

7

8

# 间接递归

def foo1():

foo2()

def foo2():

foo1()

foo1()

间接递归,是通过别的函数调用了函数自身.

但是,如果构成了循环递归是非常危险的,但是往往这种情况在复杂代码情况下,还是可能发生这种调用.要用代码规范来避免这种递归调用的发生.

递归总结

递归是一种很自然的表达,符合逻辑思维.

递归相对运行效率低,每一次调用函数都要开辟栈帧.

递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出了.

如果是有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微要复杂一些,但是只要不是死循环,可以多次迭代直至算出结果.

绝大多数递归,都可以使用循环实现.

即使递归代码很简洁,但是能不用则不用递归.

匿名函数

匿名,即没有名字.

匿名函数,即没有名字的函数.

没有名字如何定义?

没有名字如何调用?

如果能调用,如何使用?

python借助lambda表达式构建匿名函数.

格式:

lambda 参数列表: 表达式

如:

lambda x: x**2

(lambda x: x**2)(4)  # 调用.

foo = lambda x,y:(x+y)**2  # 不推荐这么用.

foo(2,1)

def foo(x,y):  # 建议使用普通函数.

return (x+y)**2

foo(2,1)

使用lambda关键字来定义匿名函数.

参数列表不需要小括号.

冒号是用来分割参数列表和表达式的.

不需要使用return,表达式的值,就是匿名函数返回值.

lambda表达式(匿名函数)只能写在一行上,被称为单行函数.

用途: 在高阶函数传参时,使用lambda表达式,往往能简化代码.

1

2

3

4

5

6

7

8

9

10

print((lambda :0)())

print((lambda x, y=3: x + y)(5))

print((lambda x, y=3: x + y)(5, 6))

print((lambda x, *, y=30: x + y)(5))

print((lambda x, *, y=30: x + y)(5, y=10))

print((lambda *args: (x for x in args))(*range(5)))

print((lambda *args: [x+1 for x in args])(*range(5)))

print((lambda *args: {x+2 for x in args})(*range(5)))

[x for x in (lambda *args: map(lambda x: x+1, args))(*range(5))] # 高阶函数.

[x for x in (lambda *args: map(lambda x: (x+1,args), args))(*range(5))]

生成器

生成器generator

生成器指的是生成器对象,可以由生成器表达式得到,也可以使用yield关键字得到一个生成器函数,调用这个函数得到一个生成器对象.

生成器函数:

函数体中包含yield语句的函数,返回生成器对象.

生成器对象,是一个可迭代对象,是一个迭代器.

生成器对象,是延迟计算,惰性求值的.

举例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

def inc():

for i in range(5):

yield i

print(type(inc))

print(type(inc()))

x = inc()

print(type(x))

print(next(x))

for m in x:

print(m, '*')

for m in x:

print(m, '**')

# 运行结果:

<class 'function'>

<class 'generator'>

<class 'generator'>

0

1 *

2 *

3 *

4 *

y = (i for i in range(5))

print(type(y))

print(next(y))

print(next(y))

# 运行结果:

<class 'generator'>

0

1

普通的函数调用fn(),函数会立即执行完毕,但是生成器函数可以使用next函数多次执行.

生成器函数等价于生成器表达式,只不过生成器函数可以更加的复杂.

举例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

def gen():

print('line 1')

yield 1

print('line 2')

yield 2

print('line 3')

return 3

next(gen()) # line 1

next(gen()) # line 1

g = gen()

print(next(g)) # line 1

print(next(g)) # line 2

# print(next(g)) # StopIteration, 此处生成器找不到yield所以抛异常.

print(next(g, 'End')) # 没有元素给个缺省值.

# 运行结果:

line 1

line 1

line 1

1

line 2

2

line 3

End

在生成器函数中,使用多个yield语句,执行一次后会暂停执行,把yield表达式的值返回.

再次执行,会执行到下一个yield语句.

return语句依然可以终止函数运行,但return语句的返回值不能被获取到.

return会导致无法继续获取下一个值,抛出StopIteration异常.

如果函数没有显示的return语句,如果生成器函数执行到结尾,一样会抛出异常StopIteration.

生成器函数:

包含yield语句的生成器函数生成生成器对象的时候,生成器函数的函数体不会立即执行.

next(generator)会从函数的当前位置向后执行之后碰到的第一个yield语句,会弹出值,并暂停函数执行.

再次调用next函数,和上一条一样的处理过程.

没有多余的yield与能被执行,继续调用next函数,会抛异常StopIteration.

生成器应用

生成器函数:

包含yield语句的生成器函数生成生成器的时候,生成器函数的函数体不会立即执行.

next(generator)会从函数的当前位置向后执行到之后碰到的一个yield语句,会弹出值,并暂停函数执行.

再次调用next函数,和上一条一样的处理过程.

没有多余的yield语句能被执行,继续调用next函数,会抛异常StopIterator.

判断一个函数是否为generator函数: 使用isgeneratorfunction判断.

In [6]: from inspect import isgeneratorfunction

In [7]: isgeneratorfunction(func)

Out[7]: True

要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator.

>>> from collections import Iterable

>>> isinstance(fab, Iterable)

False

>>> isinstance(fab(5), Iterable)

True

在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

def counter():

i = 0

while True:

i += 1

yield i

def inc(c):

return next(c)

c = counter()

print(inc(c))

print(inc(c))  # 对同一函数对象进行操作.

# 运行结果:

1

2

def counter():

i = 0

while True:

i += 1

yield i

def inc():

c = counter()

return next(c)

print(inc())

print(inc())

print(inc())  # 对三个不同的函数对象进行操作.

# 运行结果:

1

1

1

计数器:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

def inc():

def counter():

i = 0

while True:

i += 1

yield i

c = counter()

return lambda : next(c)

foo = inc()

print(foo())

print(foo())

# 运行结果:

1

2

Lambda表达式是匿名函数.

Return返回的是一个匿名函数.

左边第8行等价于:

def _inc():

return next(c)

return _inc

处理递归问题:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

def fib():

x = 0

y = 1

while True:

yield y

x, y = y, x+y

foo = fib()

for _ in range(5):

print(next(foo))

for _ in range(100):

# print(next(foo))

next(foo)

print(next(foo))

# 运行结果:

1

1

2

3

5

6356306993006846248183

# 等价于下面代码:

pre = 0

cur = 1

print(pre, cur, end = ' ')

def fib1(n, pre=0, cur=1):

pre, cur = cur, pre + cur

print(cur, end = ' ')

if n == 2:

return

fib1(n-1, pre, cur)

fib1(5)

协程coroutine:

生成器的高级用法.

比进程,线程轻量级.

是在用户空间调度函数的一种实现.

python3 asyncio就是协程实现,已经加入到标准库.

python3.5使用async,await关键字直接原生支持协程.

协程调度实现思路:

有2个生成器A,B.

next(A)后,A执行到yield语句暂停,然后执行next(B),B执行到yield语句也暂停,就再次调用next(A),再调用next(B),周而复始,就实现了调度的效果.

可以引入调度的策略来实现切换的方式.

协程是一种非抢占式调度.

另一个 yield 的例子来源于文件读取。如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。

通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:

1

2

3

4

5

6

7

8

9

def read_file(fpath):

BLOCK_SIZE = 1024

with open(fpath, 'rb') as f:

while True:

block = f.read(BLOCK_SIZE)

if block:

yield block

else:

return

yield from

举例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

def inc():

for x in range(1000):

yield x

foo = inc()

print(next(foo))

print(next(foo))

print(next(foo))

# 运行结果:

0

1

2

# 左边等同于如下代码:

def inc():

# for x in range(1000):

#     yield x

yield from range(1000)

foo = inc()

print(next(foo))

print(next(foo))

print(next(foo))

yield from是python3.3出现的新的语法.

yield from iterable是for item in iterable: yield item形式的语法糖.

1

2

3

4

5

6

7

8

9

10

11

12

# 从可迭代对象中一个个拿元素:

def counter(n):  # 生成器,迭代器

for x in range(n):

yield x

def inc(n):

yield from counter(n)

foo = inc(10)

print(next(foo))

print(next(foo))

最新文章

  1. 一次kubernetes资源文件创建失败的排查
  2. Java的List排序
  3. 边工作边刷题:70天一遍leetcode: day 80
  4. OC基础--OC中的类方法和对象方法
  5. 微信公众账号开发教程(一) 基本原理及微信公众账号注册 ——转自http://www.cnblogs.com/yank/p/3364827.html
  6. crash recovery
  7. LINQ to Entities 查询注意事项
  8. SE 2014年4月18日
  9. 移动开发中Fiddler的那些事儿 (转)
  10. ASP.NET MVC 分页问题
  11. OAF中的MASTER-DETAIL关系
  12. Python-定时爬取指定城市天气(二)-邮件提醒
  13. Spring.factories扩展机制
  14. codeforces 1042 e
  15. Java中 System.arraycopy() 和 Arrays.copyOf()方法
  16. sql字符处理
  17. git reset之后找回本地未提交的代码
  18. 强化学习算法Policy Gradient
  19. Mac OS X各版本号的历史费用和升级关系
  20. C# WebClient Get获取网页内容

热门文章

  1. 牛客网 小白赛4 A三角形【贪心】
  2. JVM的内存布局
  3. [BZOJ 1293] 生日礼物
  4. ThinkPHP处理海量数据分表机制详细代码及说明
  5. FindFirstVolume系列函数遍历驱动器,获取驱动器信息
  6. oracle 对应的JDBC驱动 版本
  7. iOS :学习新技术途径和sizeClasses屏幕适配
  8. php之文件类型解析漏洞防御与攻击
  9. Druid对比Cassandra
  10. Kubernetes用户指南(一)--快速开始、使用k8s配置文件