Không phải lúc nào hàm Python cũng có câu lệnh return. Generator function là những hàm sử dụng từ khóa yield thay cho return.
Những hàm này tạo ra generator iterator, là các đối tượng biểu diễn một luồng dữ liệu. Các phần tử do iterator biểu diễn chỉ được tạo và yield khi cần. Cách đánh giá này thường được gọi là đánh giá lười (lazy evaluation).
Khi xử lý các bộ dữ liệu lớn, generator mang lại lựa chọn tiết kiệm bộ nhớ so với việc lưu dữ liệu trong list, tuple và các cấu trúc dữ liệu khác vốn cần không gian bộ nhớ cho từng phần tử. Generator function cũng có thể tạo ra iterator vô hạn, điều không thể với các cấu trúc được đánh giá ngay như list và tuple.
Trước khi bắt đầu, hãy điểm lại sự khác nhau giữa hàm thông thường và generator:
Tính năng
Hàm
Generator
Sinh giá trị
Trả về tất cả giá trị cùng lúc
Yield từng giá trị một, theo yêu cầu
Thực thi
Thực thi toàn bộ trước khi trả về
Tạm dừng sau khi yield, tiếp tục khi giá trị tiếp theo được yêu cầu
Từ khóa
return
yield
Sử dụng bộ nhớ
Có thể cao, lưu toàn bộ dãy trong bộ nhớ
Thấp, chỉ lưu giá trị hiện tại và trạng thái cho lần kế tiếp
Lặp
Có thể lặp nhiều lần, nhưng cần lưu toàn bộ dãy
Thiết kế cho một lượt lặp, hiệu quả hơn với dãy lớn hoặc vô hạn
Sử dụng yield của Python để tạo Generator Function
Thuật ngữ generator trong Python có thể chỉ một generator iterator hoặc một generator function. Đây là những đối tượng khác nhau nhưng có liên quan trong Python. Trong hướng dẫn này, chúng tôi sẽ thường dùng đầy đủ thuật ngữ để tránh nhầm lẫn.
Hãy khám phá generator function trước. Một generator function trông giống hàm thông thường nhưng chứa từ khóa yield thay cho return.
Khi chương trình Python gọi một generator function, nó tạo ra một generator iterator. Iterator yield giá trị theo yêu cầu và tạm dừng thực thi cho đến khi cần giá trị khác. Hãy xem ví dụ để giải thích khái niệm này và minh họa sự khác biệt giữa hàm thường và generator function.
Dùng hàm thông thường
Trước hết, hãy định nghĩa một hàm thông thường có câu lệnh return. Hàm này nhận một dãy từ và một chữ cái, trả về một list chứa số lần xuất hiện của chữ cái trong từng từ:
deffind_letter_occurrences(words, letter):
output =[]for word in words:
output.append(word.count(letter))return output
print(
find_letter_occurrences(["apple","banana","cherry"],"a"))Được hỗ trợ bởi
Hàm trả ra một list chứa 1, 3 và 0 vì có một chữ a trong apple, ba lần a trong banana và không có trong cherry. Cùng hàm này có thể được viết lại dùng list comprehension thay vì khởi tạo list rỗng và dùng.append():
deffind_letter_occurrences(words, letter):return[word.count(letter)for word in words]Được hỗ trợ bởi
Hàm thông thường này trả về một list chứa toàn bộ kết quả mỗi lần được gọi. Tuy nhiên, nếu list từ rất lớn, việc gọi hàm sẽ đòi hỏi bộ nhớ vì chương trình tạo và lưu một list mới có kích thước bằng list gốc. Nếu hàm này được dùng lặp lại cho nhiều đối số đầu vào, hoặc các hàm tương tự thực hiện thao tác khác trên dữ liệu gốc, áp lực lên bộ nhớ có thể tăng nhanh.
Dùng generator function
Có thể dùng generator function thay thế:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)print(output)Được hỗ trợ bởi
<generator object find_letter_occurrences at 0x102935e00>Được hỗ trợ bởi
Hàm này dùng yield keyword thay cho return. Generator function này trả về một đối tượng generator khi được gọi, gán vào output. Đối tượng này là một iterator. Nó không chứa sẵn dữ liệu về số lần xuất hiện của chữ cái trong từng từ. Thay vào đó, generator sẽ tạo và yield các giá trị khi cần. Hãy lấy giá trị đầu tiên từ generator iterator này:
Hàm dựng sẵn next() là một cách để lấy giá trị kế tiếp từ iterator. Chúng ta sẽ xem các cách khác sau trong hướng dẫn này.
Mã trong generator function được thực thi cho đến khi chương trình gặp dòng có từ khóa yield. Trong ví dụ này, vòng lặp for bắt đầu lượt lặp đầu tiên và lấy phần tử đầu trong list words. Phương thức chuỗi .count() trả về một số nguyên, là 1 trong trường hợp này, vì có một chữ a trong apple. Generator yield giá trị này, và next(output) trả về nó.
Generator output tạm dừng thực thi tại đây. Như vậy, generator đã hoàn thành lượt lặp đầu tiên của vòng for và tìm số lần xuất hiện của chữ a trong từ đầu tiên của list. Nó đang đợi cho đến khi được cần tiếp.
Nếu gọi hàm dựng sẵn next() lần nữa với đối số là output, generator sẽ tiếp tục thực thi từ điểm tạm dừng:
Generator tiếp tục từ dòng có yield trong lượt lặp đầu tiên của vòng for. Vì vòng for không còn dòng mã nào khác, nó quay lên đầu vòng lặp và lấy phần tử thứ hai từ list words. Giá trị trả về của .count() là 3 trong trường hợp này, và giá trị này được yield. Generator lại tạm dừng tại điểm thực thi này.
Lượt lặp thứ hai đi đến cuối vòng for và chuyển sang lượt lặp thứ ba. Mã lại tiến tới dòng có yield, lần này yield số nguyên 0 vì không có chữ a nào trong cherry.
Generator lại tạm dừng. Chương trình chỉ quyết định số phận của generator khi chúng ta gọi next() lần thứ tư:
Traceback (most recent call last):
...
StopIterationĐược hỗ trợ bởi
Việc thực thi tiếp tục từ cuối lượt lặp thứ ba của vòng for. Tuy nhiên, vòng lặp đã hết phần tử vì không còn phần tử nào trong list words. Generator phát sinh ngoại lệ StopIteration.
Trong hầu hết trường hợp, các phần tử của generator không được truy cập trực tiếp bằng next() mà thông qua một quy trình lặp khác. Ngoại lệ StopIteration báo hiệu kết thúc quá trình lặp. Chúng ta sẽ khám phá thêm ở phần tiếp theo của hướng dẫn.
Python có cách khác để tạo generator iterator khi hoạt động của chúng có thể biểu diễn bằng một biểu thức đơn, như ví dụ trước. Generator iterator output có thể được tạo bằng generator expression:
words =["apple","banana","cherry"]
letter ="a"
output =(word.count(letter)for word in words)print(next(output))print(next(output))print(next(output))print(next(output))Được hỗ trợ bởi
Biểu thức trong ngoặc đơn gán cho output là một generator expression, tạo ra generator iterator tương tự cái được tạo bởi generator function find_letter_occurrences().
Hãy kết thúc phần này bằng một ví dụ khác về generator function để làm nổi bật cách thực thi tạm dừng và tiếp tục mỗi khi cần phần tử:
defshow_status():print("Start")yieldprint("Middle")yieldprint("End")yield
status = show_status()next(status)Được hỗ trợ bởi
Generator function này không có vòng lặp. Thay vào đó, nó có ba dòng chứa từ khóa yield. Mã tạo một generator iterator status khi gọi generator function show_status(). Lần đầu chương trình gọi next(status), generator bắt đầu thực thi. Nó in chuỗi "Start" và tạm dừng sau biểu thức yield đầu tiên. Generator yield None vì không có đối tượng theo sau từ khóa yield.
Chương trình chỉ in chuỗi "Middle" khi next() được gọi lần thứ hai:
Generator tạm dừng tại biểu thức yield cuối cùng. Nó sẽ phát sinh ngoại lệ StopIteration vào lần tiếp theo chương trình yêu cầu một giá trị từ generator iterator này:
Traceback (most recent call last):
...
StopIterationĐược hỗ trợ bởi
Chúng ta sẽ khám phá thêm các cách dùng generator ở phần tiếp theo.
Làm việc với Generator Iterator
Generator function tạo ra generator iterator, và iterator là iterable. Hãy bóc tách câu này. Mỗi lần chương trình gọi một generator function, nó tạo ra một iterator. Vì iterator là iterable, chúng có thể được dùng trong vòng lặp for và các quy trình lặp khác.
Do đó, hàm dựng sẵn next() không phải là cách duy nhất để truy cập phần tử trong iterator. Phần này khám phá những cách khác để làm việc với generator.
Dùng giao thức lặp của Python với generator iterator
Hãy quay lại một generator function từ phần trước trong hướng dẫn này:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)for value in output:print(value)Được hỗ trợ bởi
Thay vì dùng next() nhiều lần, phiên bản này dùng generator iterator output trong một vòng for. Vì iterator là iterable, chúng có thể dùng trong vòng for. Vòng lặp lấy các phần tử từ generator iterator cho đến khi không còn giá trị.
Khác với các cấu trúc dữ liệu như list và tuple, một iterator chỉ có thể dùng một lần. Mã sẽ không in lại các giá trị nếu chúng ta chạy cùng vòng for lần thứ hai:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)print("First attempt:")for value in output:print(value)print("Second attempt:")for value in output:print(value)Được hỗ trợ bởi
Iterator đã bị tiêu thụ bởi vòng for đầu tiên, nên không thể yield giá trị nữa. Nếu cần generator sau khi nó bị tiêu thụ, chúng ta phải tạo một generator iterator khác từ generator function.
Cũng có thể có nhiều generator iterator cùng tồn tại đồng thời trong một chương trình:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
first_output = find_letter_occurrences(words, letter)
second_output = find_letter_occurrences(words, letter)print("First value of first_output:")print(next(first_output))print("Values of second_output:")for value in second_output:print(value)print("Remaining values of first_output:")for value in first_output:print(value)Được hỗ trợ bởi
First value of first_output:
1
Values of second_output:
1
3
0
Remaining values of first_output:
3
0Được hỗ trợ bởi
Generator function find_letter_occurrences() tạo hai generator iterator: first_output và second_output. Dù cả hai iterator tham chiếu cùng dữ liệu trong list words, chúng tiến triển độc lập với nhau.
Ví dụ này lấy giá trị đầu từ first_output bằng next(). Generator iterator yield 1 và tạm dừng. Chương trình sau đó lặp qua second_output. Vì generator này chưa yield giá trị nào, vòng lặp đi qua toàn bộ các giá trị mà iterator thứ hai yield. Cuối cùng, có một vòng for khác lặp qua first_output. Tuy nhiên, iterator này đã yield giá trị đầu tiên trước đó. Vòng lặp đi qua các giá trị còn lại trong iterator này.
Vòng for không phải quy trình duy nhất có thể dùng để lặp qua generator iterator:
print(*find_letter_occurrences(words, letter))print(sorted(find_letter_occurrences(words, letter)))Được hỗ trợ bởi
Trong các ví dụ này, chương trình gọi trực tiếp generator function để tạo và dùng generator iterator thay vì gán vào biến. Ở ví dụ đầu, iterator được mở (unpack) bằng ký hiệu dấu sao. Quy trình này dựa trên cùng giao thức lặp như vòng for.
Ở ví dụ thứ hai, generator iterator được truyền vào hàm dựng sẵn sorted(), hàm này yêu cầu một đối số iterable. Generator là iterable, vì vậy có thể dùng bất cứ khi nào quá trình lặp của Python diễn ra.
Tạo iterator vô hạn
Một generator yield một giá trị và tạm dừng cho đến khi cần giá trị tiếp theo. Mỗi lần mã yêu cầu giá trị từ iterator, mã bên trong generator function sẽ thực thi cho đến khi biểu thức yield tiếp theo được đánh giá. Trong các ví dụ đến giờ, generator function có số lần yield hữu hạn. Tuy nhiên, có thể tạo một generator yield vô hạn số giá trị bằng cách dùng vòng while trong generator function. Trong ví dụ sau, generator yield một màu ngẫu nhiên từ list màu được truyền vào generator function:
import random
defget_color(colors):whileTrue:yield random.choice(colors)
output_colors = get_color(["red","green","blue"])print("First two colors:")print(next(output_colors))print(next(output_colors))print("Next 10 colors using a 'for' loop:")for _ inrange(10):print(next(output_colors))Được hỗ trợ bởi
First two colors:
green
red
Next 10 colors using a 'for' loop:
blue
green
green
green
red
red
red
blue
green
redĐược hỗ trợ bởi
Generator function get_color() có một biểu thức yield bên trong vòng while. Vì vậy, mã luôn gặp một biểu thức yield khác khi tìm giá trị tiếp theo. Generator iterator output_colors yield vô hạn màu sắc được chọn ngẫu nhiên từ list đầu vào. Generator này sẽ không bao giờ bị tiêu thụ hết.
Không thể tạo các cấu trúc dữ liệu vô hạn như list và tuple. Generator cho phép chương trình tạo các iterable vô hạn. Lưu ý rằng nếu dùng generator iterator trực tiếp trong vòng for, vòng lặp sẽ chạy mãi.
Các khái niệm nâng cao về Generator
Generator có nhiều trường hợp sử dụng nâng cao trong Python. Phần này sẽ khám phá một số trong đó.
Gửi một đối tượng vào generator
Generator cũng có thể nhận dữ liệu bổ sung để dùng khi đánh giá mã. Câu lệnh chứa từ khóa yield là một biểu thức đánh giá thành một giá trị. Giá trị này có thể được gán cho một biến trong generator function. Hãy bắt đầu với ví dụ cơ bản để minh họa khái niệm này:
defgenerator_function():
value =yield1print(f"The yield expression evaluates to: {value}")
value =yield2print(f"The yield expression evaluates to: {value}")
output = generator_function()print(next(output))print(next(output))print(next(output))Được hỗ trợ bởi
1
The yield expression evaluates to: None
2
The yield expression evaluates to: None
Traceback (most recent call last):
...
StopIterationĐược hỗ trợ bởi
Từ khóa yield trong Python tạo ra một biểu thức đánh giá thành giá trị. Tuy nhiên, giá trị của biểu thức này bên trong generator function không phải là đối tượng được generator yield. Xét biểu thức yield đầu tiên. Generator yield số nguyên 1. Do đó, print(next(output)) hiển thị 1 ở lần gọi đầu và tạm dừng thực thi generator.
Tuy nhiên, biểu thức yield trong generator đánh giá thành một đối tượng, được gán cho biến value. Trong ví dụ này, yield gán None cho value. Quy trình này lặp lại cho lần xuất hiện thứ hai của yield trong generator function. Mục đích của lần gọi next() thứ ba là đảm bảo toàn bộ mã trong generator function được thực thi.
Hãy thay thế lần gọi thứ hai và thứ ba tới next() bằng.send(), một phương thức của lớp generator:
defgenerator_function():
value =yield1print(f"The yield expression evaluates to: {value}")
value =yield2print(f"The yield expression evaluates to: {value}")
output = generator_function()print(next(output))print(output.send("Here's a value"))print(output.send("Here's another value"))Được hỗ trợ bởi
1
The yield expression evaluates to: Here's a value
2
The yield expression evaluates to: Here's another value
Traceback (most recent call last):
...
StopIterationĐược hỗ trợ bởi
Generator function không thay đổi. Generator được khởi động bằng cách gọi next(), và mã chạy cho đến khi yield số nguyên đầu tiên, 1. Thay vì dùng next() lần hai, chương trình gọi output.send(). Phương thức này gửi một đối tượng vào generator. Trong ví dụ này, đối tượng là một chuỗi. Biểu thức yield trong generator function đánh giá thành chuỗi này và gán cho value. Vì vậy, generator có thể dùng chuỗi trong mã của nó.
Lần gọi thứ hai tới .send() gửi một đối tượng mới vào generator, cũng được gán cho biến value. Generator phát sinh StopIteration sau lời gọi print() cuối vì không còn biểu thức yield nào nữa.
Hãy xem ví dụ khác dùng .send(). Generator sau đây hiển thị số dư trong tài khoản, nhưng số dư có thể được cập nhật:
defget_balance(start_balance):
balance = start_balance
whileTrue:
amount =yield balance
if amount isnotNone:
balance += amount
current_balance = get_balance(100)print(next(current_balance))print(current_balance.send(10))print(current_balance.send(-20))print(next(current_balance))Được hỗ trợ bởi
Generator function yêu cầu số dư ban đầu khi được gọi. Giá trị của balance có thể thay đổi khi generator thực thi mã. Bất kỳ đối tượng nào gửi vào generator bằng .send() sẽ được gán cho amount. Biến này sẽ là None nếu generator yield một giá trị mà không có đối tượng nào được gửi vào, hoặc sẽ chứa đối tượng được gửi bằng .send().
Generator iterator current_balance bắt đầu với số dư $100. Generator được khởi động bằng cách gọi next(), bắt đầu thực thi mã cho đến khi yield giá trị đầu tiên.
Khi generator đã khởi động, có thể tiếp tục thực thi bằng .send() thay vì next(). Generator cộng giá trị được gửi vào số dư. Nếu không gửi giá trị, như khi gọi next() lần nữa, generator yield số dư không đổi.
Yield trực tiếp từ một iterable khác
Generator của Python cũng có thể yield giá trị trực tiếp từ một generator hoặc iterable khác bằng cú pháp yield from. Hãy xem ví dụ về một generator function yield các giá trị từ một list lồng nhau:
defflatten(nested_list):for item in nested_list:ifisinstance(item,list):yieldfrom flatten(item)else:yield item
nested_list =[1,[2,3],[4,[5,6]],7]print(list(flatten(nested_list)))Được hỗ trợ bởi
Generator function nhận một list, có thể bao gồm các list lồng nhau. Vòng lặp for lặp qua các phần tử trong list. Mỗi phần tử trong list ngoài cùng hoặc là một giá trị, ở đây là số nguyên, hoặc là một list khác. Khi phần tử không phải list, generator yield phần tử đó.
Tuy nhiên, khi phần tử là một list, generator đệ quy gọi lại generator function flatten() với list bên trong làm đối số. Việc này tạo một generator iterator khác, dùng list bên trong làm nguồn dữ liệu. Nếu dòng này dùng biểu thức yield, generator thứ nhất sẽ yield generator thứ hai. Thay vào đó, bằng cách dùng yield from, generator thứ nhất yield các giá trị từ generator thứ hai.
Tóm tắt: yield so với return
Định nghĩa hàm với return và yield trông tương tự, nhưng hành vi khác nhau. Hãy tóm lược các khác biệt chính:
Hàm thường
Generator function
Từ khóa
return (ngầm định nếu không dùng tường minh)
yield
Khi được gọi
Thực thi mã đến khi gặp return, rồi trả về giá trị cuối
Tạo ra một generator iterator
Kết thúc
Kết thúc bởi câu lệnh return
Tạm dừng bởi yield, có thể tiếp tục sau
Giá trị trả về
Một đối tượng (có thể là cấu trúc dữ liệu)
Generator iterator
Biểu thức yield
Không áp dụng (tạo thành một câu lệnh)
Đánh giá thành None hoặc giá trị gửi vào bằng .send()
Trường hợp sử dụng
Phù hợp để trả về kết quả cuối cùng
Phù hợp để tạo luồng dữ liệu, đặc biệt cho dãy lớn hoặc vô hạn
Kết luận
Từ khóa yield của Python được dùng trong hàm để định nghĩa một generator function. Khi được gọi, các hàm này tạo ra generator iterator. Generator là một ví dụ về đánh giá lười trong Python, nơi biểu thức được đánh giá khi cần giá trị thay vì khi thực thi biểu thức. Do đó, biểu thức yield hữu ích để tạo luồng dữ liệu, trong đó các giá trị được sinh theo yêu cầu mà không cần lưu trong bộ nhớ.
Các cân nhắc về hiệu năng rất quan trọng khi làm việc với bộ dữ liệu lớn đòi hỏi nhiều thao tác. Generator iterator của Python là một trong những công cụ chủ chốt để xử lý hiệu quả lượng dữ liệu lớn.
Tôi học Vật lý và Toán ở bậc đại học tại Đại học Malta. Sau đó, tôi chuyển đến London và lấy bằng Tiến sĩ Vật lý tại Imperial College. Tôi nghiên cứu các kỹ thuật quang học mới để chụp ảnh võng mạc người. Hiện nay, tôi tập trung vào việc viết về Python, truyền đạt về Python và giảng dạy Python.
Học Python để phát triển phần mềm, từ việc viết hàm đến định nghĩa lớp. Hãy trang bị những kỹ năng cần thiết để bắt đầu sự nghiệp phát triển phần mềm của bạn!
Không phải lúc nào hàm Python cũng có câu lệnh
return. Generator function là những hàm sử dụng từ khóayieldthay choreturn.Những hàm này tạo ra generator iterator, là các đối tượng biểu diễn một luồng dữ liệu. Các phần tử do iterator biểu diễn chỉ được tạo và yield khi cần. Cách đánh giá này thường được gọi là đánh giá lười (lazy evaluation).
Khi xử lý các bộ dữ liệu lớn, generator mang lại lựa chọn tiết kiệm bộ nhớ so với việc lưu dữ liệu trong list, tuple và các cấu trúc dữ liệu khác vốn cần không gian bộ nhớ cho từng phần tử. Generator function cũng có thể tạo ra iterator vô hạn, điều không thể với các cấu trúc được đánh giá ngay như list và tuple.
Trước khi bắt đầu, hãy điểm lại sự khác nhau giữa hàm thông thường và generator:
Tính năng
Hàm
Generator
Sinh giá trị
Trả về tất cả giá trị cùng lúc
Yield từng giá trị một, theo yêu cầu
Thực thi
Thực thi toàn bộ trước khi trả về
Tạm dừng sau khi yield, tiếp tục khi giá trị tiếp theo được yêu cầu
Từ khóa
return
yield
Sử dụng bộ nhớ
Có thể cao, lưu toàn bộ dãy trong bộ nhớ
Thấp, chỉ lưu giá trị hiện tại và trạng thái cho lần kế tiếp
Lặp
Có thể lặp nhiều lần, nhưng cần lưu toàn bộ dãy
Thiết kế cho một lượt lặp, hiệu quả hơn với dãy lớn hoặc vô hạn
Sử dụng
yieldcủa Python để tạo Generator FunctionThuật ngữ generator trong Python có thể chỉ một generator iterator hoặc một generator function. Đây là những đối tượng khác nhau nhưng có liên quan trong Python. Trong hướng dẫn này, chúng tôi sẽ thường dùng đầy đủ thuật ngữ để tránh nhầm lẫn.
Hãy khám phá generator function trước. Một generator function trông giống hàm thông thường nhưng chứa từ khóa
yieldthay choreturn.Khi chương trình Python gọi một generator function, nó tạo ra một generator iterator. Iterator yield giá trị theo yêu cầu và tạm dừng thực thi cho đến khi cần giá trị khác. Hãy xem ví dụ để giải thích khái niệm này và minh họa sự khác biệt giữa hàm thường và generator function.
Dùng hàm thông thường
Trước hết, hãy định nghĩa một hàm thông thường có câu lệnh
return. Hàm này nhận một dãy từ và một chữ cái, trả về một list chứa số lần xuất hiện của chữ cái trong từng từ:Hàm trả ra một list chứa 1, 3 và 0 vì có một chữ a trong apple, ba lần a trong banana và không có trong cherry. Cùng hàm này có thể được viết lại dùng list comprehension thay vì khởi tạo list rỗng và dùng
.append():Hàm thông thường này trả về một list chứa toàn bộ kết quả mỗi lần được gọi. Tuy nhiên, nếu list từ rất lớn, việc gọi hàm sẽ đòi hỏi bộ nhớ vì chương trình tạo và lưu một list mới có kích thước bằng list gốc. Nếu hàm này được dùng lặp lại cho nhiều đối số đầu vào, hoặc các hàm tương tự thực hiện thao tác khác trên dữ liệu gốc, áp lực lên bộ nhớ có thể tăng nhanh.
Dùng generator function
Có thể dùng generator function thay thế:
Hàm này dùng
yieldkeyword thay choreturn. Generator function này trả về một đối tượng generator khi được gọi, gán vàooutput. Đối tượng này là một iterator. Nó không chứa sẵn dữ liệu về số lần xuất hiện của chữ cái trong từng từ. Thay vào đó, generator sẽ tạo và yield các giá trị khi cần. Hãy lấy giá trị đầu tiên từ generator iterator này:Hàm dựng sẵn
next()là một cách để lấy giá trị kế tiếp từ iterator. Chúng ta sẽ xem các cách khác sau trong hướng dẫn này.Mã trong generator function được thực thi cho đến khi chương trình gặp dòng có từ khóa
yield. Trong ví dụ này, vòng lặpforbắt đầu lượt lặp đầu tiên và lấy phần tử đầu trong listwords. Phương thức chuỗi.count()trả về một số nguyên, là 1 trong trường hợp này, vì có một chữ a trong apple. Generator yield giá trị này, vànext(output)trả về nó.Generator
outputtạm dừng thực thi tại đây. Như vậy, generator đã hoàn thành lượt lặp đầu tiên của vòngforvà tìm số lần xuất hiện của chữ a trong từ đầu tiên của list. Nó đang đợi cho đến khi được cần tiếp.Nếu gọi hàm dựng sẵn
next()lần nữa với đối số làoutput, generator sẽ tiếp tục thực thi từ điểm tạm dừng:Generator tiếp tục từ dòng có
yieldtrong lượt lặp đầu tiên của vòngfor. Vì vòngforkhông còn dòng mã nào khác, nó quay lên đầu vòng lặp và lấy phần tử thứ hai từ listwords. Giá trị trả về của.count()là 3 trong trường hợp này, và giá trị này được yield. Generator lại tạm dừng tại điểm thực thi này.Lần gọi thứ ba tới
next()tiếp tục thực thi:Lượt lặp thứ hai đi đến cuối vòng
forvà chuyển sang lượt lặp thứ ba. Mã lại tiến tới dòng cóyield, lần này yield số nguyên 0 vì không có chữ a nào trong cherry.Generator lại tạm dừng. Chương trình chỉ quyết định số phận của generator khi chúng ta gọi
next()lần thứ tư:Việc thực thi tiếp tục từ cuối lượt lặp thứ ba của vòng
for. Tuy nhiên, vòng lặp đã hết phần tử vì không còn phần tử nào trong listwords. Generator phát sinh ngoại lệStopIteration.Trong hầu hết trường hợp, các phần tử của generator không được truy cập trực tiếp bằng
next()mà thông qua một quy trình lặp khác. Ngoại lệStopIterationbáo hiệu kết thúc quá trình lặp. Chúng ta sẽ khám phá thêm ở phần tiếp theo của hướng dẫn.Python có cách khác để tạo generator iterator khi hoạt động của chúng có thể biểu diễn bằng một biểu thức đơn, như ví dụ trước. Generator iterator
outputcó thể được tạo bằng generator expression:Biểu thức trong ngoặc đơn gán cho
outputlà một generator expression, tạo ra generator iterator tương tự cái được tạo bởi generator functionfind_letter_occurrences().Hãy kết thúc phần này bằng một ví dụ khác về generator function để làm nổi bật cách thực thi tạm dừng và tiếp tục mỗi khi cần phần tử:
Generator function này không có vòng lặp. Thay vào đó, nó có ba dòng chứa từ khóa
yield. Mã tạo một generator iteratorstatuskhi gọi generator functionshow_status(). Lần đầu chương trình gọinext(status), generator bắt đầu thực thi. Nó in chuỗi"Start"và tạm dừng sau biểu thứcyieldđầu tiên. Generator yieldNonevì không có đối tượng theo sau từ khóayield.Chương trình chỉ in chuỗi
"Middle"khinext()được gọi lần thứ hai:Generator tạm dừng sau biểu thức
yieldthứ hai. Lần gọi thứ ba tớinext()in chuỗi cuối cùng,"End":Generator tạm dừng tại biểu thức
yieldcuối cùng. Nó sẽ phát sinh ngoại lệStopIterationvào lần tiếp theo chương trình yêu cầu một giá trị từ generator iterator này:Chúng ta sẽ khám phá thêm các cách dùng generator ở phần tiếp theo.
Làm việc với Generator Iterator
Generator function tạo ra generator iterator, và iterator là iterable. Hãy bóc tách câu này. Mỗi lần chương trình gọi một generator function, nó tạo ra một iterator. Vì iterator là iterable, chúng có thể được dùng trong vòng lặp
forvà các quy trình lặp khác.Do đó, hàm dựng sẵn
next()không phải là cách duy nhất để truy cập phần tử trong iterator. Phần này khám phá những cách khác để làm việc với generator.Dùng giao thức lặp của Python với generator iterator
Hãy quay lại một generator function từ phần trước trong hướng dẫn này:
Thay vì dùng
next()nhiều lần, phiên bản này dùng generator iteratoroutputtrong một vòngfor. Vì iterator là iterable, chúng có thể dùng trong vòngfor. Vòng lặp lấy các phần tử từ generator iterator cho đến khi không còn giá trị.Khác với các cấu trúc dữ liệu như list và tuple, một iterator chỉ có thể dùng một lần. Mã sẽ không in lại các giá trị nếu chúng ta chạy cùng vòng
forlần thứ hai:Iterator đã bị tiêu thụ bởi vòng
forđầu tiên, nên không thể yield giá trị nữa. Nếu cần generator sau khi nó bị tiêu thụ, chúng ta phải tạo một generator iterator khác từ generator function.Cũng có thể có nhiều generator iterator cùng tồn tại đồng thời trong một chương trình:
Generator function
find_letter_occurrences()tạo hai generator iterator:first_outputvàsecond_output. Dù cả hai iterator tham chiếu cùng dữ liệu trong listwords, chúng tiến triển độc lập với nhau.Ví dụ này lấy giá trị đầu từ
first_outputbằngnext(). Generator iterator yield 1 và tạm dừng. Chương trình sau đó lặp quasecond_output. Vì generator này chưa yield giá trị nào, vòng lặp đi qua toàn bộ các giá trị mà iterator thứ hai yield. Cuối cùng, có một vòngforkhác lặp quafirst_output. Tuy nhiên, iterator này đã yield giá trị đầu tiên trước đó. Vòng lặp đi qua các giá trị còn lại trong iterator này.Vòng
forkhông phải quy trình duy nhất có thể dùng để lặp qua generator iterator:Trong các ví dụ này, chương trình gọi trực tiếp generator function để tạo và dùng generator iterator thay vì gán vào biến. Ở ví dụ đầu, iterator được mở (unpack) bằng ký hiệu dấu sao. Quy trình này dựa trên cùng giao thức lặp như vòng
for.Ở ví dụ thứ hai, generator iterator được truyền vào hàm dựng sẵn
sorted(), hàm này yêu cầu một đối số iterable. Generator là iterable, vì vậy có thể dùng bất cứ khi nào quá trình lặp của Python diễn ra.Tạo iterator vô hạn
Một generator yield một giá trị và tạm dừng cho đến khi cần giá trị tiếp theo. Mỗi lần mã yêu cầu giá trị từ iterator, mã bên trong generator function sẽ thực thi cho đến khi biểu thức
yieldtiếp theo được đánh giá. Trong các ví dụ đến giờ, generator function có số lầnyieldhữu hạn. Tuy nhiên, có thể tạo một generator yield vô hạn số giá trị bằng cách dùng vòngwhiletrong generator function. Trong ví dụ sau, generator yield một màu ngẫu nhiên từ list màu được truyền vào generator function:Generator function
get_color()có một biểu thứcyieldbên trong vòngwhile. Vì vậy, mã luôn gặp một biểu thứcyieldkhác khi tìm giá trị tiếp theo. Generator iteratoroutput_colorsyield vô hạn màu sắc được chọn ngẫu nhiên từ list đầu vào. Generator này sẽ không bao giờ bị tiêu thụ hết.Không thể tạo các cấu trúc dữ liệu vô hạn như list và tuple. Generator cho phép chương trình tạo các iterable vô hạn. Lưu ý rằng nếu dùng generator iterator trực tiếp trong vòng
for, vòng lặp sẽ chạy mãi.Các khái niệm nâng cao về Generator
Generator có nhiều trường hợp sử dụng nâng cao trong Python. Phần này sẽ khám phá một số trong đó.
Gửi một đối tượng vào generator
Generator cũng có thể nhận dữ liệu bổ sung để dùng khi đánh giá mã. Câu lệnh chứa từ khóa
yieldlà một biểu thức đánh giá thành một giá trị. Giá trị này có thể được gán cho một biến trong generator function. Hãy bắt đầu với ví dụ cơ bản để minh họa khái niệm này:Từ khóa
yieldtrong Python tạo ra một biểu thức đánh giá thành giá trị. Tuy nhiên, giá trị của biểu thức này bên trong generator function không phải là đối tượng được generator yield. Xét biểu thứcyieldđầu tiên. Generator yield số nguyên 1. Do đó,print(next(output))hiển thị 1 ở lần gọi đầu và tạm dừng thực thi generator.Tuy nhiên, biểu thức
yieldtrong generator đánh giá thành một đối tượng, được gán cho biếnvalue. Trong ví dụ này,yieldgánNonechovalue. Quy trình này lặp lại cho lần xuất hiện thứ hai củayieldtrong generator function. Mục đích của lần gọinext()thứ ba là đảm bảo toàn bộ mã trong generator function được thực thi.Hãy thay thế lần gọi thứ hai và thứ ba tới
next()bằng.send(), một phương thức của lớp generator:Generator function không thay đổi. Generator được khởi động bằng cách gọi
next(), và mã chạy cho đến khi yield số nguyên đầu tiên, 1. Thay vì dùngnext()lần hai, chương trình gọioutput.send(). Phương thức này gửi một đối tượng vào generator. Trong ví dụ này, đối tượng là một chuỗi. Biểu thứcyieldtrong generator function đánh giá thành chuỗi này và gán chovalue. Vì vậy, generator có thể dùng chuỗi trong mã của nó.Lần gọi thứ hai tới
.send()gửi một đối tượng mới vào generator, cũng được gán cho biếnvalue. Generator phát sinhStopIterationsau lời gọiprint()cuối vì không còn biểu thứcyieldnào nữa.Hãy xem ví dụ khác dùng
.send(). Generator sau đây hiển thị số dư trong tài khoản, nhưng số dư có thể được cập nhật:Generator function yêu cầu số dư ban đầu khi được gọi. Giá trị của
balancecó thể thay đổi khi generator thực thi mã. Bất kỳ đối tượng nào gửi vào generator bằng.send()sẽ được gán choamount. Biến này sẽ làNonenếu generator yield một giá trị mà không có đối tượng nào được gửi vào, hoặc sẽ chứa đối tượng được gửi bằng.send().Generator iterator
current_balancebắt đầu với số dư $100. Generator được khởi động bằng cách gọinext(), bắt đầu thực thi mã cho đến khi yield giá trị đầu tiên.Khi generator đã khởi động, có thể tiếp tục thực thi bằng
.send()thay vìnext(). Generator cộng giá trị được gửi vào số dư. Nếu không gửi giá trị, như khi gọinext()lần nữa, generator yield số dư không đổi.Yield trực tiếp từ một iterable khác
Generator của Python cũng có thể yield giá trị trực tiếp từ một generator hoặc iterable khác bằng cú pháp
yield from. Hãy xem ví dụ về một generator function yield các giá trị từ một list lồng nhau:Generator function nhận một list, có thể bao gồm các list lồng nhau. Vòng lặp
forlặp qua các phần tử trong list. Mỗi phần tử trong list ngoài cùng hoặc là một giá trị, ở đây là số nguyên, hoặc là một list khác. Khi phần tử không phải list, generator yield phần tử đó.Tuy nhiên, khi phần tử là một list, generator đệ quy gọi lại generator function
flatten()với list bên trong làm đối số. Việc này tạo một generator iterator khác, dùng list bên trong làm nguồn dữ liệu. Nếu dòng này dùng biểu thứcyield, generator thứ nhất sẽ yield generator thứ hai. Thay vào đó, bằng cách dùngyield from, generator thứ nhất yield các giá trị từ generator thứ hai.Tóm tắt:
yieldso vớireturnĐịnh nghĩa hàm với
returnvàyieldtrông tương tự, nhưng hành vi khác nhau. Hãy tóm lược các khác biệt chính:Hàm thường
Generator function
Từ khóa
return (ngầm định nếu không dùng tường minh)
yield
Khi được gọi
Thực thi mã đến khi gặp return, rồi trả về giá trị cuối
Tạo ra một generator iterator
Kết thúc
Kết thúc bởi câu lệnh return
Tạm dừng bởi yield, có thể tiếp tục sau
Giá trị trả về
Một đối tượng (có thể là cấu trúc dữ liệu)
Generator iterator
Biểu thức yield
Không áp dụng (tạo thành một câu lệnh)
Đánh giá thành None hoặc giá trị gửi vào bằng .send()
Trường hợp sử dụng
Phù hợp để trả về kết quả cuối cùng
Phù hợp để tạo luồng dữ liệu, đặc biệt cho dãy lớn hoặc vô hạn
Kết luận
Từ khóa
yieldcủa Python được dùng trong hàm để định nghĩa một generator function. Khi được gọi, các hàm này tạo ra generator iterator. Generator là một ví dụ về đánh giá lười trong Python, nơi biểu thức được đánh giá khi cần giá trị thay vì khi thực thi biểu thức. Do đó, biểu thứcyieldhữu ích để tạo luồng dữ liệu, trong đó các giá trị được sinh theo yêu cầu mà không cần lưu trong bộ nhớ.Các cân nhắc về hiệu năng rất quan trọng khi làm việc với bộ dữ liệu lớn đòi hỏi nhiều thao tác. Generator iterator của Python là một trong những công cụ chủ chốt để xử lý hiệu quả lượng dữ liệu lớn.
Nếu bạn muốn học thêm về Python, hãy xem lộ trình nghề nghiệp Python Developer này.