Generators và Iterators
Generators và Iterators là hai khái niệm quan trọng trong Python giúp làm việc hiệu quả với chuỗi dữ liệu lớn mà không cần load toàn bộ vào bộ nhớ.
1. Iterators (Trình lặp)
Phần tiêu đề “1. Iterators (Trình lặp)”Iterator là gì?
Phần tiêu đề “Iterator là gì?”Iterator là một object có thể duyệt qua (iterate) từng phần tử một, tuân theo Iterator Protocol.
Iterator Protocol yêu cầu:
- Có phương thức
__iter__(): Trả về chính object đó - Có phương thức
__next__(): Trả về phần tử tiếp theo, raiseStopIterationkhi hết
# List là iterable (có thể lặp), KHÔNG phải iteratormy_list = [1, 2, 3]
# Tạo iterator từ iterablemy_iterator = iter(my_list)
# Lấy từng phần tửprint(next(my_iterator)) # 1print(next(my_iterator)) # 2print(next(my_iterator)) # 3
# Hết phần tử → StopIteration# print(next(my_iterator)) # StopIterationFor loop sử dụng iterator ngầm định
Phần tiêu đề “For loop sử dụng iterator ngầm định”# Khi dùng for loopfor item in [1, 2, 3]: print(item)
# Python thực ra làm:iterator = iter([1, 2, 3])while True: try: item = next(iterator) print(item) except StopIteration: breakTạo Iterator tùy chỉnh
Phần tiêu đề “Tạo Iterator tùy chỉnh”class Counter: """Iterator đếm từ start đến end"""
def __init__(self, start, end): self.current = start self.end = end
def __iter__(self): return self
def __next__(self): if self.current > self.end: raise StopIteration self.current += 1 return self.current - 1
# Sử dụngcounter = Counter(1, 5)
for num in counter: print(num) # 1, 2, 3, 4, 5
# Hoặc dùng next()counter2 = Counter(10, 12)print(next(counter2)) # 10print(next(counter2)) # 11print(next(counter2)) # 12# print(next(counter2)) # StopIterationIterator vô hạn
Phần tiêu đề “Iterator vô hạn”class InfiniteCounter: """Iterator đếm vô hạn"""
def __init__(self, start=0): self.current = start
def __iter__(self): return self
def __next__(self): self.current += 1 return self.current - 1
# Sử dụng với breakcounter = InfiniteCounter()
for num in counter: if num >= 5: break print(num) # 0, 1, 2, 3, 42. Generators (Trình tạo)
Phần tiêu đề “2. Generators (Trình tạo)”Generator là gì?
Phần tiêu đề “Generator là gì?”Generator là một cách đơn giản để tạo iterator bằng cách dùng từ khóa yield.
Lợi ích:
- Code ngắn gọn hơn iterator class
- Tự động implement Iterator Protocol
- Tiết kiệm bộ nhớ (lazy evaluation)
# Generator functiondef count_up_to(n): count = 1 while count <= n: yield count count += 1
# Tạo generator objectcounter = count_up_to(5)
print(type(counter)) # <class 'generator'>
# Duyệt qua generatorfor num in counter: print(num) # 1, 2, 3, 4, 5yield vs return
Phần tiêu đề “yield vs return”# return: Kết thúc hàm ngay lập tứcdef with_return(n): for i in range(n): return i # Chỉ trả về 0
print(with_return(5)) # 0
# yield: Tạm dừng, giữ trạng thái, tiếp tục saudef with_yield(n): for i in range(n): yield i # Trả về từng giá trị
gen = with_yield(5)print(next(gen)) # 0print(next(gen)) # 1print(next(gen)) # 2Generator expression
Phần tiêu đề “Generator expression”# List comprehension (load hết vào memory)squares_list = [x**2 for x in range(1000000)]print(type(squares_list)) # <class 'list'>
# Generator expression (lazy evaluation)squares_gen = (x**2 for x in range(1000000))print(type(squares_gen)) # <class 'generator'>
# Sử dụngfor square in squares_gen: if square > 100: break print(square)3. So sánh Generator và List
Phần tiêu đề “3. So sánh Generator và List”Bộ nhớ
Phần tiêu đề “Bộ nhớ”import sys
# List: Load toàn bộ vào memorynumbers_list = [x for x in range(1000000)]print(f"List size: {sys.getsizeof(numbers_list)} bytes")# List size: ~8000000 bytes
# Generator: Chỉ lưu công thứcnumbers_gen = (x for x in range(1000000))print(f"Generator size: {sys.getsizeof(numbers_gen)} bytes")# Generator size: ~200 bytesHiệu năng
Phần tiêu đề “Hiệu năng”import time
# List: Tạo toàn bộ ngaystart = time.time()numbers_list = [x**2 for x in range(1000000)]total = sum(numbers_list)print(f"List time: {time.time() - start:.4f}s")
# Generator: Tạo từng phần tử khi cầnstart = time.time()numbers_gen = (x**2 for x in range(1000000))total = sum(numbers_gen)print(f"Generator time: {time.time() - start:.4f}s")
# Generator thường nhanh hơn và tiết kiệm memory hơnSử dụng một lần
Phần tiêu đề “Sử dụng một lần”# Generator chỉ dùng được 1 lầngen = (x for x in range(5))
# Lần 1: OKfor num in gen: print(num, end=" ") # 0 1 2 3 4print()
# Lần 2: Không in gì (đã hết)for num in gen: print(num, end=" ") # (không in gì)
# List có thể dùng nhiều lầnnumbers = [0, 1, 2, 3, 4]
for num in numbers: print(num, end=" ") # 0 1 2 3 4print()
for num in numbers: print(num, end=" ") # 0 1 2 3 4 (vẫn OK)4. Ví dụ Generator thực tế
Phần tiêu đề “4. Ví dụ Generator thực tế”Đọc file lớn từng dòng
Phần tiêu đề “Đọc file lớn từng dòng”def read_large_file(file_path): """Generator đọc file từng dòng""" with open(file_path, 'r') as file: for line in file: yield line.strip()
# Sử dụng# for line in read_large_file('huge_file.txt'):# process(line) # Xử lý từng dòng, không load hết vào memoryFibonacci sequence
Phần tiêu đề “Fibonacci sequence”def fibonacci(n): """Generator tạo n số Fibonacci đầu tiên""" a, b = 0, 1 count = 0
while count < n: yield a a, b = b, a + b count += 1
# Sử dụngfor fib in fibonacci(10): print(fib, end=" ") # 0 1 1 2 3 5 8 13 21 34print()
# Lấy 5 số đầufib_gen = fibonacci(5)print(list(fib_gen)) # [0, 1, 1, 2, 3]Fibonacci vô hạn
Phần tiêu đề “Fibonacci vô hạn”def fibonacci_infinite(): """Generator tạo chuỗi Fibonacci vô hạn""" a, b = 0, 1 while True: yield a a, b = b, a + b
# Lấy 10 số đầufib = fibonacci_infinite()for _ in range(10): print(next(fib), end=" ") # 0 1 1 2 3 5 8 13 21 34print()
# Hoặc dùng itertools.isliceimport itertoolsfib = fibonacci_infinite()first_10 = list(itertools.islice(fib, 10))print(first_10) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]Prime numbers generator
Phần tiêu đề “Prime numbers generator”def primes(): """Generator tạo số nguyên tố vô hạn""" def is_prime(n): if n < 2: return False for i in range(2, int(n**0.5) + 1): if n % i == 0: return False return True
n = 2 while True: if is_prime(n): yield n n += 1
# Lấy 10 số nguyên tố đầuprime_gen = primes()for _ in range(10): print(next(prime_gen), end=" ") # 2 3 5 7 11 13 17 19 23 29print()Chunking data
Phần tiêu đề “Chunking data”def chunk_data(data, chunk_size): """Chia data thành các chunks""" for i in range(0, len(data), chunk_size): yield data[i:i + chunk_size]
# Sử dụngdata = list(range(20))
for chunk in chunk_data(data, 5): print(chunk)
# [0, 1, 2, 3, 4]# [5, 6, 7, 8, 9]# [10, 11, 12, 13, 14]# [15, 16, 17, 18, 19]Pipeline pattern
Phần tiêu đề “Pipeline pattern”def read_numbers(file_path): """Đọc số từ file""" for line in open(file_path): yield int(line.strip())
def filter_even(numbers): """Lọc số chẵn""" for num in numbers: if num % 2 == 0: yield num
def square(numbers): """Bình phương""" for num in numbers: yield num ** 2
# Pipeline: đọc → lọc → bình phương# numbers = read_numbers('numbers.txt')# even_numbers = filter_even(numbers)# squared = square(even_numbers)## for result in squared:# print(result)5. Generator Methods
Phần tiêu đề “5. Generator Methods”send()
Phần tiêu đề “send()”def echo_generator(): """Generator nhận giá trị từ bên ngoài""" while True: received = yield print(f"Received: {received}")
gen = echo_generator()next(gen) # Khởi động generator
gen.send("Hello") # Received: Hellogen.send("World") # Received: Worldgen.send(123) # Received: 123send() với giá trị trả về
Phần tiêu đề “send() với giá trị trả về”def running_average(): """Tính trung bình động""" total = 0 count = 0
while True: value = yield total / count if count > 0 else 0 total += value count += 1
avg = running_average()next(avg) # Khởi động
print(avg.send(10)) # 10.0print(avg.send(20)) # 15.0print(avg.send(30)) # 20.0throw()
Phần tiêu đề “throw()”def error_handler(): """Generator xử lý exception""" while True: try: value = yield print(f"Received: {value}") except ValueError as e: print(f"ValueError caught: {e}")
gen = error_handler()next(gen)
gen.send(42) # Received: 42gen.throw(ValueError, "Invalid input") # ValueError caught: Invalid inputgen.send(100) # Received: 100close()
Phần tiêu đề “close()”def count_forever(): """Generator đếm vô hạn""" count = 0 try: while True: yield count count += 1 finally: print("Generator closed")
gen = count_forever()
print(next(gen)) # 0print(next(gen)) # 1print(next(gen)) # 2
gen.close() # Generator closed# print(next(gen)) # StopIteration6. yield from (Python 3.3+)
Phần tiêu đề “6. yield from (Python 3.3+)”Delegate to sub-generator
Phần tiêu đề “Delegate to sub-generator”def generator_1(): yield 1 yield 2
def generator_2(): yield 3 yield 4
# Cách cũ: Dùng for loopdef combined_old(): for value in generator_1(): yield value for value in generator_2(): yield value
# Cách mới: Dùng yield fromdef combined_new(): yield from generator_1() yield from generator_2()
# Sử dụngfor num in combined_new(): print(num, end=" ") # 1 2 3 4print()Flatten nested list
Phần tiêu đề “Flatten nested list”def flatten(nested_list): """Làm phẳng list lồng nhau""" for item in nested_list: if isinstance(item, list): yield from flatten(item) # Đệ quy else: yield item
# Sử dụngnested = [1, [2, 3, [4, 5]], 6, [7, [8, 9]]]flat = list(flatten(nested))print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]7. itertools - Module làm việc với Iterators
Phần tiêu đề “7. itertools - Module làm việc với Iterators”itertools.count()
Phần tiêu đề “itertools.count()”import itertools
# Đếm vô hạn từ startfor i in itertools.count(start=10, step=2): if i > 20: break print(i, end=" ") # 10 12 14 16 18 20print()itertools.cycle()
Phần tiêu đề “itertools.cycle()”import itertools
# Lặp vô hạncolors = ['red', 'green', 'blue']color_cycle = itertools.cycle(colors)
for _ in range(7): print(next(color_cycle), end=" ")# red green blue red green blue redprint()itertools.repeat()
Phần tiêu đề “itertools.repeat()”import itertools
# Lặp lại giá trịfor value in itertools.repeat('Hello', 3): print(value) # Hello (3 lần)itertools.chain()
Phần tiêu đề “itertools.chain()”import itertools
# Nối các iterableslist1 = [1, 2, 3]list2 = [4, 5, 6]list3 = [7, 8, 9]
chained = itertools.chain(list1, list2, list3)print(list(chained)) # [1, 2, 3, 4, 5, 6, 7, 8, 9]itertools.islice()
Phần tiêu đề “itertools.islice()”import itertools
# Cắt iteratornumbers = range(100)first_10 = itertools.islice(numbers, 10)print(list(first_10)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Từ vị trí 5 đến 15numbers = range(100)slice_5_15 = itertools.islice(numbers, 5, 15)print(list(slice_5_15)) # [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]itertools.takewhile() và dropwhile()
Phần tiêu đề “itertools.takewhile() và dropwhile()”import itertools
numbers = [1, 2, 3, 4, 5, 1, 2, 3]
# Lấy phần tử cho đến khi điều kiện sairesult = itertools.takewhile(lambda x: x < 4, numbers)print(list(result)) # [1, 2, 3]
# Bỏ phần tử cho đến khi điều kiện sairesult = itertools.dropwhile(lambda x: x < 4, numbers)print(list(result)) # [4, 5, 1, 2, 3]8. Best Practices
Phần tiêu đề “8. Best Practices”✅ Khi nào dùng Generator?
Phần tiêu đề “✅ Khi nào dùng Generator?”# 1. Dữ liệu lớn, không cần load hết vào memorydef process_large_file(file_path): with open(file_path) as f: for line in f: # Generator đọc từng dòng yield process_line(line)
# 2. Chuỗi vô hạndef fibonacci_infinite(): a, b = 0, 1 while True: yield a a, b = b, a + b
# 3. Pipeline xử lý dữ liệudef pipeline(): data = read_data() filtered = filter_data(data) transformed = transform_data(filtered) return transformed
# 4. Lazy evaluationdef squares(): for i in range(1000000): yield i ** 2 # Chỉ tính khi cần❌ Khi nào KHÔNG dùng Generator?
Phần tiêu đề “❌ Khi nào KHÔNG dùng Generator?”# 1. Cần truy cập ngẫu nhiên (random access)# ❌ Generator không hỗ trợ indexinggen = (x for x in range(10))# value = gen[5] # TypeError
# ✅ Dùng listnumbers = [x for x in range(10)]value = numbers[5] # OK
# 2. Cần dùng nhiều lần# ❌ Generator chỉ dùng 1 lầngen = (x for x in range(5))list(gen) # [0, 1, 2, 3, 4]list(gen) # [] (rỗng!)
# ✅ Dùng listnumbers = [x for x in range(5)]list(numbers) # [0, 1, 2, 3, 4]list(numbers) # [0, 1, 2, 3, 4] (vẫn OK)
# 3. Cần biết độ dài# ❌ Generator không có len()gen = (x for x in range(10))# length = len(gen) # TypeError
# ✅ Dùng listnumbers = [x for x in range(10)]length = len(numbers) # 10Tổng kết
Phần tiêu đề “Tổng kết”- Iterator: Object implement
__iter__()và__next__() - Generator: Cách đơn giản tạo iterator bằng
yield - yield: Tạm dừng hàm, giữ trạng thái, trả về giá trị
- Generator expression:
(x for x in iterable)- giống list comprehension nhưng lazy - Lợi ích:
- Tiết kiệm bộ nhớ (không load hết vào memory)
- Lazy evaluation (tính khi cần)
- Hỗ trợ dữ liệu vô hạn
- Hạn chế:
- Chỉ dùng 1 lần
- Không hỗ trợ indexing
- Không có len()
- Generator methods: send(), throw(), close()
- yield from: Delegate to sub-generator
- itertools: Module với nhiều công cụ làm việc với iterators