Bộ sinh (generator) trong Python
Bài viết này giải thích về bộ sinh (generator) trong Python.
YouTube Video
Bộ sinh (generator) trong Python
Tổng quan
Generators trong Python là một loại iterator và là tính năng mạnh mẽ để xử lý lặp lại một cách hiệu quả. Chúng cho phép bạn viết mã tiết kiệm bộ nhớ khi xử lý lượng dữ liệu lớn.
Bộ sinh (Generator) là gì?
Một bộ sinh trong Python là một hàm đặc biệt, tạo ra từng giá trị một, được định nghĩa bằng từ khóa yield
. Đặc điểm của nó là tạm dừng thực thi nhưng vẫn giữ nguyên trạng thái và có thể tiếp tục sau đó.
Cơ bản về yield
yield
là một từ khóa trả về một giá trị và tạm dừng thực thi hàm cùng lúc.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8print(next(gen)) # 1
9print(next(gen)) # 2
10print(next(gen)) # 3
- Khi được gọi, hàm này trả về một đối tượng generator, tạo ra các giá trị lần lượt.
- Nếu bạn gọi
next()
khi không còn giá trị tiếp theo, lỗiStopIteration
sẽ xảy ra.
next()
và StopIteration
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8try:
9 while True:
10 value = next(gen)
11 print(value)
12except StopIteration:
13 print("Finished")
- Bằng cách xử lý rõ ràng lỗi
StopIteration
như thế này, bạn có thể phát hiện khi generator đã kết thúc.
send(value)
Gọi send(value)
tiếp tục generator và gửi value
đến vị trí của biểu thức yield
. Giá trị được gửi có thể được nhận ở phía generator như là giá trị trả về của biểu thức yield
. Ở lần gọi đầu tiên, bạn không thể gửi bất kỳ giá trị nào ngoài None
với send(value)
, vì vậy bạn phải sử dụng next()
hoặc send(None)
.
1def gen():
2 x = yield 1
3 print(f"x = {x}")
4 y = yield 2
5 print(f"y = {y}")
6
7g = gen()
8print(next(g)) # -> 1 (value from yield 1)
9print(g.send(10)) # -> x = 10, 2 (value from yield 2)
10print(g.send(20)) # -> y = 20, StopIteration occurs
- Với
send(10)
,yield
trong generator trở thành biểu thức trả về 10, và 10 được gán chox
.
throw()
Gọi throw
sẽ tiếp tục generator và ném ra một ngoại lệ tại vị trí yield
đang tạm dừng. Bạn có thể xử lý ngoại lệ bên trong generator để tiếp tục xử lý. Nếu ngoại lệ không được bắt, nó sẽ lan ra ngoài và generator sẽ kết thúc.
1def gen():
2 try:
3 yield 1
4 except ValueError as e:
5 print(f"Caught: {e}")
6 yield "recovered"
7
8g = gen()
9print(next(g)) # -> 1
10print(g.throw(ValueError("boom"))) # -> Caught: boom, "recovered"
- Trong đoạn mã này,
throw
được gọi để đưa một ngoại lệ vào generator. Ở phía generator, ngoại lệ được xử lý vàrecovered
được trả về.
close()
Gọi close()
sẽ kết thúc generator. Bên trong generator, bạn có thể thực hiện dọn dẹp bằng cách sử dụng finally
. Gọi next()
hoặc send()
sau khi gọi close()
sẽ sinh ra lỗi StopIteration
.
1def gen():
2 try:
3 yield 1
4 finally:
5 print("Cleaning up...")
6
7g = gen()
8print(next(g)) # -> 1
9g.close() # -> Cleaning up...
- Đoạn mã này cho thấy khi gọi
close()
sẽ kết thúc generator và kích hoạt quá trình dọn dẹp trongfinally
.
yield from
yield from
là cú pháp được sử dụng để ủy quyền cho trình sinh phụ. Đây là một cách đơn giản để gọi một trình sinh khác bên trong một trình sinh và truyền tất cả các giá trị của nó ra phạm vi bên ngoài.
1def sub_gen():
2 yield 1
3 yield 2
4
5def main_gen():
6 yield from sub_gen()
7 yield 3
8
9print(list(main_gen())) # -> [1, 2, 3]
- Đoạn mã này ủy quyền tất cả các giá trị từ trình sinh phụ sang trình sinh bên ngoài bằng cách sử dụng
yield from
, sau đó trả về giá trị3
.
Mối liên hệ với Iterator
Generators cài đặt nội bộ __iter__()
và __next__()
, vì vậy chúng là một loại iterator. Do đó, chúng hoàn toàn tương thích với các thao tác lặp như vòng lặp for
.
Tích hợp với vòng lặp for
Trong Python, vòng lặp for
sử dụng next()
bên trong để tự động lấy giá trị.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6for value in simple_generator():
7 print(value)
Với cách này, việc xử lý StopIteration
cũng được thực hiện tự động.
Tạo generator vô hạn
1def count_up(start=0):
2 while True:
3 yield start
4 start += 1
5
6counter = count_up()
7print(next(counter)) # 0
8print(next(counter)) # 1
Có thể tạo ra vòng lặp vô hạn bằng generator, nhưng cần thận trọng khi sử dụng.
Biểu thức generator
Biểu thức generator, được viết với dấu ngoặc tròn, cho phép bạn định nghĩa generator với cú pháp giống như list comprehension.
1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8 print(square)
Khác với list comprehension, chúng không nạp tất cả phần tử vào bộ nhớ cùng lúc, giúp tiết kiệm bộ nhớ hơn.
Xử lý lỗi trong generator
Ngoại lệ có thể xảy ra bên trong một generator. Trong những trường hợp như vậy, bạn sử dụng try-except
giống như trong mã Python thông thường.
1def safe_divide_generator(numbers, divisor):
2 """Yields results of dividing numbers by a given divisor safely."""
3 for number in numbers:
4 try:
5 yield number / divisor # Attempt to divide and yield result.
6 except ZeroDivisionError:
7 yield float('inf') # Return infinity if division by zero occurs.
8
9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0) # Create generator with divisor as 0.
12for value in gen:
13 print(value) # Output: inf, inf, inf
Trong ví dụ này, việc xử lý lỗi được thực hiện thích hợp khi xảy ra phép chia cho 0.
Stack trace của generator
Nếu có một ngoại lệ xảy ra bên trong generator, nó sẽ được ném ra khi generator được tiếp tục thực thi.
1def error_generator():
2 """A generator that yields values and raises an error."""
3 yield 1
4 raise ValueError("An error occurred") # Raise a ValueError intentionally.
5 yield 2
6
7gen = error_generator()
8print(next(gen)) # Output: 1 (first value yielded)
9try:
10 print(next(gen)) # Attempt to get the next value, which raises an error
11except ValueError as e:
12 print(e) # Output: An error occurred (exception message is printed)
- Generator này trả về
1
đầu tiên. Lỗi phát sinh khi tiếp tục được bắt và hiển thị dưới dạng thông báo lỗi.
Ví dụ sử dụng generator
Đọc file từng dòng một (phù hợp cho các file lớn)
1def read_large_file(filepath):
2 with open(filepath, 'r') as f:
3 for line in f:
4 yield line.strip()
- Hàm này đọc từng dòng của tệp văn bản bằng iterator, loại bỏ khoảng trắng ở mỗi dòng và trả về dưới dạng generator, cho phép xử lý các tệp lớn với mức sử dụng bộ nhớ thấp.
Generator cho dãy Fibonacci
1def fibonacci(limit):
2 a, b = 0, 1
3 while a < limit:
4 yield a
5 a, b = b, a + b
6
7for n in fibonacci(100):
8 print(n)
- Đoạn mã này sử dụng một generator để tạo tuần tự các số Fibonacci nhỏ hơn giới hạn trên và xuất chúng bằng vòng lặp
for
.
Trường hợp sử dụng
Generator cũng có thể được sử dụng trong các trường hợp sau.
- Xử lý tuần tự các file CSV hoặc log lớn
- Phân trang API
- Xử lý dữ liệu streaming (ví dụ: Kafka, thiết bị IoT)
Tóm tắt
Khái niệm | Điểm chính |
---|---|
yield |
Tạm dừng và trả về một giá trị |
Hàm generator | Một hàm chứa yield và trả về một iterator khi được gọi |
Ưu điểm | Tiết kiệm bộ nhớ và lý tưởng cho việc xử lý tập dữ liệu lớn |
Biểu thức generator | Cho phép cú pháp ngắn gọn như (x for x in iterable) |
Bằng cách sử dụng generator, bạn có thể xử lý hiệu quả tập dữ liệu lớn, tiết kiệm bộ nhớ và giữ cho mã nguồn ngắn gọn.
Bạn có thể làm theo bài viết trên bằng cách sử dụng Visual Studio Code trên kênh YouTube của chúng tôi. Vui lòng ghé thăm kênh YouTube.