递归函数

函数执行流程

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))

递归函数, 匿名函数, yield from的更多相关文章

  1. Python内置函数二 (递归函数,匿名函数,二分法)

    匿名函数 lambda() 语法: lambad  参数 : 返回值 def func(a,b): return a * b print(func(2,5)) a = lambda a ,b : a* ...

  2. python全栈开发之匿名函数和递归函数

    python 匿名函数和递归函数 python全栈开发,匿名函数,递归函数 匿名函数 lambda函数也叫匿名函数,即函数没有具体的名称.是为了解决一些功能很简单需求而设计的一句话函数.如下: #这段 ...

  3. Python之路-函数基础&局部变量与全局变量&匿名函数&递归函数&高阶函数

    一.函数的定义与调用 函数:组织好的.可重复使用的.用户实现单一或者关联功能的代码段.函数能够提高应用的模块性和代码的重复利用率.Python提供了很多内置的函数,比如len等等,另外也可以根据自己的 ...

  4. Python入门-匿名函数,递归函数,主函数

    1.三目运算符 对简单的条件语句,可以用三元运算简写.三元运算只能写在一行代码里面 # 书写格式 result = 值1 if 条件 else 值2 # 如果条件成立,那么将 "值1&quo ...

  5. Python函数篇(二)之递归函数、匿名函数及高阶函数

    1.全局变量和局部变量 一般定义在程序的最开始的变量称为函数变量,在子程序中定义的变量称为局部变量,可以简单的理解为,无缩进的为全局变量,有缩进的是局部变量,全局变量的作用域是整个程序,而局部变量的作 ...

  6. Python函数篇(2)-递归函数、匿名函数及高阶函数

    1.全局变量和局部变量 一般定义在程序的最开始的变量称为函数变量,在子程序中定义的变量称为局部变量,可以简单的理解为,无缩进的为全局变量,有缩进的是局部变量,全局变量的作用域是整个程序,而局部变量的作 ...

  7. python基础——匿名函数及递归函数

    python基础--匿名函数及递归函数 1 匿名函数语法 匿名函数lambda x: x * x实际上就是: def f(x): return x * x 关键字lambda表示匿名函数,冒号前面的x ...

  8. python之内置函数(二)与匿名函数、递归函数初识

    一.内置函数(二)1.和数据结构相关(24)列表和元祖(2)list:将一个可迭代对象转化成列表(如果是字典,默认将key作为列表的元素).tuple:将一个可迭代对象转化成元组(如果是字典,默认将k ...

  9. day16 python之匿名函数,递归函数

    匿名函数 匿名函数格式 函数名 = lambda 参数 :返回值 #参数可以有多个,用逗号隔开 #匿名函数不管逻辑多复杂,只能写一行,且逻辑执行结束后的内容就是返回值 #返回值和正常的函数一样可以是任 ...

随机推荐

  1. Codeforces 801 A.Vicious Keyboard & Jxnu Group Programming Ladder Tournament 2017江西师大新生赛 L1-2.叶神的字符串

    A. Vicious Keyboard time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  2. 解决windows下文件拷贝到ubuntu下文件名乱码的问题

    sudo apt-get install convmv     解压zip文件:    convmv -f gbk -t utf8 -r --notest *

  3. Exchange2010启用反垃圾邮件功能

    今天邮箱服务器发现有大量发件人为空的邮件等待执行,也就是说空邮件堵塞了队列. 一般来说,空邮件就是别人发送垃圾邮件给你,你的服务上不存在这个收件人,那么系统会产生一封退信告诉你这封邮件已经被退.而ex ...

  4. 倒置输入的数 Exercise07_02

    import java.util.Scanner; /** * @author 冰樱梦 * 时间:2018年下半年 * 题目:倒置输入的数 * */ public class Exercise07_0 ...

  5. iOS消息传递机制

    每个应用或多或少都由一些需要相互传递消息的对象结合起来以完成任务.在这篇文章里,我们将介绍所有可用的消息传递机制,并通过例子来介绍怎样在苹果的框架里使用.我们还会选择一些最佳范例来介绍什么时候该用什么 ...

  6. JAVA 基本概念和编码规范

    概括性描述:一个Java程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作. 基本概念: 下面简要介绍下类.对象.方法和属性的概念. 对象:对象是类的一个实例,有状态和行为.例如, ...

  7. 简单说说DNS劫持_firefox吧_百度贴吧

    简单说说DNS劫持_firefox吧_百度贴吧 DNSSEC

  8. 在MEF中手动导入依赖的模块

    对于简单的场景来讲,在MEF中导入依赖模块非常简单,只要用ImportAttribute标记依赖的成员,MEF模块会自动找到并创建该模块.但有的时候我们依赖的模块是上下文相关的,此时MEF框架的自动组 ...

  9. sqlserver 调试WINDBG ---troubleshootingsql.com

    https://troubleshootingsql.com/tag/stack-dump/ Debugging that latch timeout Posted on August 26, 201 ...

  10. 集群Cluster介绍

    来源:http://www.ibm.com/developerworks/cn/linux/cluster/lw-clustering.html简单的说,集群(cluster)就是一组计算机,它们作为 ...