Python中for循環運行機制探究以及可迭代對象、迭代器詳解

  Python中的for循環語法十分簡潔,那麼其背後的運行機制你是否瞭解。實際上,要了解其背後的機制,需要先充分了解Python中另外兩個重要概念:可迭代對象、迭代器。

  1. 可迭代對象概念首窺

  下面首先通過for循環來引出可迭代對象的概念。如下述代碼:

  def test_list():

  for each in [1, 2, 3]:

  print(each)

  print()

  def test_tuple():

  for each in (11, 22, 33):

  print(each)

  print()

  def test_dict():

  for key, value in {"C": "Cheng", "Q": "Qing", "S": "Song"}.items():

  print(key, "=", value)

  print()

  def test_str():

  for each in "Pythonic":

  print(each)

  print()

  def test_int():

  for each in 100:

  print(each)

  print()

  def main():

  test_list()

  test_tuple()

  test_dict()

  test_str()

  test_int()

  if __name__ == "__main__":

  main()

  上述代碼的運行結果爲:

  1

  2

  3

  11

  22

  33

  C = Cheng

  Q = Qing

  S = Song

  P

  y

  t

  h

  o

  n

  i

  c

  Traceback (most recent call last):

  File “/home/XXX/test.py”, line 50, in

  main()

  File “/home/XXX/test.py”, line 46, in main

  test_int()

  File “/home/XXX/test.py”, line 35, in test_int

  for each in 100:

  TypeError: ‘int’ object is not iterable

  由上述運行結果可知,列表、元組、字典、字符串都可以使用for循環遍歷,而int數值不可以,且編譯器提示TypeError: ‘int’ object is not iterable。

  根據上述結果以及錯誤信息,可以反推:

  既然數值不可以通過for循環遍歷,且編譯器報錯爲“數值不可以迭代”,那麼列表、元組、字典、字符串必然都是可迭代的,即對象可以使用for循環的必要條件是該對象是可迭代的。

  由此,我們引出了可迭代可迭代對象的概念,進一步地,在Python的官方文檔中,對於iterable給出的定義爲:

  An object capable of returning its members one at a time.

  可迭代對象是一類對象,即這類對象可以一次返回一個內部成員(聯繫for循環的現象)。

  Examples of iterables include all sequence type (such as list, str, and tuple) and some non-sequence types like dict, file objects, and objects of any classes you define with an __iter__() method or with a __getitem__() method that implement Sequence semantics.

  可迭代對象的例子包括所有序列類型(如:列表、字符串、元組)以及部分非序列類型,如:字典、文件對象,以及任何定義了__iter__()(所謂“支持迭代協議(iteration protocol)”)或__getitem__()(所謂“支持序列協議(sequence protocol)”)方法的類所創建的對象。

  2. 判斷對象是否是可迭代的

  在Python中可以通過內置函數isinstance(object, classinfo)來方便地判斷一個對象是否爲可迭代的,即:函數的第一個參數傳入待判斷對象,第二個參數爲包collections.abc(abc爲Abstract Base Class的縮寫)中Iterable類,如果函數返回值是True,則待判斷對象是可迭代的,否則不是。如:

  In [1]: from collections.abc import Iterable

  In [2]: isinstance([1, 2], Iterable)

  Out[2]: True

  In [3]: isinstance((11, 22), Iterable)

  Out[3]: True

  In [4]: isinstance("CodingGuru", Iterable)

  Out[4]: True

  In [5]: isinstance({"P": "Python"}, Iterable)

  Out[5]: True

  In [6]: isinstance(100, Iterable)

  Out[6]: False

  3. 自定義類創建對象使用for循環

  通過前面的討論,我們知道:

  可以使用for循環的必要條件是該對象是可迭代的;

  任何對象想要成爲可迭代對象的一個充分條件是創建該對象的類實現了 __iter__()或__getitem__() 方法。

  3.1 自定義類實現__iter()__方法

  那麼,自然地,我們會想,是否通過任何實現__iter__()或__getitem__() 方法的類所創建的對象都可以使用for循環呢?下面通過代碼驗證:

  from collections.abc import Iterable

  classe ScandinavianGod(object):

  def __init__(self):

  self.names = list()

  def add_name(self, name):

  self.names.append(name)

  def __iter__(self):

  pass

  def main():

  scandinavian_god = ScandinavianGod()

  scandinavian_god.add_name("奧丁")

  scandinavian_god.add_name("托爾")

  scandinavian_god.add_name("洛基")

  print("判斷scandinavian_god是否爲可迭代對象:", isinstance(scandinavian_god, Iterable))

  for name in scandinavian_god:

  print(name)

  if __name__ == "__main__":

  main()

  上述代碼的運行結果爲:

  判斷scandinavian_god是否爲可迭代對象: True

  Traceback (most recent call last):

  File “/home/XXX/iterator.py”, line 29, in

  main()

  File “/home/XXX/iterator.py”, line 24, in main

  for name in scandinavian_god:

  TypeError: iter() returned non-iterator of type ‘NoneType’

  通過上述代碼的運行結果可知,對象scandinavian_god雖然是可迭代的,但是不能使用for循環,即:

  一個對象是可迭代的是一個對象可使用for循環的必要不充分條件。

  因此,我們將目光再次轉向Python官方文檔關於__iter()__解釋的部分:

  This method is called when an iterator is required for a container.

  當一個容器需要一個迭代器時,該方法被調用。

  This method should return a new iterator object that can iterate over all the objects in the container.

  該方法應當返回一個新的迭代器對象,該迭代器對象可以迭代容器中的所有對象。

  Iterator objects also need to implement this method; they are required to return themselves.

  迭代器對象也需要實現該方法,因爲迭代器對象需要返回其自身。

  即上述問題出現在,我們並沒有正確實現__iter()__方法,即沒有讓該方法返回一個迭代器對象,再回看上述代碼的錯誤信息也是指向這一點,即:TypeError: iter() returned non-iterator of type ‘NoneType’。

  3.1.1 迭代器

  雖然找到了問題,但我們首先需要知道iter()方法要返回的迭代器是什麼,Python官方文檔中,對於迭代器的定義爲:

  An object representing a stream of data.

  迭代器是一個對象,該對象代表了一個數據流。

  Repeated calls to the iterator’s __next__() method (or passing it to the built-in function next()) return successive items in the stream.

  重複調用迭代器的__next__()方法(或將迭代器對象當作參數傳入內置函數next()中)將依次返回數據流中的元素。

  When no more data are available a StopIteration exception is raised instead.

  當數據流中無可返回元素時,則拋出StopIteration異常。

  Iterators are required to have an __iter__() method that returns the iterator object itself so every iterator is also iterable and may be used in most places where other iterables are accepted.

  迭代器必須擁有__iter__()方法,該方法返回迭代器對象自身,因此,每一個迭代器都是可迭代的,並且可以用於大多數可迭代對象的使用場合(其他場合請見第5部分——迭代器應用)。

  3.1.2 自定義迭代器類

  通過上述的研究,我們知道,自定義類創建的對象要想使用for循環的一個充分條件是:

  該對象需要實現__iter()__方法,且

  __iter()__方法返回一個迭代器對象。

  而對於迭代器對象,Python官方文檔的要求爲:

  迭代器必須擁有__iter__()方法,該方法返回迭代器對象自身;

  迭代器還需要擁有__next__()方法,而重複調用迭代器的__next__()方法(或將迭代器對象當作參數傳入內置函數next()中)將依次返回數據流中的元素。

  基於上述結論,下面代碼:

  實現一個自定義迭代器類ScandinavianGodIterator:

  該類中擁有一個__iter__()方法,該方法返回迭代器對象自身;

  該類中還擁有一個__next__()方法,該方法依次返回數據流中的元素;

  爲ScandinavianGod類中的__iter__()方法返回迭代器類ScandinavianGodIterator所創建的對象。

  基於上述分析,有下面代碼:

  classe ScandinavianGod(object):

  def __init__(self):

  self.names = list()

  def add_name(self, name):

  self.names.append(name)

  def __iter__(self):

  # 將通過ScandinavianGod類創建的對象引用傳遞至迭代器初始化方法,

  # 使得:通過類ScandinavianGodIterator創建的對象後,

  # 該對象的__next__()方法可以獲取實例屬性names後進行數據依次取出

  return ScandinavianGodIterator(self)

  classe ScandinavianGodIterator(object):

  def __init__(self, obj):

  # 定義一個實例屬性,用於接收傳遞過來的ScandinavianGod類創建的對象引用

  self.obj = obj

  self.current_index = 0

  def __iter__(self):

  return self

  def __next__(self):

  if self.current_index < len(self.obj.names):

  ret = self.obj.names[self.current_index]

  self.current_index += 1

  return ret

  else:

  # 遍歷完self.obj.names實例屬性後,拋出該異常,該異常將由for循環捕捉處理

  raise StopIteration

  def main():

  scandinavian_god = ScandinavianGod()

  scandinavian_god.add_name("奧丁")

  scandinavian_god.add_name("托爾")

  scandinavian_god.add_name("洛基")

  for name in scandinavian_god:

  if name == "奧丁":

  print("主神:%s" % name)

  elif name == "托爾":

  print("雷神:%s" % name)

  elif name == "洛基":

  print("惡作劇與毀滅之神:%s" % name)

  else:

  print("其他神:%s" % name)

  if __name__ == "__main__":

  main()

  運行上述代碼,輸出結果爲:

  主神:奧丁

  雷神:托爾

  惡作劇與毀滅之神:洛基

  即成功實現了自定義類創建對象使用for循環。

  3.1.3 迭代器使用for循環

  由上述討論可知,如果想要對自定義類創建的對象使用for循環,需要使用兩個類,此舉不僅複雜還不直觀,是否可以僅使用一個類呢?答案是肯定的。

  首先,有上述討論可知:迭代器一定是可迭代對象(因爲其實現了__iter__()方法),但可迭代對象不一定是迭代器(因爲可迭代對象未實現__next__()方法)。

  因此,可迭代對象要想成爲一個迭代器,其中一個充分條件是其要實現__next__()方法。

  故:

  在ScandinavianGod類中實現__next__()方法使之通過其創建的對象都是迭代器;

  在__iter__()方法處返回self。

  因此,有下列代碼:

  classe ScandinavianGod(object):

  def __init__(self):

  self.names = list()

  self.current_index = 0

  def add_name(self, name):

  self.names.append(name)

  def __iter__(self):

  return self

  def __next__(self):

  if self.current_index < len(self.names):

  ret = self.names[self.current_index]

  self.current_index += 1

  return ret

  else:

  raise StopIteration

  def main():

  scandinavian_god = ScandinavianGod()

  scandinavian_god.add_name("奧丁")

  scandinavian_god.add_name("托爾")

  scandinavian_god.add_name("洛基")

  for name in scandinavian_god:

  print(name)

  if __name__ == "__main__":

  main()

  3.2 自定義類實現__getitem()__方法

  classe ScandinavianGod(object):

  def __init__(self):

  self.names = list()

  def add_name(self, name):

  self.names.append(name)

  def __getitem__(self, key):

  if (type(key) is int) and (key < len(self.names)):

  print("key = ", key)

  return self.names[key]

  elif type(key) is not int:

  raise TypeError

  else:

  raise IndexError

  def main():

  scandinavian_god = ScandinavianGod()

  scandinavian_god.add_name("奧丁")

  scandinavian_god.add_name("托爾")

  scandinavian_god.add_name("洛基")

  # for循環方式遍歷

  print("=" * 10, "for循環方式遍歷:", "=" * 10)

  for name in scandinavian_god:

  if name == "奧丁":

  print("主神:%s" % name)

  elif name == "托爾":

  print("雷神:%s" % name)

  elif name == "洛基":

  print("惡作劇與毀滅之神:%s" % name)

  else:鄭州人流手術多少錢 http://www.zykdrl120.com

  print("其他神:%s" % name)

  # 索引方式遍歷

  print("=" * 10, "索引方式遍歷:", "=" * 10)

  print(scandinavian_god[0])

  print(scandinavian_god[1])

  print(scandinavian_god[2])

  if __name__ == "__main__":

  main()

  3.3 for循環運行機制小結

  通過上述分析,我們知道,對於自定義類實現__iter__()方法,使用for循環遍歷一個對象的流程應該是:

  判斷該對象是否爲可迭代的:檢查該對象是否有__iter__()方法;

  判斷__iter__()方法是否有正確類型返回值:檢查返回值是否爲迭代器引用;

  依次順序獲取待遍歷元素:調用迭代器的__next__()方法;

  判斷是否遍歷完所有元素:捕獲處理StopIteration異常。

  4. 迭代器應用

  在上述討論中,我們明確了可迭代對象、迭代器的概念,並且基於這兩個概念,實現了對於自定義類創建的對象使用for循環。

  那麼,是否上述所有的討論都僅僅只能實現對自定義類創建對象使用for循環呢?答案是否定的,這樣一種情形下,迭代器可以大大提升程序(內存方面)的效率:即希望利用程序生成大量數據供程序使用,如:生成一個很長的斐波那契數列。

  針對上述需求,現有兩種實現思路:

  定義一個迭代器類,實現生成斐波那契數列的方法;

  定義一函數,利用一個容器(如:列表)存儲生成的斐波那契數列的每一項。

  4.1 利用迭代器實現數據生成的方式

  下面代碼利用迭代器實現數據生成的方式,並使用for循環遍歷數列:

  import time

  class Fibonacci(object):

  def __init__(self, len_of_fib):

  self._current_fib_num = 0

  self._next_fib_num = 1

  self._len_of_fib = len_of_fib

  self._current_index = 0

  def __iter__(self):

  return self

  def __next__(self):

  if self._current_index < self._len_of_fib:

  ret = self._current_fib_num

  self._current_fib_num, self._next_fib_num = \

  self._next_fib_num, self._current_fib_num + self._next_fib_num

  self._current_index += 1

  return ret

  else:

  raise StopIteration

  def main():

  t_start = time.time()

  for each in Fibonacci(100000):

  pass

  t_stop = time.time()

  elapsed_time = t_stop - t_start

  print(elapsed_time)

  if __name__ == "__main__":

  main()

  爲了進行對比,下面代碼利用容器先存儲生成的數據,然後使用for循環遍歷數列::

  import time

  def fibonacci(len_of_fib):

  fib_sequence = list()

  current_fib_num = 0

  next_fib_num = 1

  current_index = 0

  while True:

  if current_index < len_of_fib:

  fib_sequence.append(current_fib_num)

  current_fib_num, next_fib_num = next_fib_num, \

  current_fib_num + next_fib_num

  current_index += 1

  else:

  break

  return fib_sequence

  def main():

  t_start = time.time()

  # fibonacci(1000000)

  for each in fibonacci(100000):

  pass

  t_stop = time.time()

  elapsed_time = t_stop - t_start

  print(elapsed_time)

  if __name__ == "__main__":

  main()

  運行上述代碼,結果分別爲:

  利用迭代器實現數據生成方式

  程序執行完成所需的時間爲0.109802485秒…

  利用容器存儲生成的數據:

  程序執行完成所需的時間爲0.226054192秒…

  我們知道:

  第一種方式程序執行所需時間僅爲第二種方式的一半;

  實際上,如果再增加數列項數的數量級,第一種方式所需時間雖然會增加,但第二種方式會很快導致計算機無法執行程序。

  究其緣由,在於第二種方式是一次性生成大量數據,然後將其保存在列表中,內存的開銷相比於第一種大得多得多,第一種方式只是實現了獲取所需數據的方式,故內存開銷只有運算和實例屬性存儲時所需的內存。

  實際上,上述兩種實現策略分別相當於Python 2.7中xrange()和range()的實現策略。

  4.2 作爲參數傳入其他接收可迭代對象處

  在Python中,可以很方便地使用tuple()將一個列表轉換爲元組,使用list()實現相反操作。實際上,tuple()和list()的形參處所接收的都是可迭代對象,如下述代碼:

  class Fibonacci(object):

  def __init__(self, len_of_fib):

  self._current_fib_num = 0

  self._next_fib_num = 1

  self._len_of_fib = len_of_fib

  self._current_index = 0

  def __iter__(self):

  return self

  def __next__(self):

  if self._current_index < self._len_of_fib:

  ret = self._current_fib_num

  self._current_fib_num, self._next_fib_num = \

  self._next_fib_num, self._current_fib_num + self._next_fib_num

  self._current_index += 1

  return ret

  else:

  raise StopIteration

  def main():

  fib_tuple = tuple(Fibonacci(10))

  print(fib_tuple)

  fib_list = list(Fibonacci(10))

  print(fib_list)

  if __name__ == "__main__":

  main()

  其運行結果爲:

  (0, 1, 1, 2, 3, 5, 8, 13, 21, 34)

  [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章