函數進階之生成器和迭代器

前提:python

列表生成式算法

給列表a裏的大於5的每個值加10函數

a = [1, 2, 5, 6, 7, 8]
a = [i + 10 if i > 5 else i for i in a]  # 能夠循環任何可循環的東西,不過只能寫到列表或元組裏。
print(a)  # [1, 2, 5, 16, 17, 18]

複雜東西列表生成式寫不出來,最多到三元運算了。性能

 

正文:大數據

生成器是爲了省內存,不一次釋放,須要一個取一個。spa

g = (i for i in range(5))
print(g)  # <generator object <genexpr> at 0x101fba048>
print(next(g))  # 0
print(next(g))  # 1
print(next(g))  # 2
print(next(g))  # 3
print(next(g))  # 4
print(next(g))  # 到最後沒有了,會報錯

生成器只能往前走,不能後退。code

咱們建立了一個generator後,基本上永遠不會調用next(),而是經過for循環來迭代它,而且不須要關心StopIteration的錯誤,for循環會把它自動消化。對象

g2 = (i for i in range(5))

for i in g2:
    print(i)

generator很是強大。若是推算的算法比較複雜,用相似列表生成式的for循環沒法實現的時候,還能夠用函數來實現。blog

 

生成器和range內存

python2裏range是直接建立列表。
python3裏的range是用生成器作的,是循環到了才建立。
python2裏有個xrange,其實就是等於python3裏的range。python3只有range,等因而把python2裏的那個range去掉了。

 

用生成器實現斐波那契

def fib(max):
    n, a, b = 10, 0, 1
    while n < max:
        print('before yield')yield n  # 把函數的執行過程凍結在這一步,而且把b的值,返回給外面的next()
        print(b)
        a, b = b, a + b
        n += 1
    return 'done'


print(fib(15))  # <generator object fib at 0x101fba048> 裏面有yield,函數名加括號,內部代碼根本不執行,只是生成一個生成器對象。

f = fib(15)  # return function into a generator

next(f)  # before yield  # first time call next()
n = next(f)  # 1 # before yield
next(f)  # 1 # before yield
next(f)  # 2 # before yield

print(n)  # 11

yield的做用是把函數裏面的值返回出來到外部

解析:第一次print出 before yield以後,遇到yield,程序終止,再次執行next(f),程序繼續運行print出b的值,而後直到再次走到print('before yield')後,
程序遇到yield n,程序終止,而後再次執行next(f),以此循環......

 

生成器的建立方式

1. "列表"生成式(),最多支持三元表達式。 例:g2 = (i for i in range(5))
2. 函數

 

用生成器實現range方法

def range2(n):
    count = 0
    while count < n:
        print('count', count)
        yield count
        count += 1


new_range = range2(10)
r1 = next(new_range)
print(r1)  # 0
r2 = new_range.__next__()  # 和 next(new_range)是同樣的
print(r2)  # 1

 

yield vs return

return 返回並終止函數
yield 返回數據,並凍結當前的執行過程

next 喚醒凍結的函數執行過程,繼續執行,直到遇到下一個yield

 

函數有了yield以後
1.函數名加()就變成了一個生成器
2.return在生成器裏,表明生成器的終止,直接報錯

 

生成器send方法

def range2(n):
    count = 0
    while count < n:
        print('count', count)
        sign = yield count
        count += 1
        if sign == 'stop':
            print('----sign ', sign)
            break


new_range = range2(10)
n1 = next(new_range)  # count 0

n2 = new_range.__next__()  # count 1

n3 = new_range.send(None)  # count 2 這條語句等於next,由於next(或__next__)方法就是默認往生成器裏面發送了一個None

n4 = new_range.send('stop')  # 終止,程序報錯,打印----sign  stop

 

send的做用:
1.喚醒並繼續執行
2. 發送一個信息到生成器的內部

next()默認往生成器裏發送一個None

 

迭代器

能夠被next()函數調用並不斷返回下一個值的對象稱爲迭代器:Iterator。

可使用isinstance()判斷一個對象是不是Iterable對象

from collections import Iterable

print(isinstance(123, Iterable))  # False
print(isinstance('abc', Iterable))  # True

只有生成器是迭代器。

from collections import Iterator

li = [i for i in range(10)]
print(isinstance(li, Iterator))  # False

可是能夠把列表、字符串等變成迭代器

li = iter(li)

print(li.__next__())  # 0
print(li.__next__())  # 1

Python的Iterator對象表示的是一個數據流,Iterator對象能夠被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。
能夠把這個數據流看作是一個有序序列,但咱們卻不能提早知道序列的長度,只能不斷經過next()函數實現按需計算下一個數據,因此Iterator的計算是惰性的,
只有在須要返回下一個數據時它纔會計算。Iterator甚至能夠表示一個無限大的數據流,例如全體天然數。而使用list是永遠不可能存儲全體天然數的。

 

須要注意的是:

  1.列表用len能知道長度,可是迭代器不能知道
  2.迭代器比生成器的範圍要大一些,學了面向對象後不用生成器也能next。

 

凡是可做用於for循環的對象都是Iterable類型;
凡是可做用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;
集合數據類型如list、dict、str等是Iterable但不是Iterator,不過能夠經過iter()函數得到一個Iterator對象。
Python3的for循環本質上就是經過不斷調用next()函數實現的

 

生成器算得上是Python語言中最吸引人的特性之一,生成器實際上是一種特殊的迭代器,不過這種迭代器更加優雅。
它不須要再像上面的類同樣寫__iter__()和__next__()方法了,只須要一個yiled關鍵字。
生成器必定是迭代器(反之不成立),所以任何生成器也是以一種懶加載的模式生成值。

 

生成器 VS 迭代器 (補充)

 

1. 迭代器(Iterator)

這裏的迭代能夠指for循環,在Python中,對於像list,dict和文件等而言,均可以使用for循環,可是它們並非迭代器,它們屬於可迭代對象。
1.1 什麼可迭代對象
最簡單的解釋:可使用for...in...語句進行循環的對象,就是可迭代對象(Iterable),可使用isinstance()方法進行判斷。

from collections import Iterable 
type = isinstance('python', Iterable)
print type

1.2 什麼是迭代器
迭代器指的是可使用next()方法來回調的對象,能夠對可迭代對象使用iter()方法,將其轉換爲迭代器。

temp = iter([1, 2, 3])
print type(temp)
print next(temp)

此時temp就是一個迭代器。因此說,迭代器基於兩個方法:

  • next:返回下一個項目
  • _iter_ 返回迭代器自己
    可理解爲可被next()函數調用並不斷返回下一個值的對象就是迭代器,在定義一個裝飾器時將須要同時定義這兩個方法。
    迭代器的優點
    在構建迭代器時,不是將全部的元素一次性的加載,而是等調用next方法時返回元素,因此不須要考慮內存的問題。
    迭代器應用場景
    那麼,具體在什麼場景下可使用迭代器呢?
  • 數列的數據規模巨大
  • 數列有規律,可是不能使用列表推導式描述。

1.3 文件對象

f = open('myfile.txt')
print(next(f))
print(next(f))
print(next(f))
print(next(f))

hello text file
goodbyt text file
hahahahah
Traceback (most recent call last):
 File "E:/12homework/12homework.py", line 5, in <module>
print(next(f))
StopIteration

 文件對象的迭代器就是他本身。即文件對象既是迭代器,又是可迭代對象.

2. 生成器

生成器是一種高級迭代器,使得須要返回一系列元素的函數所需的代碼更加的簡單和高效(不像建立迭代器代碼那般冗長)。
2.1 生成器函數
生成器函數和常規函數都採用def語句進行定義,可是基於yield指令返回一個值,能夠暫停一個函數並返回中間結果。當須要一個將返回一個序列或在循環中執行的函數時,就可使用生成器,由於當這些元素被傳遞到另外一個函數中進行後續處理時,一次返回一個元素能夠有效的提高總體性能。
常見的應用場景是使用生成器的流數據緩衝區。

2.2 生成器表達式
生成式表達式是一種實現生成器的便捷方式,將列表推導式的中括號替換爲圓括號。
和列表推導式的區別:列表生成式能夠直接建立一個表,可是生成器表達式是一種邊循環邊計算,使得列表的元素能夠在循環過程當中一個個的推算出來,不須要建立完整的列表,從而節省了大量的空間。


g = (x * x for x in range(10))

總結:生成器是一種高級迭代器。生成器的優勢是延遲計算,一次返回一個結果,這樣很是適用於大數據量的計算。可是,使用生成器必需要注意的一點是:生成器只能遍歷一次。

相關文章
相關標籤/搜索