遞歸函數, 匿名函數, yield from

遞歸函數

函數執行流程

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

1python

2async

3函數

4性能

5spa

6線程

7代碼規範

8協程

9htm

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

相關文章
相關標籤/搜索