python之關鍵字return和yield的區別以及詳細介紹 || python之生成器及其優點


前言

 return :在程序函數中返回某個值,返回之后函數不在繼續執行,徹底結束。

 yield : 帶有 yield 的函數是一個生成器,函數返回某個值時,會停留在某個位置,返回函數值后,會在前面停留的位置繼續執行,直到程序結束。

簡單理解: yield 就是 return 返回一個值,並且記住這個返回的位置,下次迭代就從這個位置后開始。

普通函數和生成器函數

假如要創建一個返回奇數數列的函數,普通函數的做法如下:

def odd_numbers(n):
    odd_num_list = []
    for i in range(n):
        if (i % 2) == 1:
            odd_num_list.append(i)

    return odd_num_list


for j in odd_numbers(10):
    print(j)

運行結果:

1
3
5
7
9

當將上述函數改成如下形式時,普通函數便變成了一個生成器函數。這時候調用 odd_numbers(10) 不會執行 odd_numbers 函數,而是返回一個生成器對象。

def odd_numbers(n):
    for x in range(n):
        if (x % 2) == 0:
            yield x


num = odd_numbers(10)
print(num)

運行結果:

<generator object odd_numbers at 0x7f9d77a095f0>

從生成器對象獲取值

為了從生成器對象獲取值,我們可以使用 for loop 、 next() 或者 list() 方法。

1、 for loop 方法:

def odd_numbers(n):
    for x in range(n):
        if (x % 2) == 1:
            yield x


num = odd_numbers(10)
for i in num:
    print(i)

2、next() 方法:

def odd_numbers(n):
    for x in range(n):
        if (x % 2) == 1:
            yield x


num = odd_numbers(10)
print(next(num))
print(next(num))
print(next(num))
print(next(num))
print(next(num))

3、 list() 方法

def odd_numbers(n):
    for x in range(n):
        if (x % 2) == 1:
            yield x


num = odd_numbers(10)
for i in list(num):
    print(i)

生成器對象只能被遍歷一次

生成器對象只能被遍歷一次,當我們再次使用時,生成器對象為空。

def odd_numbers(n):
    for x in range(n):
        if (x % 2) == 1:
            yield x


num = odd_numbers(10)
for i in num:
    print(i)

# 再次使用 num
print(list(num))

運行結果:

1
3
5
7
9 
[]  # 從輸出中可以看出,當再次使用 num 時,num 變成了空。

yield 對比 return

 yield :

包含 yield 的函數在被調用時,返回一個生成器對象給調用者,只有在遍歷對象時,函數的代碼才會被執行。

生成器對象不占用內存。

適合處理數據量比較大的情況。

當數據量較大時,性能較好。

 return :

return 返回一個值給調用者。

需要給返回的值分配內存。

當處理的數據量較小時,比較方便。

當數據量較大時,由於需要使用大量內存,會拖慢性能。

總結

  • 返回的生成器對象在使用過一次后便不再可以使用,如果要獲取值,需要遍歷生成器對象。
  • 可以使用for loopnext()或者list()方法從 生成器對象獲取值。
  • yieldreturn的最大區別是,yield 返回一個生成器對象給調用者,而return返回一個值給調用者。
  • 使用yield時,不會將值存儲在內存中,這在處理的數據量很大時,比較有優勢。

舉例

def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))

運行結果:

starting...
4
********************
res: None
4

詳細解釋:

1、程序開始執行以后,因為foo函數中有yield關鍵字,所以foo函數並不會真的執行,而是先得到一個生成器g(相當於一個對象)。

2、直到調用next方法,foo函數正式開始執行,先執行foo函數中的print方法,然后進入while循環。

3、程序遇到yield關鍵字,然后把yield想想成return,return了一個4之后,程序停止,並沒有執行賦值給res操作,此時next(g)語句執行完成,所以輸出的前兩行(第一個是while上面的print的結果,第二個是return出的結果)是執行print(next(g))的結果。

4、程序執行 print("*"*20) ,輸出20個*

5、又開始執行下面的 print(next(g)) ,這個時候和上面那個差不多,不過不同的是,這個時候是從剛才那個next程序停止的地方開始執行的,也就是要執行res的賦值操作;這時候要注意,這個時候賦值操作的右邊是沒有值的(因為剛才那個是return出去了,並沒有給賦值操作的左邊傳參數),所以這個時候res賦值是None,所以接着下面的輸出就是res:None。

6、程序會繼續在while里執行,又一次碰到yield,這個時候同樣return 出4,然后程序停止,print函數輸出的4就是這次return出的4。

python之生成器及其優點

迭代器協議

生成器自動實現了迭代器協議。

1、迭代器協議是指:對象需要提供next方法,它要么返回迭代中的下一項,要么就引起一個StopIteration異常,以終止迭代。
2、可迭代對象就是:實現了迭代器協議的對象。
3、協議是一種約定,可迭代對象實現迭代器協議,Python的內置工具(如for循環,sum,min,max函數等)使用迭代器協議訪問對象。

舉個例子:在所有語言中,我們都可以使用for循環來遍歷數組,Python的list底層實現是一個數組,所以,我們可以使用for循環來遍歷list。如下所示:

>>> for n in [1, 2, 3, 4]:
...     print n

但是,其實Python的for循環不但可以用來遍歷list,還可以用來遍歷文件對象,如下所示:

with open(‘/etc/passwd’) as f: # 文件對象提供迭代器協議
        for line in f: # for循環使用迭代器協議訪問文件
            print line

為什么在Python中,文件還可以使用for循環進行遍歷呢?

這是因為,在Python中,文件對象實現了迭代器協議,for循環並不知道它遍歷的是一個文件對象,它只管使用迭代器協議訪問對象即可。正是由於Python的文件對象實現了迭代器協議,才得以使用如此方便的方式訪問文件,如下所示:

f = open('/etc/passwd')
dir(f)
# ['__class__', '__enter__', '__exit__', '__iter__', '__new__', 'writelines', '...'

生成器

Python使用生成器對延遲操作提供了支持。所謂延遲操作,是指在需要的時候才產生結果,而不是立即產生結果。這也是生成器的主要好處。

Python有兩種不同的方式提供生成器:

1、生成器函數:常規函數定義,但是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每個結果中間,掛起函數的狀態,以便下次重它離開的地方繼續執行。
2、生成器表達式:類似於列表推導,但是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表。

生成器函數

舉例:使用生成器返回自然數的平方(注意返回的是多個值):

def gensquares(N):
    for i in range(N):
        yield i ** 2

for item in gensquares(5):
    print item

使用普通函數:

def gensquares(N):
    res = []
    for i in range(N):
        res.append(i*i)
    return res

for item in gensquares(5):
    print item

生成器表達式

使用列表推導,將會一次產生所有結果:【列表生成式】

squares = [x**2 for x in range(5)]
print(squares)  # [0, 1, 4, 9, 16]

將列表推導的中括號,替換成圓括號,就是一個生成器表達式:

squares = (x**2 for x in range(5))
print(squares) # <generator object at 0x00B2EC88>
print(next(squares))  # 0
print(next(squares))  # 1
print(next(squares))  # 4
print(list(squares))  # [9, 16]

Python不但使用迭代器協議,讓for循環變得更加通用。

大部分內置函數,也是使用迭代器協議訪問對象的。例如, sum函數是Python的內置函數,該函數使用迭代器協議訪問對象,而生成器實現了迭代器協議,所以,我們可以直接這樣計算一系列值的和:

sum(x ** 2 for x in xrange(4)) 

再看生成器

前面已經對生成器有了感性的認識,我們以生成器函數為例,再來深入探討一下Python的生成器:

1、語法上和函數類似:生成器函數和常規函數幾乎是一樣的。它們都是使用def語句進行定義,差別在於,生成器使用yield語句返回一個值,而常規函數使用return語句返回一個值。
2、自動實現迭代器協議:對於生成器,Python會自動實現迭代器協議,以便應用到迭代背景中(如for循環,sum函數)。由於生成器自動實現了迭代器協議,所以,我們可以調用它的next方法,並且,在沒有值可以返回的時候,生成器自動產生StopIteration異常。
3、狀態掛起:生成器使用yield語句返回一個值。yield語句掛起該生成器函數的狀態,保留足夠的信息,以便之后從它離開的地方繼續執行

舉例:

首先,生成器的好處是延遲計算,一次返回一個結果。也就是說,它不會一次生成所有的結果,這對於大數據量處理,將會非常有用。

大家可以在自己電腦上試試下面兩個表達式,並且觀察內存占用情況。對於前一個表達式,我在自己的電腦上進行測試,還沒有看到最終結果電腦就已經卡死,對於后一個表達式,幾乎沒有什么內存占用。

sum([i for i in xrange(10000000000)])
sum(i for i in xrange(10000000000))

除了延遲計算,生成器還能有效提高代碼可讀性

例如,現在有一個需求,求一段文字中,每個單詞出現的位置。

不使用生成器的情況:

def index_words(text):
    result = []
    if text:
        result.append(0)
      for index, letter in enumerate(text, 1):
        if letter == ' ':
            result.append(index)
    return result

使用生成器的情況:

def index_words(text):
    if text:
        yield 0
    for index, letter in enumerate(text, 1):
         if letter == ' ':
            yield index

這里,至少有兩個充分的理由說明 ,使用生成器比不使用生成器代碼更加清晰:

1、使用生成器以后,代碼行數更少。大家要記住,如果想把代碼寫的Pythonic,在保證代碼可讀性的前提下,代碼行數越少越好。
2、不使用生成器的時候,對於每次結果,我們首先看到的是result.append(index),其次,才是index。也就是說,我們每次看到的是一個列表的append操作,只是append的是我們想要的結果。使用生成器的時候,直接yield index,少了列表append操作的干擾,我們一眼就能夠看出,代碼是要返回index。

這個例子充分說明了,合理使用生成器,能夠有效提高代碼可讀性。只要大家完全接受了生成器的概念,理解了yield語句和return語句一樣,也是返回一個值。那么,就能夠理解為什么使用生成器比不使用生成器要好,能夠理解使用生成器真的可以讓代碼變得清晰易懂。

使用生成器的注意事項

直接舉例:

我們直接來看例子,假設文件中保存了每個省份的人口總數,現在,需要求每個省份的人口占全國總人口的比例。顯然,我們需要先求出全國的總人口,然后在遍歷每個省份的人口,用每個省的人口數除以總人口數,就得到了每個省份的人口占全國人口的比例。

def get_province_population(filename):
     with open(filename) as f:
        for line in f:
            yield int(line)
gen = get_province_population('data.txt')
all_population = sum(gen)
#print all_population
for population in gen:
     print population / all_population

執行上面這段代碼,將不會有任何輸出,這是因為,生成器只能遍歷一次。在我們執行sum語句的時候,就遍歷了我們的生成器,當我們再次遍歷我們的生成器的時候,將不會有任何記錄。所以,上面的代碼不會有任何輸出。

因此,生成器的唯一注意事項就是:生成器只能遍歷一次。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

前言

生成器是一個簡單的方式來完成迭代。簡單來說,Python的生成器是一個返回可以迭代對象的函數。

怎樣創建生成器

在一個一般函數中使用 yield 關鍵字,可以實現一個最簡單的生成器,此時這個函數變成一個生成器函數。
yield 與 return 返回相同的值,區別在於return返回后,函數狀態終止,而yield會保存當前函數的執行狀態,在返回后,函數又回到之前保存的狀態繼續執行。

生成器函數與一般函數的不同

1、生成器函數包含一個或者多個 yield 。

2、當調用生成器函數時,函數將返回一個對象,但是不會立刻向下執行。

3、像 __iter__() 和 __next__() 方法等是自動實現的,所以我們可以通過 next() 方法對對象進行迭代。

4、一旦函數被 yield ,函數會暫停,控制權返回調用者。

5、局部變量和它們的狀態會被保存,直到下一次調用。

6、函數終止的時候,StopIteraion會被自動拋出。

舉例

# 簡單的生成器函數
def my_gen():
     n=1
     print("first")
     # yield區域
     yield n

     n+=1
     print("second")
     yield n

     n+=1
     print("third")
     yield n

 a=my_gen()
 print("next method:")
 # 每次調用a的時候,函數都從之前保存的狀態執行
 print(next(a))
 print(next(a))
 print(next(a))

 print("for loop:")
 # 與調用next等價的
 b=my_gen()
 for elem in my_gen():
     print(elem)

運行結果:

next method:
first
1
second
2
third
3
for loop:
first
1
second
2
third
3

使用循環的生成器

# 逆序yield出對象的元素
def rev_str(my_str):
    length=len(my_str)
    for i in range(length-1,-1,-1):
        yield my_str[i]

for char in rev_str("hello"):
    print(char)

運行結果:

o
l
l
e
h

生成器的表達式

Python中,有一個列表生成方法,比如:

# 產生1,2,3,4,5的一個列表
[x for x in range(5)]

如果把[]換成(),那么會成為生成器的表達式。

print((x for x in range(5)))  # <generator object <genexpr> at 0x0000017BD3427CF0> 

舉例:

a = (x for x in range(10))
b = [x for x in range(10)]

# 這是錯誤的,因為生成器不能直接給出長度
# print("length a:",len(a))

# 輸出列表的長度
print("length b:", len(b))

b = iter(b)
# 二者輸出等價,不過b是在運行時開辟內存,而a是直接開辟內存
print(next(a))
print(next(b))

為什么使用生成器

1、更容易使用,代碼量較小。

2、內存使用更加高效。比如列表是在建立的時候就分配所有的內存空間,而生成器僅僅是需要的時候才使用。

3、代表了一個無限的流。如果我們要讀取並使用的內容遠遠超過內存,但是需要對所有的流中的內容進行處理,那么生成器是一個很好的選擇,比如可以讓生成器返回當前的處理狀態,由於它可以保存狀態,那么下一次直接處理即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM