3.1 Làm quen với Generators
Generators là hàm mà trả về đối tượng iterator. Các iterator này tạo ra giá trị khi được yêu cầu, điều này cho phép xử lý các tập dữ liệu có thể lớn mà không cần tải hoàn toàn vào bộ nhớ.
Có vài cách để tạo Generators, dưới đây sẽ là một số cách phổ biến nhất.
Generators dựa trên hàm
Generators được tạo ra với từ khóa yield trong hàm. Khi hàm với yield được gọi, nó trả về một đối tượng generator nhưng không thực thi mã bên trong hàm ngay lập tức. Thay vào đó, quá trình thực thi sẽ bị tạm dừng tại biểu thức yield và tiếp tục mỗi khi phương thức __next__() của đối tượng generator được gọi.
def count_up_to(max):
count = 1
while count <= max:
yield count
count += 1
counter = count_up_to(5)
print(next(counter)) # Kết quả: 1
print(next(counter)) # Kết quả: 2
print(next(counter)) # Kết quả: 3
print(next(counter)) # Kết quả: 4
print(next(counter)) # Kết quả: 5
Nếu trong hàm có lệnh yield, thì Python thay vì thực thi hàm theo cách truyền thống sẽ tạo ra một đối tượng generator, quản lý trạng thái thực thi của hàm.
Generator Expressions
Generator expressions giống với List Comprehension, nhưng được tạo ra bằng dấu ngoặc tròn thay vì dấu ngoặc vuông. Chúng cũng trả về đối tượng generator.
squares = (x ** 2 for x in range(10))
print(next(squares)) # Kết quả: 0
print(next(squares)) # Kết quả: 1
print(next(squares)) # Kết quả: 4
Cách nào bạn thấy hay hơn?
3.2 Lợi ích của Generators
Sử dụng bộ nhớ hiệu quả
Generators tính toán giá trị theo nhu cầu, điều này cho phép xử lý dữ liệu lớn mà không cần tải hoàn toàn vào bộ nhớ. Điều này làm cho Generators là lựa chọn lý tưởng để làm việc với các tập dữ liệu lớn hoặc dòng dữ liệu.
def large_range(n):
for i in range(n):
yield i
for value in large_range(1000000):
# Xử lý giá trị từng cái một
print(value)
Tính toán lười biếng
Generators thực hiện tính toán lười biếng, nghĩa là chúng chỉ tính toán khi cần thiết. Điều này tránh việc tính toán không cần thiết và cải thiện hiệu suất.
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib))
Dễ sử dụng cú pháp
Generators cung cấp cú pháp thuận tiện để tạo iterator, điều này giúp đơn giản hóa việc viết và đọc code.
3.3 Sử dụng Generators
Ví dụ sử dụng Generators trong thư viện tiêu chuẩn
Nhiều hàm trong thư viện Python tiêu chuẩn sử dụng Generators. Ví dụ, hàm range() trả về một đối tượng generator, tạo ra một dãy số.
for i in range(10):
print(i)
Ừ thì, thế giới không bao giờ sẽ như cũ nữa.
Tạo chuỗi vô hạn
Generators cho phép tạo chuỗi vô hạn, điều này hữu ích trong các kịch bản khác nhau, như tạo dòng dữ liệu vô hạn.
def natural_numbers():
n = 1
while True:
yield n
n += 1
naturals = natural_numbers()
for _ in range(10):
print(next(naturals))
Sử dụng send() và close()
Đối tượng generator hỗ trợ phương thức send() và close(), giúp gửi giá trị trở lại generator và kết thúc thực hiện của nó.
def echo():
while True:
received = yield
print(received)
e = echo()
next(e) # Khởi chạy generator
e.send("Hello, world!") # Kết quả: Hello, world!
e.close()
3.4 Generators trong thực tế
Generators và xử lý ngoại lệ
Generators có thể xử lý ngoại lệ, làm cho chúng trở thành công cụ mạnh để viết mã bền vững hơn.
def controlled_execution():
try:
yield "Start"
yield "Working"
except GeneratorExit:
print("Generator closed")
gen = controlled_execution()
print(next(gen)) # Kết quả: Start
print(next(gen)) # Kết quả: Working
gen.close() # Kết quả: Generator closed
Chúng ta sẽ tìm hiểu thêm về xử lý ngoại lệ trong các bài giảng sau, nhưng mình nghĩ bạn sẽ thấy hữu ích khi biết rằng generators hoạt động tốt với chúng.
Generators lồng nhau
Generators có thể lồng nhau, cho phép tạo ra cấu trúc lặp phức tạp.
def generator1():
yield from range(3)
yield from "ABC"
for value in generator1():
print(value)
# Kết quả
0
1
2
A
B
C
Giải thích:
yield from: Cấu trúc này được dùng để ủy thác một phần hoạt động cho một generator khác, điều này giúp đơn giản hóa mã và cải thiện độ rõ ràng.
Generators và hiệu năng
Sử dụng Generators có thể cải thiện đáng kể hiệu suất chương trình nhờ giảm thiểu sử dụng bộ nhớ và thực thi các vòng lặp hiệu quả hơn.
Ví dụ so sánh danh sách và Generators
import time
import sys
def memory_usage(obj):
return sys.getsizeof(obj)
n = 10_000_000
# Sử dụng danh sách
start_time = time.time()
list_comp = [x ** 2 for x in range(n)]
list_time = time.time() - start_time
list_memory = memory_usage(list_comp)
# Sử dụng generator
start_time = time.time()
gen_comp = (x ** 2 for x in range(n))
gen_result = sum(gen_comp) # Tính tổng để so sánh kết quả
gen_time = time.time() - start_time
gen_memory = memory_usage(gen_comp)
print(f"Danh sách:")
print(f" Thời gian: {list_time:.2f} giây")
print(f" Bộ nhớ: {list_memory:,} byte")
print(f"\nGenerator:")
print(f" Thời gian: {gen_time:.2f} giây")
print(f" Bộ nhớ: {gen_memory:,} byte")
Danh sách:
Thời gian: 0.62 giây
Bộ nhớ: 89,095,160 byte
Generator:
Thời gian: 1.13 giây
Bộ nhớ: 200 byte
GO TO FULL VERSION