Trong hướng dẫn này, bạn sẽ học cách dễ dàng tạo các lần lặp bằng trình tạo Python, nó khác với các trình lặp và các hàm thông thường như thế nào và tại sao bạn nên sử dụng nó.
Video: Trình tạo Python
Trình tạo bằng Python
Có rất nhiều công việc trong việc xây dựng một trình lặp trong Python. Chúng ta phải triển khai một lớp với __iter__()
và __next__()
phương thức, theo dõi các trạng thái bên trong và nâng lên StopIteration
khi không có giá trị nào được trả về.
Điều này vừa dài dòng vừa phản trực giác. Máy phát điện đến để giải cứu trong những tình huống như vậy.
Trình tạo Python là một cách đơn giản để tạo trình vòng lặp. Tất cả công việc mà chúng tôi đã đề cập ở trên đều được xử lý tự động bởi các trình tạo bằng Python.
Nói một cách đơn giản, trình tạo là một hàm trả về một đối tượng (trình lặp) mà chúng ta có thể lặp lại (một giá trị tại một thời điểm).
Tạo trình tạo bằng Python
Nó khá đơn giản để tạo một trình tạo bằng Python. Nó dễ dàng như xác định một hàm bình thường, nhưng với một yield
câu lệnh thay vì một return
câu lệnh.
Nếu một hàm chứa ít nhất một yield
câu lệnh (nó có thể chứa các câu lệnh yield
hoặc return
câu lệnh khác), nó sẽ trở thành một hàm tạo. Cả hai yield
và return
sẽ trả về một số giá trị từ một hàm.
Sự khác biệt là trong khi một return
câu lệnh kết thúc hoàn toàn một hàm, yield
câu lệnh sẽ tạm dừng hàm lưu tất cả các trạng thái của nó và sau đó tiếp tục từ đó với các lệnh gọi kế tiếp.
Sự khác biệt giữa chức năng Máy phát điện và chức năng Bình thường
Đây là cách một hàm máy phát điện khác với một hàm bình thường.
- Hàm tạo chứa một hoặc nhiều
yield
câu lệnh. - Khi được gọi, nó trả về một đối tượng (trình lặp) nhưng không bắt đầu thực thi ngay lập tức.
- Các phương thức như
__iter__()
và__next__()
được thực hiện tự động. Vì vậy, chúng tôi có thể lặp lại các mục đang sử dụngnext()
. - Sau khi hàm cho kết quả, hàm sẽ bị tạm dừng và quyền điều khiển được chuyển cho người gọi.
- Các biến cục bộ và trạng thái của chúng được ghi nhớ giữa các lần gọi liên tiếp.
- Cuối cùng, khi chức năng kết thúc,
StopIteration
sẽ tự động được nâng lên trong các lần gọi tiếp theo.
Đây là một ví dụ để minh họa tất cả các điểm đã nêu ở trên. Chúng ta có một hàm tạo được đặt tên my_gen()
với một số yield
câu lệnh.
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n
Dưới đây là một cuộc chạy tương tác trong trình thông dịch. Chạy chúng trong trình bao Python để xem đầu ra.
>>> # It returns an object but does not start execution immediately. >>> a = my_gen() >>> # We can iterate through the items using next(). >>> next(a) This is printed first 1 >>> # Once the function yields, the function is paused and the control is transferred to the caller. >>> # Local variables and theirs states are remembered between successive calls. >>> next(a) This is printed second 2 >>> next(a) This is printed at last 3 >>> # Finally, when the function terminates, StopIteration is raised automatically on further calls. >>> next(a) Traceback (most recent call last):… StopIteration >>> next(a) Traceback (most recent call last):… StopIteration
Một điều thú vị cần lưu ý trong ví dụ trên là giá trị của biến n được ghi nhớ giữa mỗi lần gọi.
Không giống như các hàm thông thường, các biến cục bộ không bị hủy khi hàm cho kết quả. Hơn nữa, đối tượng trình tạo chỉ có thể được lặp lại một lần.
Để khởi động lại quá trình, chúng ta cần tạo một đối tượng trình tạo khác bằng cách sử dụng một cái gì đó như a = my_gen()
.
Một điều cuối cùng cần lưu ý là chúng ta có thể sử dụng trực tiếp bộ tạo với vòng lặp for.
Điều này là do một for
vòng lặp sử dụng một trình lặp và lặp lại nó bằng next()
hàm. Nó tự động kết thúc khi StopIteration
được nâng lên. Kiểm tra ở đây để biết vòng lặp for thực sự được triển khai như thế nào trong Python.
# A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n # Using for loop for item in my_gen(): print(item)
Khi bạn chạy chương trình, đầu ra sẽ là:
Cái này được in đầu tiên 1 Cái này được in lần thứ hai 2 Cái này được in sau cùng 3
Trình tạo Python với một vòng lặp
Ví dụ trên ít được sử dụng hơn và chúng tôi nghiên cứu nó chỉ để có ý tưởng về những gì đang xảy ra trong nền.
Thông thường, các chức năng của bộ tạo được thực hiện với một vòng lặp có điều kiện kết thúc phù hợp.
Hãy lấy một ví dụ về trình tạo đảo ngược một chuỗi.
def rev_str(my_str): length = len(my_str) for i in range(length - 1, -1, -1): yield my_str(i) # For loop to reverse the string for char in rev_str("hello"): print(char)
Đầu ra
olleh
Trong ví dụ này, chúng ta đã sử dụng range()
hàm để lấy chỉ mục theo thứ tự ngược lại bằng vòng lặp for.
Lưu ý : Hàm trình tạo này không chỉ hoạt động với chuỗi mà còn với các loại lặp khác như danh sách, tuple, v.v.
Biểu thức trình tạo Python
Máy phát điện đơn giản có thể được tạo dễ dàng bằng cách sử dụng biểu thức máy phát điện. Nó làm cho việc xây dựng máy phát điện trở nên dễ dàng.
Tương tự như các hàm lambda tạo ra các hàm ẩn danh, các biểu thức trình tạo tạo các hàm tạo ẩn danh.
Cú pháp cho biểu thức trình tạo tương tự như cú pháp của một danh sách hiểu trong Python. Nhưng dấu ngoặc vuông được thay thế bằng dấu ngoặc tròn.
Sự khác biệt chính giữa khả năng hiểu danh sách và biểu thức trình tạo là khả năng hiểu danh sách tạo ra toàn bộ danh sách trong khi biểu thức trình tạo tạo ra một mục tại một thời điểm.
They have lazy execution ( producing items only when asked for ). For this reason, a generator expression is much more memory efficient than an equivalent list comprehension.
# Initialize the list my_list = (1, 3, 6, 10) # square each term using list comprehension list_ = (x**2 for x in my_list) # same thing can be done using a generator expression # generator expressions are surrounded by parenthesis () generator = (x**2 for x in my_list) print(list_) print(generator)
Output
(1, 9, 36, 100)
We can see above that the generator expression did not produce the required result immediately. Instead, it returned a generator object, which produces items only on demand.
Here is how we can start getting items from the generator:
# Initialize the list my_list = (1, 3, 6, 10) a = (x**2 for x in my_list) print(next(a)) print(next(a)) print(next(a)) print(next(a)) next(a)
When we run the above program, we get the following output:
1 9 36 100 Traceback (most recent call last): File "", line 15, in StopIteration
Generator expressions can be used as function arguments. When used in such a way, the round parentheses can be dropped.
>>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100
Use of Python Generators
There are several reasons that make generators a powerful implementation.
1. Easy to Implement
Generators can be implemented in a clear and concise way as compared to their iterator class counterpart. Following is an example to implement a sequence of power of 2 using an iterator class.
class PowTwo: def __init__(self, max=0): self.n = 0 self.max = max def __iter__(self): return self def __next__(self): if self.n> self.max: raise StopIteration result = 2 ** self.n self.n += 1 return result
The above program was lengthy and confusing. Now, let's do the same using a generator function.
def PowTwoGen(max=0): n = 0 while n < max: yield 2 ** n n += 1
Since generators keep track of details automatically, the implementation was concise and much cleaner.
2. Memory Efficient
A normal function to return a sequence will create the entire sequence in memory before returning the result. This is an overkill, if the number of items in the sequence is very large.
Generator implementation of such sequences is memory friendly and is preferred since it only produces one item at a time.
3. Represent Infinite Stream
Máy phát điện là phương tiện tuyệt vời để đại diện cho một luồng dữ liệu vô hạn. Luồng vô hạn không thể được lưu trữ trong bộ nhớ và vì trình tạo chỉ sản xuất một mục tại một thời điểm, chúng có thể đại diện cho một luồng dữ liệu vô hạn.
Hàm tạo sau có thể tạo ra tất cả các số chẵn (ít nhất là trên lý thuyết).
def all_even(): n = 0 while True: yield n n += 2
4. Máy phát điện đường ống
Nhiều máy phát điện có thể được sử dụng để tạo ra một loạt các hoạt động. Điều này được minh họa tốt nhất bằng cách sử dụng một ví dụ.
Giả sử chúng ta có một trình tạo ra các số trong chuỗi Fibonacci. Và chúng tôi có một bộ tạo khác cho các số bình phương.
Nếu chúng ta muốn tìm ra tổng bình phương của các số trong chuỗi Fibonacci, chúng ta có thể thực hiện theo cách sau đây bằng cách kết hợp đầu ra của các hàm trình tạo với nhau.
def fibonacci_numbers(nums): x, y = 0, 1 for _ in range(nums): x, y = y, x+y yield x def square(nums): for num in nums: yield num**2 print(sum(square(fibonacci_numbers(10))))
Đầu ra
4895
Pipelining này hiệu quả và dễ đọc (và vâng, mát hơn rất nhiều!).