迭代器、生成器

随机昵称yi發表於2024-05-21

迭代器

【一】迭代器介紹

  • 迭代器就是用來迭代取值的工具,是重複反饋過程的程式
  • 目的是為了更加逼近我們想要的目標和結果
  • 每一次迭代得到的返回結果就是下一次迭代開始的初始值
num_list = [1,2,3,4,5,6]
count = 0
while count <len(num_list):
    # 每一次使用的索引位置就是上一次+1後的索引位置
    print(num_list[count])
    count += 1
    
1
2
3
4
5
6

【二】可迭代物件

【1】什麼是可迭代物件

  • 可以透過列表的索引進行取值,但是如果一個資料沒有索引的話
  • 必須找到一種可以用索引也可以不用索引的方法來取值

【2】八大基本資料型別

補充

生成可迭代物件的兩種方式

iter()

相當於

__iter__(資料)

迭代器物件迭代的方法的兩種方式

迭代器物件.__next__()

next(迭代器物件)

數字型別

整數
age = 18
print(iter(age))
print(age.__iter__)
報錯 	'int' object is not iterable

浮點數
age = 18.5
print(iter(age))
print(age.__iter__)
報錯	'float' object is not iterable

字串型別

qwer = 'qewr'
print(iter(qwer))
print(qwer.__iter__)

輸出
<str_iterator object at 0x0000021D016BA740>
<method-wrapper '__iter__' of str object at 0x0000021D0165D3B0>

列表型別

list = [1,2,3,4,5,6]
print(iter(list))
print(list.__iter__)

輸出
<list_iterator object at 0x000001FA4D7097B0>
<method-wrapper '__iter__' of list object at 0x000001FA4D6B4E40>

字典型別

dict = {'q':'1','w':2}

print(iter(dict))
print(dict.__iter__)

輸出
<dict_keyiterator object at 0x000001E457AED670>
<method-wrapper '__init__' of dict object at 0x000001E457AA1F40>

布林型別

bool = True
print(iter(bool))
print(bool.__iter__)

報錯 	'bool' object is not iterable

元組型別

tuple = (1,2,3,4,5,6)

print(iter(tuple))
print(tuple.__iter__)

輸出
<tuple_iterator object at 0x000001AE3656A740>
<method-wrapper '__init__' of tuple object at 0x000001AE36578940>

集合型別

set = {1,2,3,4,5,6}
print(iter(set))
print(set.__iter__)

輸出
<set_iterator object at 0x000001E74BFF2080>
<method-wrapper '__init__' of set object at 0x000001E74C026DC0>

【3】總結

能.__iter__不報錯的就是可迭代型別 ————>可迭代物件

可迭代型別

字串、列表、元組、集合、字典

不可迭代型別

整數、浮點數、布林

【三】迭代器物件

【1】什麼是迭代器物件

# 如果呼叫obj.__iter__()返回的是一個可迭代物件

# 有的可迭代物件還有__next__()方法
# 迭代器物件實在可迭代物件(具有.__iter__()方法)的基礎上具有.__next__()方法的物件

【2】八大基本資料型別(除了不可迭代)

(1)字串

name = "qwer"
name_iter = name.__iter__()
print(name_iter)
print(name_iter.__next__())
print(name_iter.__next__())
print(name_iter.__next__())
print(name_iter.__next__())
print(name_iter.__next__()) # 會報錯 most recent call last

<str_iterator object at 0x00000202DE74A740>
q
w
e
r
第五行報錯

(2)列表

name = [1,2,3,4,5,6]
name_iter = name.__iter__()
print(name_iter)
print(name_iter.__next__())
print(name_iter.__next__())
print(name_iter.__next__())
print(name_iter.__next__())
print(name_iter.__next__())


<list_iterator object at 0x0000026B66C3A740>
1
2
3
4
5

(3)字典

得到的是鍵

name = {'q':1,'w':'2','e':'3','r':'4'}
name_iter = name.__iter__()
print(name_iter)
print(name_iter.__next__())
print(name_iter.__next__())
print(name_iter.__next__())
print(name_iter.__next__())


<dict_keyiterator object at 0x000001C4B92DD670>
q
w
e
r

(4)元組

name = (1,2,3)
name_iter = name.__iter__()
print(name_iter)
print(name_iter.__next__())
print(name_iter.__next__())
print(name_iter.__next__())


<tuple_iterator object at 0x000001D9C593A740>
1
2
3

(5)集合

name = {1,2,3,4,5,6}
name_iter = name.__iter__()
print(name_iter)
print(name_iter.__next__())
print(name_iter.__next__())
print(name_iter.__next__())
print(name_iter.__next__())

<set_iterator object at 0x00000234968D2000>
1
2
3
4

【3】總結

具有__iter__() 和 __next__()方法的物件是可迭代物件
  • 在八大基本資料型別中,除了數字和布林型別,其他都是迭代器物件

  • 迭代器物件一定是可迭代物件,可迭代物件不一定是迭代器物件

  • 迭代器物件是同時具有上述兩個方法的物件

  • 可迭代物件只需有第一個方法既是可迭代物件

【四】迭代器的優缺點

  1. 優點
    • 不使用索引取值(例如字典不支援索引取值)
    • 取到值後會儲存當前狀態,下次從當前位置開始繼續取值
  2. 缺點
    • 除非取值取到盡頭,否則永遠不知道終止索引在哪裡
    • 只能取一次記一次位置,下一次的其實位置是上一次的終止位置,想回到開頭 回不去
    • 呼叫 next 取值的前提是 已經生成並且得到了一個迭代器物件 iter_obj
    • 迭代同一個物件只能重新建立
name = 'dr'
name_iter = name.__iter__()
print(next(name_iter))
print(next(name_iter))
name_iter = name.__iter__()	# 要重複取值必須重新建立迭代器物件
print(next(name_iter))

d
r
d 

生成器

【一】什麼是生成器

  • 生成器是一種特殊的迭代器

  • 在需要的時候給你資料,不需要就不會給資料

  • for迴圈 range(10) 可以把生成的數字取出來

  • 資料量過大的話 讀取資料記憶體滿,電腦卡死

  • 生成器 ---> 一次取...行 --->處理完這些資料後--->再取...行

例子 for i in fp:

【二】生成器的建立方式

【1】列表推導式

print([i for i in range(10)])
# 如果將列表生成式外面的列表換成元組,則會生成一個生成器物件
print((i for i in range(10)))

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
<generator object <genexpr> at 0x0000020E358F8430>

【2】yiled關鍵字

def add(i):
    yield i

# add(2)進入到add函式,將原本的函式物件轉換為生成器物件
res = add(2)
print(res)
# 想取值只能使用next來迭代取值
print(res.__next__())

<generator object add at 0x00000155DB1E8430>
2
def add(i):
    # i = 1
    while True:
        yield i	 # 第一次next


res = add(1)
print(res)
print(res.__next__())
print(res.__next__())
print(res.__next__())
print(res.__next__())
res = add(3)
print(res.__next__())


<generator object add at 0x000001EFDF0CEAB0>
1
1
1
1
3

補充

在第一次呼叫生成器時,`yield`語句會在賦值運算子`=`的右邊停下。

考慮下面的程式碼:

```python
def my_generator():
    x = yield
    print("Received:", x)

gen = my_generator()
```

當你執行`gen = my_generator()`時,生成器`my_generator`並沒有開始執行。相反,它返回了一個生成器物件`gen`。此時,`my_generator`函式中的程式碼並沒有執行。

然後,當你呼叫`gen.__next__()`或`next(gen)`時,生成器才會開始執行。它會執行到`x = yield`這一行,但暫停在`yield`右邊,等待接收一個值。這個值會被賦給`x`。因此,第一次呼叫`yield`語句時,生成器會停在賦值運算子`=`的右邊。

【三】生成器案例

def outer():
    print("開始吃飯")
    while True:
        food = yield  # 等同於yield(food)
        print(f"做了{food},吃{food}")

res = outer()
print(res)
print(res.__next__())
print(res.__next__())


<generator object outer at 0x00000183F2538430>
開始吃飯
None	
做了None,吃None
None	# 原因是在生成器內部走完了上面程式碼,又回到了yield 沒有返回值

yield語句在生成器函式中充當了暫停和恢復執行的標記,同時也充當了返回值的功能。當生成器函式執行到yield語句時,它會暫停執行並將yield後面的表示式作為返回值返回給呼叫方。在上述程式碼中food為yield的後面的表示式

向生成器中傳值

send() 傳值後會再往下走一步

res = outer()
print(res)
print(res.__next__())
res.send('炒餅') # 向 yield 傳值並且讓生成器向下走一下
print(res.__next__())
res.send('炒飯')


<generator object outer at 0x000002A8C99E8430>
開始吃飯
None
做了炒餅,吃炒餅
做了None,吃None
None
做了炒飯,吃炒飯

【四】裝飾器 + 生成器

def init_iter(func):
    def inner(*args,**kwargs):
        # g 是得到的生成器物件
        g = func(*args,**kwargs)
        # 呼叫自己生成器往下走
        next(g)
        # 走回來的返回值返回出去
        return g
    return inner

@init_iter
def outer():
    print("開始吃飯")
    while True:
        # yield food
        food = yield
        print(f"做了{food},吃{food}")

res = outer()
res.send('炒米粉')
res.send('炒fan')


開始吃飯
做了炒米粉,吃炒米粉
做了炒fan,吃炒fan

【五】生成器內部修改可變資料型別

def init_iter(func):
    def inner(*args,**kwargs):
        # g 是得到的生成器物件
        g = func(*args,**kwargs)
        # 呼叫自己生成器往下走
        next(g)
        # 走回來的返回值返回出去
        return g
    return inner

@init_iter
def outer():
    print("開始吃飯")
    list = []
    while True:
        # yield food
        food = yield
        list.append(food)	因為沒有退出迴圈,所以會把新值一直加到列表中
        print(f"做了{food},吃{food}")
        print(list)

res = outer()
res.send('炒米粉')
res.send('炒fan')


開始吃飯
做了炒米粉,吃炒米粉
['炒米粉']
做了炒fan,吃炒fan
['炒米粉', '炒fan']

【六】yield + next 搭配著使用

仿照迴圈自己構建迴圈函式

for i in range(5):
    print(i)
def my_range(start, stop, step):
    '''

    :param start: 0
    :param stop: 5
    :param step: 1
    :return:
    '''
    print("start")
    while start < stop:
        yield start
        start += step
    print("end")


res = my_range(0, 5, 1)
print(res)  # <generator object my_range at 0x000002DF0361CBA0>
print(res.__next__())
print(res.__next__())
print(res.__next__())
print(res.__next__())
print(res.__next__())
# 這裡會報錯 ---> 沒有可迭代的值可以被獲取了
print(res.__next__())  # StopIteration
def init_iter(func):
    def inner(*args, **kwargs):
        try:
            generator = func(*args, **kwargs)
            next(generator)
            return generator
        except StopIteration:
            pass

    return inner

def range_(start, stop, step):
    while start < stop:
        yield start
        start += step

# for 迴圈內部做了異常捕獲
for i in range_(0,5,1):
    print(i)

def my_range(start, stop, step):
    def range_(start, stop, step):
        while start < stop:
            yield start
            start += step

    res = range_(start, stop, step)
    while True:
        try:
            print(res.__next__())
        except StopIteration:
            break

res = my_range(start=1, stop=5, step=1)
res

0
1
2
3
4
1
2
3
4

相關文章