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函數返回. |
遞歸動態圖示: 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)) |