簡體   English   中英

為什么在 Python 中有一個 __iter__ 方法?

[英]Why have an __iter__ method in Python?

為什么有一個__iter__方法? 如果一個對象是一個迭代器,那么擁有一個返回自身的方法是沒有意義的。 如果它不是迭代器而是一個迭代器,即具有__iter____getitem__方法的東西,那么為什么要定義返回迭代器但不是迭代器本身的東西呢? 在 Python 中,何時需要定義一個本身不是迭代器的可迭代對象? 或者,什么是可迭代但不是迭代器的示例?

嘗試一次回答一個問題:

為什么有一個__iter__方法? 如果一個對象是一個迭代器,那么擁有一個返回自身的方法是沒有意義的。

這不是毫無意義的。 迭代器協議需要__iter____next__ (或 Python 2 中的next )方法。 我見過的所有理智的迭代器都只是在他們的__iter__方法中return self ,但擁有該方法仍然至關重要。 沒有它會導致各種怪異,例如:

somelist = [1, 2, 3]
it = iter(somelist)

現在

iter(it)

for x in it: pass

會拋出一個TypeError並抱怨it不可迭代,因為當iter(x)被調用時(當你使用for循環時隱式發生)它期望參數對象x能夠產生一個迭代器(它只是試圖調用__iter__在那個對象上)。 具體示例(Python 3):

>>> class A:
...     def __iter__(self):
...         return B()
...
>>> class B:
...     def __next__(self):
...         pass
...
>>> iter(iter(A()))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'B' object is not iterable

考慮任何函數,特別是來自itertools 的期望可迭代的函數,例如dropwhile 使用具有__iter__方法的任何對象調用它都可以,無論它是不是迭代器的可迭代對象,還是迭代器——因為當使用該對象作為參數調用iter時,您可以期待相同的結果。 在這里對兩種可迭代對象進行奇怪的區分會違背 python 強烈擁護的鴨子類型原則。

整潔的技巧,如

>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(zip(*[iter(a)]*3))
[(1, 2, 3), (4, 5, 6), (7, 8, 9)]

如果您無法將迭代器傳遞給zip它將停止工作。

為什么要定義一些返回迭代器但本身不是迭代器的東西

讓我們考慮這個簡單的列表迭代器:

>>> class MyList(list):
...     def __iter__(self):
...         return MyListIterator(self)
>>>
>>> class MyListIterator:
...     def __init__(self, lst):
...         self._lst = lst
...         self.index = 0
...     def __iter__(self):
...         return self
...     def __next__(self):
...         try:
...             n = self._lst[self.index]
...             self.index += 1
...             return n
...         except IndexError:
...             raise StopIteration
>>>    
>>> a = MyList([1,2,3])
>>> for x in a:
...     for x in a:
...         x
...
1
2
3
1
2
3
1
2
3

請記住,對於兩個for循環, iter都是使用有問題的可迭代對象調用的,每次都期望從對象的__iter__方法獲得一個新的迭代器。

現在,如果沒有每次使用for循環時都生成迭代器,那么當MyList對象同時迭代任意次數時,您如何能夠跟蹤任何迭代的當前狀態? 哦,沒錯,你不能。 :)

編輯:獎金和對 Tadhg McDonald-Jensen 評論的回復

可重用迭代器並非不可想象,但當然有點奇怪,因為它依賴於使用“不可消費”迭代器(即不是經典迭代器)進行初始化:

>>> class riter(object):
...     def __init__(self, iterable):
...         self.iterable = iterable
...         self.it = iter(iterable)
...     def __next__(self): # python 2: next
...         try:
...             return next(self.it)
...         except StopIteration:
...             self.it = iter(self.iterable)
...             raise
...     def __iter__(self):
...         return self
... 
>>> 
>>> a = [1, 2, 3]
>>> it = riter(a)
>>> for x in it:
...     x
... 
1
2
3
>>> for x in it:
...     x
... 
1
2
3

迭代器是可以迭代(循環)的東西,而迭代器是被消費的東西。

什么是可迭代但不是迭代器的示例?

簡單,一個list 或任何序列,因為您可以在不破壞列表的情況下根據需要多次迭代列表:

>>> a = [1,2,3]
>>> for i in a:
    print(i,end=" ")

1 2 3 
>>> for i in a:
    print(i,end=" ")

1 2 3 

作為迭代器(如生成器)只能使用一次的地方:

>>> b = (i for i in range(3))
>>> for i in b:
    print(i,end=" ")

0 1 2 
>>> for i in b:
    print(i,end=" ")


>>> #iterator has already been used up, nothing gets printed

對於像迭代器一樣使用的列表,您需要使用self.pop(0)類的東西來刪除列表的第一個元素以進行迭代:

class IteratorList(list):
    def __iter__(self):
        return self #since the current mechanics require this
    def __next__(self):
        try:
            return self.pop(0)
        except IndexError: #we need to raise the expected kind of error
            raise StopIteration
    next = __next__ #for compatibility with python 2

a = IteratorList([1,2,3,4,5])

for i in a:
    print(i)
    if i==3:  # lets stop at three and
        break # see what the list is after

print(a)

這給出了這個輸出:

1
2
3
[4, 5]

你看到了嗎? 這就是迭代器所做的,一旦一個值從__next__返回,它就沒有理由在迭代器或內存中徘徊,所以它被刪除了。 這就是為什么我們需要__iter__來定義迭代器,讓我們迭代序列而不在過程中破壞它們。


為了回應@timgeb 的評論,我想如果您將項目添加到IteratorList然后再次迭代它會有意義:

a = IteratorList([1,2,3,4,5])

for i in a:
    print(i)

a.extend([6,7,8,9])

for i in a:
    print(i)

但是所有迭代器只有在被消費或永不結束時才有意義。 (如itertools.repeat

你在錯誤的方向思考。 迭代器必須實現__iter__的原因是這樣,容器迭代器都可以在forin語句中使用。

> # list is a container
> list = [1,2,3]
> dir(list)
[...,
 '__iter__',
 '__getitem__',
 ...]

> # let's get its iterator
> it = iter(list)
> dir(it)
[...,
 '__iter__',
 '__next__',
 ...]

> # you can use the container directly:
> for i in list:
>     print(i)
1
2
3

> # you can also use the iterator directly:
> for i in it:
>     print(i)
1
2
3
> # the above will fail if it does not implement '__iter__'

這也是為什么你只需要在迭代器的幾乎所有實現中都返回self 它並不意味着任何時髦的東西,只是語法上的一點點簡單。

參考: https : //docs.python.org/dev/library/stdtypes.html#iterator-types

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM