Để trả lời câu hỏi của bạn về nơi StopIteration
bị phát hiện trong máy phát điện gen
được tạo bên trong itertools.tee
: không. Đó là tùy thuộc vào người tiêu dùng của kết quả tee
để bắt ngoại lệ khi họ lặp lại.
Trước hết, điều quan trọng cần lưu ý là chức năng máy phát (là bất kỳ hàm nào có câu lệnh yield
trong đó, ở bất kỳ đâu) về cơ bản khác với chức năng bình thường. Thay vì chạy mã của hàm khi nó được gọi, thay vào đó, bạn sẽ chỉ nhận được một đối tượng generator
khi bạn gọi hàm. Chỉ khi bạn lặp qua máy phát thì bạn sẽ chạy mã.
Chức năng máy phát sẽ không bao giờ kết thúc lặp mà không tăng StopIteration
(trừ khi nó tăng một số ngoại lệ khác thay thế). StopIteration
là tín hiệu từ máy phát điện mà nó được thực hiện, và nó không phải là tùy chọn. Nếu bạn đạt được tuyên bố return
hoặc kết thúc mã của hàm của trình tạo không cần nâng cấp bất cứ điều gì, Python sẽ tăng StopIteration
cho bạn!
Điều này khác với các chức năng thông thường, trả về None
nếu chúng kết thúc mà không trả lại bất kỳ điều gì khác. Nó liên kết với các cách khác nhau mà máy phát điện hoạt động, như tôi đã mô tả ở trên.
Dưới đây là một chức năng máy phát điện ví dụ mà sẽ làm cho nó dễ dàng để xem làm thế nào StopIteration
được nêu ra:
def simple_generator():
yield "foo"
yield "bar"
# StopIteration will be raised here automatically
Đây là những gì xảy ra khi bạn tiêu thụ nó:
>>> g = simple_generator()
>>> next(g)
'foo'
>>> next(g)
'bar'
>>> next(g)
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
next(g)
StopIteration
Calling simple_generator
luôn trả về một đối tượng generator
ngay lập tức (không chạy bất kỳ mã nào trong hàm). Mỗi cuộc gọi của next
trên đối tượng máy phát sẽ chạy mã cho đến câu lệnh yield
tiếp theo và trả về giá trị được tạo. Nếu không có thêm để nhận được, StopIteration
được nâng lên.
Bây giờ, thông thường bạn không thấy StopIteration
ngoại lệ. Lý do cho điều này là bạn thường tiêu thụ máy phát điện bên trong vòng for
. Một tuyên bố for
sẽ tự động gọi next
hơn và hơn cho đến khi StopIteration
được nâng lên. Nó sẽ bắt và ngăn chặn các ngoại lệ StopIteration
cho bạn, vì vậy bạn không cần phải lộn xộn xung quanh với try
/except
khối để đối phó với nó.
Một vòng lặp for
như for item in iterable: do_suff(item)
là gần như chính xác tương đương với while
vòng lặp này (sự khác biệt duy nhất là một thực for
không cần một biến tạm thời để giữ iterator):
iterator = iter(iterable)
try:
while True:
item = next(iterator)
do_stuff(item)
except StopIteration:
pass
finally:
del iterator
Chức năng gen
máy phát điện bạn cho thấy ở trên cùng là một ngoại lệ. Nó sử dụng ngoại lệ StopIteration
được tạo bởi trình vòng lặp, nó tiêu thụ như là tín hiệu riêng của nó mà nó được thực hiện đang được lặp lại. Đó là, thay vì bắt các StopIteration
và sau đó phá vỡ ra khỏi vòng lặp, nó chỉ đơn giản là cho phép ngoại lệ đi uncaught (có lẽ là bị bắt bởi một số mã cấp cao hơn).
Không liên quan đến câu hỏi chính, có một điều khác tôi muốn chỉ ra. Trong mã của bạn, bạn đang gọi số next
trên một biến có tên là iterable
. Nếu bạn lấy tên đó làm tài liệu cho loại đối tượng bạn sẽ nhận được, điều này không nhất thiết phải an toàn.
next
là một phần của iterator
giao thức, không phải là iterable
(hoặc container
) giao thức. Nó có thể làm việc cho một số loại vòng lặp (chẳng hạn như các tệp và trình tạo, vì các kiểu đó là các trình vòng lặp riêng của chúng), nhưng nó sẽ thất bại đối với các vòng lặp khác, chẳng hạn như các bộ và danh sách. Cách tiếp cận đúng hơn là gọi iter
trên giá trị iterable
của bạn, sau đó gọi next
trên trình lặp mà bạn nhận được. (Hoặc chỉ cần sử dụng for
vòng, mà gọi cả iter
và next
cho bạn vào những thời điểm thích hợp!)
Edit: tôi chỉ tìm thấy câu trả lời của riêng tôi trong một tìm kiếm Google cho một câu hỏi liên quan, và tôi nghĩ rằng tôi muốn cập nhật đến thời điểm câu trả lời ở trên sẽ không hoàn toàn đúng trong các phiên bản Python trong tương lai. PEP 479 làm cho nó trở thành lỗi khi cho phép StopIteration
bong bóng lên không bị bắt do một chức năng của máy phát điện. Nếu điều đó xảy ra, Python sẽ biến nó thành một ngoại lệ RuntimeError
thay thế.
Điều này có nghĩa là mã như ví dụ trong itertools
sử dụng StopIteration
để thoát khỏi chức năng của trình tạo sẽ cần được sửa đổi. Thông thường, bạn sẽ cần phải nắm bắt ngoại lệ với try
/except
và sau đó thực hiện return
.
Vì đây là sự thay đổi không tương thích ngược, nó đang được phân đoạn dần dần. Trong Python 3.5, tất cả mã sẽ hoạt động như trước theo mặc định, nhưng bạn có thể nhận được hành vi mới với from __future__ import generator_stop
. Trong Python 3.6, mã sẽ vẫn hoạt động, nhưng nó sẽ đưa ra một cảnh báo. Trong Python 3.7, hành vi mới sẽ áp dụng mọi lúc.
Bạn gọi điện thoại này như thế nào? –