递归函数, 匿名函数, yield from

递归函数

函数执行流程

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

posted @ 2017-11-07 09:37  yangbin  阅读(499)  评论(0编辑  收藏  举报