Iterator vs Generator trong Python

att288
SWE Tieng Viet
Published in
6 min readMay 5, 2019

Một chút tản mạn vì tại sao lại có bài viết này. Hôm trước mình có dạo quanh Quora, mình vô tình đọc được 1 comment của 1 bác làm ở Google về Python developers. Đại loại, bác ấy chia Python developers làm 3 loại:

- Loại 1: hiểu rõ được sự khác nhau giữa Generator và Iterator
- Loại 2: không phân biệt được sự khác nhau
- Loại 3: chưa bao giờ nghe thấy Generator là gì

Đọc xong comment của bác ấy, mình thấy hơi chột dạ và nghĩ ngay ra việc nên viết bài viết này, với hy vọng thế giới sẽ bớt đi số lượng lập trình viên thuộc loại số 3 (trong đấy có mình 😆).

Trong bài viết này, mình sẽ đi theo trình tự như sau:
1. Iterator trong Python
2. Thế nào là Generator
3. Sự khác nhau giữa Generator và Iterator

Iterator trong Python

Để hiểu rõ về Iterator, trước hết mình sẽ giới thiệu qua vài khái niệm:
- Iterable
- Iterator
- The Iterator Protocol trong Python

Iterable là gì?

Đầu tiên, hãy bắt đầu với 1 ví dụ đơn giản:

print("Printing my tuple")
my_tuple = (1, 2, 3, 4)
i = 0
while i < len(my_tuple):
print(my_tuple[i])
i+=1
print("Printing my list")
my_list = [-1, -2, -3, -4]
i = 0
while i < len(my_list):
print(my_list[i])
i+=1

==> Output:
Printing my tupe
1
2
3
4
Printing my tupe
-1
-2
-3
-4

Bạn có thể thấy mình cả my_tuplemy_list đều cho phép mình access value từ index phải không (print(my_tuple[i]), print(my_list[i]))? Hãy thử với 1 data structure khác. Với set chẳng hạn:

my_set = {'elm1', 'elm2', 'elm3'}for i in range(len(my_set)):
print(my_set[i])

==> Output:
----------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-147-f4c878ab5355> in <module>
2
3 for i in range(len(my_set)):
----> 4 print(my_set[i])

TypeError: 'set' object does not support indexing

Có vẻ không ổn. Chương trình báo lỗi: TypeError: ‘set’ object does not support indexing. Nó có nghĩa là mình không thể access set bằng index (hay có nghĩa là set trong Python không phải là 1 indexable object). Như vậy, không phải collection nào cũng là indexable đúng không?

Để đơn giản, mình sẽ phân loại những data structures này làm 2 loại:
- Sequences: list, tuple, string, etc.
- Not sequences: dictionary, set, etc.

Khoan, nếu như vậy thì làm cách nào để access element từ not sequence? Và chẳng phải mình vẫn access set element bằng cách dưới này sao:

my_set = {'elm1', 'elm2', 'elm3'}for elm in my_set:
print(elm)
==> Output:elm3
elm2
elm1

Đúng vậy, bạn vẫn có thể dùng ‘for loop’ để access element từ sequence và not sequence collections. Vì sao? Vì thật ra cả 2 loại data structures đều cho phép bạn visit từng element một. Vậy 2 loại đấy có điểm chung gì? Điểm chung chính là bạn có thể dùng for loop để access từng element. Và đấy cũng chính là định nghĩa của iterable: 1 iterable object là 1 object mà bạn có thể dùng for loop để access từng element của nó. Tuy nhiên, object đấy có thể là indexable (sequence) hoặc non-indexable (dict, set, etc.). Vì sao mình lại đề cập đến non-indexable data structures ở đây? Hãy xem qua ví dụ này:

from itertools import count
infinite_counter = count(step=1)
for i in infinite_counter:
if i > 3:
break
print(i)
==> Output:0
1
2
3

Bạn có thể thấy infinite_counter là 1 iterable object (vì mình có thể dùng for loop để access từng element của nó). Tuy nhiên, nó không có length, và nó sẽ là infinite loop nếu mình gỡ bỏ dòng lệnh break.

Iterator là gì?

Tiếp theo, hãy xem iterator là gì, và mối liên hệ của iterator và iterable.

my_list_iterator = iter([1, 2, 3, 4, 5])
my_string_iterator = iter('Hello world')
print(my_list_iterator)
print(my_string_iterator)
==> Output:
<list_iterator object at 0x10f111cf8>
<str_iterator object at 0x10f111c18>

Từ ví dụ trên, mình có thể thấy iterator là 1 returned object từ function iter(). Khi mình iter() 1 list, nó sẽ là list_iterator, khi mình iter() 1 string, nó sẽ là str_iterator. Khoan, thế iterator dùng để làm gì?

Nhiệm vụ của iterator khá đơn giản. Return next element khi mình gọi next() function từ iterator, và trả lại StopIteration Exception nếu không còn next element.

for idx in range(10):
print(f'idx={idx}, ', next(my_list_iterator))
==> Output:
idx=0, 1
idx=1, 2
idx=2, 3
idx=3, 4
idx=4, 5
------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-180-d1afc3596fa1> in <module>
1 for idx in range(10):
----> 2 print(f'idx={idx}, ', next(my_list_iterator))

StopIteration:

Như bạn thấy ở trên, next(my_list_iterator) trả lại giá trị từ 1 đến 5 trong 5 lần lặp đầu tiên. Tuy nhiên, khi idx=5, chương trình trả lại StopIteration Exception.

Tóm lại, iterator là return object khi mình gọi iter() function của 1 iterable object. Và iterator sẽ return next element hoặc StopIteration Exception khi mình gọi next(iterator).

*** Interesting fact: iterator cũng là 1 iterable (có nghĩa là khi bạn gọi iter(iterator) thì nó sẽ trả lại chính nó).

iter(my_list_iterator)==> Output:
<list_iterator at 0x10f111cf8>
# Memory address chính là memory address của my_list_iterator

The Iterator Protocol trong Python

Iterator Protocol là cách nói khác của việc giải thích “Iterables trong Python hoạt động như thế nào”. Hãy tóm tắt theo ngôn ngữ Python:

  • Iterable là 1 object mà khi mình pass nó vào iter() fuction thì nó sẽ return một iterator.
  • Iterator là 1 object mà khi mình pass nó vào next() function thì nó sẽ return next element hoặc StopIteration Exception.
  • Iterator cũng là 1 iterable vì iter(interator) return chính nó.

Chú ý: iter() và next() là built-in funcions của Python.

=> Như vậy, bạn có thể thấy định nghĩa của Iterator trong Python là 1 abstract defintion, và nó chỉ cần đảm bảo điều kiện:

  • Return next element/StopIteration Exception trong next() function
  • Return chính nó trong iter() function

Generator trong Python

Bạn đã bao giờ nghe đến keyword “yield” trong Python? Nếu bạn đã nhìn thấy ở đâu đó, thì chắc hẳn là bạn đã từng làm việc với Generator rồi đấy.

Hãy bắt đầu với ví dụ này để nhắc lại 1 chút khái niệm về yield:

def my_generator(n):
my_sum = 0
for i in range(n):
my_sum += 5
yield i, my_sum
def my_calculator(n):
my_sum = 0
for i in range(n):
my_sum += 5
return i, my_sum
if __name__ == '__main__':
print(my_generator(5))
print(my_calculator(5))
==> Output:
<generator object my_generator at 0x10eea22b0>
(0, 5)

Như các bạn thấy ở trên, sự khác nhau duy nhất giữa 2 functions my_generatormy_calculator là ở my_generator mình sử dụng keyword yield, còn ở my_calculatorreturn.

Khi mình gọi my_generator(5), function returned lại cho mình 1 object được gọi là generator object, còn khi mình gọi my_calculator(5), thì function trả lại cho mình giá trị (0, 5).

Như vậy, function sử dụng yield sẽ trả lại 1 generator object thay vì giá trị tại thời điểm đấy. Vậy generator object là gì, và làm cách nào để sử dụng nó? Hãy tiếp tục với ví dụ tiếp theo:

def my_generator(n):
my_sum = 0
for i in range(n):
my_sum += 5
yield i, my_sum
generator_obj = my_generator(5)for returned_obj in generator_obj:
print(returned_obj)
==> Output:
(0, 5)
(1, 10)
(2, 15)
(3, 20)
(4, 25)

Ồ, như vậy là mình có thể iterate generator_obj và nhận giá trị mỗi lần yield được gọi. Khá là thú vị phải không nào?

Hãy phân tích sâu hơn 1 tẹo qua 1 vài dòng lệnh cơ bản dưới đây:

generator_obj = my_generator(5)print(iter(generator_obj))
print(next(generator_obj))
==> Output:<generator object my_generator at 0x10eea2ba0>
(0, 5)

Như ta thấy, generator_obj vừa hỗ trợ iter()next(). Như vậy generator_obj chính là 1 iterator phải không nào?

Lại tiếp tục 1 dòng lệnh khác:

len(generator_obj)==> Output:TypeError                         Traceback (most recent call last)
<ipython-input-209-1205efd149ff> in <module>
----> 1 len(generator_obj)

TypeError: object of type 'generator' has no len()

Hmm, generator không có len(). Điều này chứng tỏ là generator không phải là 1 collection đúng không?

Đến đây, mình có thể kết luận gì?

  • Generator là 1 loại Iterator
  • Generator không phải là 1 loại data structure (list, dict, set, etc.), mà là 1 loại function (lazy evaluation). Function này sẽ yield giá trị cần trả lại tại thời điểm khi next() được gọi.

Như vậy, có thể bạn sẽ thắc mắc: Generator là 1 loại Iterator thì Iterator có phải là Generator? Câu trả lời là không. Vì Iterator cũng có thể là 1 collection (list, dict, etc.), miễn là nó support iter() next(). Hiểu đơn giản thì Iterator là 1 abstract definition, còn generator là 1 loại function mà ở đấy nó return 1 iterator sử dụng lazy evaluation.

Mình hy vọng bài viết này phần nào giúp bạn hiểu rõ hơn sự khác nhau giữa Iterator và Generator trong Python. Bạn có thể tìm hiểu sâu hơn về Generator trong bài viết này.

Happy Pythoning!

--

--