zoukankan      html  css  js  c++  java
  • 递归函数, 匿名函数, 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))

  • 相关阅读:
    2017 湖南省赛 K Football Training Camp
    一些相似单词的区别之处
    LeetCode301. Remove Invalid Parentheses
    算法刷题细节点总结
    LeetCode765. Couples Holding Hands
    LeetCode741. Cherry Pickup
    LeetCode312. Burst Balloons
    LeetCode679. 24 Game
    LeetCode862. Shortest Subarray with Sum at Least K
    LeetCode818. Race Car
  • 原文地址:https://www.cnblogs.com/amesy/p/7797467.html
Copyright © 2011-2022 走看看