1. 程式人生 > 其它 >18、函式遞迴

18、函式遞迴

一、生成器與yield

若函式體包含yield關鍵字,再呼叫函式,並不會執行函式體程式碼,得到的返回值即生成器物件

>>> def my_range(start,stop,step=1):
... print('start...')
... while start < stop:
... yield start
... start+=step
... print('end...')
...
>>> g=my_range(0,3)
>>> g
<generator object my_range at 0x000002283363A970>

生成器內建有iternext方法,所以生成器本身就是一個迭代器

>>> g.__iter__
<method-wrapper '__iter__' of generator object at 0x000002283363A970>
>>> g.__next__
<method-wrapper '__next__' of generator object at 0x000002283363A970>

因而我們可以用next(生成器)觸發生成器所對應函式的執行

>>> next(g) # 觸發函式執行直到遇到yield則停止,將yield後的值返回,並在當前位置掛起函式
start...
0
>>> next(g) # 再次呼叫next(g),函式從上次暫停的位置繼續執行,知道重新遇到yield...
1
>>> next(g)
2
>>> next(g)
end...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

既然生成器物件屬於迭代器,那麼必然可以使用for迴圈迭代,如下:

>>> for i in countdown(3):
... print(i)
...
countdown start
3
2
1
Done!

有了yield關鍵字,我們就有了一種自定義迭代器的實現方式。yield可以用於返回值,但不同於return,函式一旦遇到return就結束了,而yield可以儲存函式的執行狀態掛起函式,用來返回多次值

二、yield表示式應用

在函式內可以採用表示式形式的yield

>>> def eater():
... print('Ready to eat')
... while True:
... food = yield
... print('get the food:%s,and start to eat' % food)

可以拿到函式的生成器物件持續為函式體send值,如下

>>> g = eater()
>>> g
<generator object eater at 0x00000228335DAB30>
>>> next(g)
Ready to eat
>>> g.send('包子')
get the food:包子,and start to eat
>>> g.send('饅頭')
get the food:饅頭,and start to eat

針對表示式形式的yield,生成器物件必須事先被初始化一次,讓函式掛在food=yield的位置,等待呼叫g.send()為函式體傳值,g.send(None)等同於next(g)

我們可以編寫裝飾器來完成為所有表示式形式yield對應生成器的初始化操作,如下

def init(func):
def wrapper(*args, **kwargs):
g = func(*args, **kwargs)
next(g)
return wrapper
@init
def eater():
print('Ready to eat')
while True:
food = yield
print('get the food:%s,and start to eat' % food)

表示式形式的yield也可以用於返回多次值,即變數名=yield值的形式,如下

def init(func):
def wrapper(*args, **kwargs):
g = func(*args, **kwargs)
next(g)
return g

return wrapper


@init
def eater():
print('Ready to eat')
while True:
food = yield
print('get the food:%s,and start to eat' % food)


g = eater()
g.send('包子')

三、三元表示式、列表生成式、生成器表示式

三元表示式

三元表示式是python為我們提供的一種簡化程式碼的解決方案,語法如下

res = 條件成立時返回的值 if 條件 else 條件不成立時返回的值

針對下述場景

def max(x, y):
if x > y:
return x
else:
return y


res = max(1, 2)

用三元表示式可以一行解決

x = 1
y = 2
res = x if x > y else y

列表生成器

列表生成器是python為我們提供的一種簡化程式碼的解決方案,用來快速生成列表,語法如下

[expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
...
for itemN in iterableN if conditionN
]

#類似於
res=[]
for item1 in iterable1:
if condition1:
for item2 in iterable2:
if condition2
...
for itemN in iterableN:
if conditionN:
res.append(expression)

針對下述場景

egg_list=[]
for i in range(10):
egg_list.append('雞蛋%s' %i)

用列表生成器可以一行程式碼解決

>>> ['雞蛋%s' %i for i in range(10)]
['雞蛋0', '雞蛋1', '雞蛋2', '雞蛋3', '雞蛋4', '雞蛋5', '雞蛋6', '雞蛋7', '雞蛋8', '雞蛋9']

生成器表示式

建立一個生成器物件有兩種方式,一種是呼叫帶yield關鍵字的函式,另一種就是生成器表示式,與列表生成器的語法格式相同,只需要將[]切換成(),即:

(expression for item in iterable if condition)

對比列表生成式返回的是一個列表,生成器表示式返回的是一個生成器物件

>>> [x*x for x in range(3)]
[0, 1, 4]
>>> g = (x*x for x in range(3))
>>> g
<generator object <genexpr> at 0x00000206CD15AB30>

對比列表生成式,生成器表示式的優點自然是節省記憶體(一次只產生一個值在記憶體中)

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g) # 丟擲異常StopIteration
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

如果我們要讀取一個大檔案的位元組數,應該基於生成器表示式的方式完成

with open(r'1.txt', 'r', encoding='utf8') as f:
num = (len(line) for line in f) # for迴圈預設每次讀取一行
totle = sum(num) # 依次執行next(num),然後累加到一起得到結果
print(totle)