生成器和迭代器

1.列表生成式

現在有個需求,看列表[0, 1, 2, 3, 4, 5],要求你把列表裏的每個值加1,你怎麼實現?

可以使用for循環,while循環
map方式
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a = map(lambda x:x+1, a)
>>> a
<map object at 0x101d2c630>
>>> for i in a:print(i)
... 
3
5
7
9
11

高級方式:
列表生成式方式
>>> a = [i+1 for i in range(10)]
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a = [i for i in range(10)]
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>

2.生成器

2.1生成器的創建與調用

通過列表生成式,我們可以直接創建一個列表。但是,收到內存限制,列表容量是有限的。而且,創建一個包含100萬個元素的列表,不僅佔用很大的存儲空間,如果我們僅僅使用其中的幾個元素,那後面的元素所佔用的空間就浪費掉了。
所以,如果列表中的元素可以按照某種算法推算出來,那是否可以在循環的過程中推算出後續的元素呢?就不需要創建完整的list,從而節省大量的空間。在Python中,一邊循環一邊計算的機制,稱爲生成器:generator.
要創建一個generator,有很多種方法。第一種方法很簡單,只要把一個列表生成式的[]改成(),就創建了一個generator
生成器的特性:
1.預先定義一個生產輸的範圍,使用一個生產一個,不佔用內存空間
2.只能往下不斷取數,不能回退
3.走到最後,報stopIteration錯誤

##這裏生成大量的數據,需要一段時間
>>> a = [i for i in range(100000000000000)]
##列表生成式,立刻生成,因爲是調用net()方法時才產生數據,調用一次產生一次
>>> a = (i for i in range(100000000000000))
>>> 
>>>
>>> a = [i for i in range(10)]
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> g = (i*2 for i in range(10))
>>> g
<generator object <genexpr> at 0x00000237FF9BF2B0>
通過next()函數獲得generator的下一個返回值
>>> next(g)
0
>>> next(g)
2
>>> next(g)
4
>>>
>>> next(g)
16
>>> next(g)
18
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

#可使用for循環調用
#創建了一個generator後,基本上永遠不會調用next(),而是通過for循環來迭代它,並且不需要關心StopIteration的錯誤
>>> g = (i*2 for i in range(10))
>>> for n in g:
...     print(n)
...
0
2
4
6
8
10
12
14
16
18
>>>

2.2通過函數方式創建生成器

generator非常強大。如果推算的算法比較複雜,用類似列表生成式的for循環無法實現的時候,可以使用函數來實現

2.2.1斐波那契數列-普通函數方式

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: vita

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
                # 相當於t = a+b, a = b,b = t,但不需要顯式寫出臨時變量t就可以賦值
        a, b = b, a + b
        n = n + 1
    return 'done'
fib(10)

E:\PythonProject\python-test\venvP3\Scripts\python.exe E:/PythonProject/python-test/BasicGrammer/test.py
1
1
2
3
5
8
13
21
34
55

Process finished with exit code 0

仔細觀察。fib函數實際上已經定義了斐波那契數列的推算規則,可以從第一個元素開始推算出後續的任意元素,這種邏輯其實已經很類似generator,實際只需要把print(b)改爲yield b就變成了生成器。

2.2.2斐波那契數列-生成器方式

如果一個函數定義中包含yield關鍵字,那麼這個函數就不再是一個普通函數,而是一個generator.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: vita

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
# 用for循環輸出內容
for i in fib(10):
    print(i)

E:\PythonProject\python-test\venvP3\Scripts\python.exe E:/PythonProject/python-test/BasicGrammer/test.py
1
1
2
3
5
8
13
21
34
55

Process finished with exit code 0

print(fib(10))
<generator object fib at 0x000002BF85F8B888>

生成器和迭代器

普通函數是順序執行的,遇到return語句或最後一行函數語句就返回數據,並凍結當前的執行過程
generator的執行流程是每次調用next()的時候執行,遇到yeild語句返回,再次調用next()時從上次yeild語句處繼續執行。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: vita

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
data = fib(10)
print(data.__next__())
print(data.__next__())
print("做別的事情")
print(data.__next__())
print(data.__next__())
print(data.__next__())

E:\PythonProject\python-test\venvP3\Scripts\python.exe E:/PythonProject/python-test/BasicGrammer/test.py
1
1
做別的事情
2
3
5

Process finished with exit code 0

用for循環調用generator時,發現拿不到generator的return語句的返回值。如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: vita

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
g = fib(10)
while True:
    try:
        x = next(g)
        print('g:', x)
    except StopIteration as e:
        print('Generator return value:', e.value)
        break

E:\PythonProject\python-test\venvP3\Scripts\python.exe E:/PythonProject/python-test/BasicGrammer/test.py
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
g: 13
g: 21
g: 34
g: 55
Generator return value: done

Process finished with exit code 0

2.3send

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

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: vita

def fib(max):
    n,a,b = 0,0,1

    while n < max:
        #print(b)
        sign=yield  b
        a,b = b,a+b
        if sign=="stop":
            print(sign)

        n += 1

    return 'done'

f = fib(6)
print(next(f))
print(next(f))
print(next(f))
f.send("stop")
print(next(f))

E:\PythonProject\python-test\venvP3\Scripts\python.exe E:/PythonProject/python-test/BasicGrammer/test.py
1
1
2
stop
5

2.4range和xrange

python3中
range()就是創建了一個生成器,用到的時候纔會生成數據
Python2中
range()是創建一個定義大小的列表,
xrange()是創建了一個一個生成器,和Python3中的range()相同

>python3
Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:57:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> range(100000000000000000000)
range(0, 100000000000000000000)
>>> type(range(10))
<class 'range'>

>>> quit()

>python2
Python 2.7.16 (v2.7.16:413a49145e, Mar  4 2019, 01:37:19) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 
>>> xrange(100000000)
xrange(100000000)
>>> range(10000000)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37...]
>>> type(range(10))
<type 'list'>
>>> type(xrange(10))
<type 'xrange'>

3.迭代器

3.1可迭代對象

我們已經知道,可以用於for循環的數據類型有以下幾種:
一類是集合數據類型,如list,tuple,dict,set,str等;
一類是generator,包括生成器和帶yeild的generator function。
這些可以直接作用於for循環的對象稱爲可迭代對象(Iterable)
可以使用isinstance()判斷一個對象是否是Iterable對象

>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

3.2迭代器

生成器不但可以用for循環,還可以被next()函數不斷調用並返回下一個值,直到最後拋出StopIteration錯誤表示無法繼續返回下一個值。
可以被next()函數調用並不斷返回下一個值的對象稱爲迭代器(Iterator)
可以使用isinstance()判斷一個對象是否是Iterator對象

python3
>>> from collections import Iterator
 >>> isinstance(range(10),Iterator)
False

>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

生成器都是迭代器,但list,tuple,set,dict,str雖然是可迭代對象,但不是迭代器。
可以使用iter()函數把list,str,dict,set變爲迭代器

>>> isinstance(iter([]),Iterator)
True
>>> isinstance(iter(""),Iterator)
True
>>> isinstance(iter({}),Iterator)
True
>>>

爲什麼list,set,dict,str不是迭代器?
因爲Python的迭代器對象表示的是一個數據流,Iterator對象可以被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以Iterator的計算是惰性的,只有在需要返回下一個數據時纔會計算。
Iterator甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。

4.小節

1.凡是可作用於for循環的對象都是Iterable類型;
2.凡是可作用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;
3.集合數據類型如list、dict、str等是Iterable但不是Iterator,不過可以通過iter()函數獲得一個Iterator對象。
4.Python3的for循環本質上就是通過不斷調用next()函數實現的,例如:

for x in [1, 2, 3, 4, 5]:
    pass
實際上完全等價於:

# 首先獲得Iterator對象:
it = iter([1, 2, 3, 4, 5])
# 循環:
while True:
    try:
        # 獲得下一個值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循環
        break
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章