PEP này đề xuất một số cải tiến đối với API và cú pháp của trình tạo, để làm cho chúng có thể sử dụng được như các coroutine đơn giản. Về cơ bản nó là sự kết hợp ý tưởng từ 2 PEP này, có thể coi là thừa nếu PEP này được chấp nhận
- PEP 288, Thuộc tính và ngoại lệ của trình tạo. PEP hiện tại bao gồm nửa sau của nó, các ngoại lệ của trình tạo [trên thực tế, tên phương thức
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
6 được lấy từ PEP 288]. Tuy nhiên, PEP 342 thay thế các thuộc tính trình tạo bằng một khái niệm từ bản sửa đổi trước đó của PEP 288, biểu thức năng suất - PEP 325, Hỗ trợ giải phóng tài nguyên cho máy phát điện. PEP 342 liên kết một số điểm lỏng lẻo trong thông số kỹ thuật PEP 325, để làm cho nó phù hợp với việc triển khai thực tế
Coroutines là một cách tự nhiên để thể hiện nhiều thuật toán, chẳng hạn như mô phỏng, trò chơi, I/O không đồng bộ và các dạng lập trình hướng sự kiện khác hoặc đa nhiệm hợp tác. Các hàm tạo của Python gần như là các coroutine – nhưng không hoàn toàn – ở chỗ chúng cho phép tạm dừng thực thi để tạo ra một giá trị, nhưng không cung cấp các giá trị hoặc ngoại lệ được chuyển vào khi thực thi tiếp tục. Chúng cũng không cho phép tạm dừng thực thi trong phần
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]7 của khối
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]8, và do đó gây khó khăn cho một coroutine bị hủy bỏ để tự dọn dẹp sau đó
Ngoài ra, các trình tạo không thể tạo ra quyền kiểm soát trong khi các chức năng khác đang thực thi, trừ khi các chức năng đó được thể hiện dưới dạng trình tạo và trình tạo bên ngoài được viết để tạo ra để đáp ứng các giá trị do trình tạo bên trong tạo ra. Điều này làm phức tạp việc triển khai các trường hợp sử dụng thậm chí tương đối đơn giản như truyền thông không đồng bộ, bởi vì việc gọi bất kỳ chức năng nào cũng yêu cầu trình tạo chặn [i. e. không thể mang lại quyền kiểm soát], nếu không, phải thêm rất nhiều mã vòng lặp soạn sẵn xung quanh mỗi lệnh gọi hàm cần thiết
Tuy nhiên, nếu có thể chuyển các giá trị hoặc ngoại lệ vào một trình tạo tại điểm mà nó bị treo, thì một hàm lập lịch trình đồng quy trình đơn giản hoặc chức năng tấm bạt lò xo sẽ cho phép các coroutine gọi lẫn nhau mà không bị chặn – một lợi ích to lớn cho các ứng dụng không đồng bộ. Các ứng dụng như vậy sau đó có thể viết các đồng quy trình để thực hiện I/O ổ cắm không chặn bằng cách nhường quyền kiểm soát cho bộ lập lịch I/O cho đến khi dữ liệu được gửi hoặc có sẵn. Trong khi đó, mã thực hiện I/O sẽ đơn giản làm điều gì đó như thế này
data = [yield nonblocking_read[my_socket, nbytes]]
để tạm dừng thực thi cho đến khi chương trình đăng ký
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]9 tạo ra một giá trị
Nói cách khác, với một vài cải tiến tương đối nhỏ đối với ngôn ngữ và triển khai kiểu trình tạo-lặp, Python sẽ có thể hỗ trợ thực hiện các hoạt động không đồng bộ mà không cần phải viết toàn bộ ứng dụng dưới dạng một loạt lệnh gọi lại và không yêu cầu . Do đó, những cải tiến này sẽ mang lại cho Python tiêu chuẩn nhiều lợi ích của ngã ba Python không ngăn xếp mà không yêu cầu bất kỳ sửa đổi đáng kể nào đối với lõi CPython hoặc API của nó. Ngoài ra, những cải tiến này có thể dễ dàng thực hiện được bằng bất kỳ triển khai Python nào [chẳng hạn như Jython] đã hỗ trợ trình tạo
Bằng cách thêm một vài phương thức đơn giản vào loại trình tạo lặp và với hai điều chỉnh cú pháp nhỏ, các nhà phát triển Python sẽ có thể sử dụng các hàm trình tạo để triển khai các quy trình đồng thời và các hình thức đa nhiệm hợp tác khác. Những phương pháp và điều chỉnh này là
- Xác định lại
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
0 là một biểu thức, chứ không phải là một câu lệnh. Tuyên bố năng suất hiện tại sẽ trở thành một biểu thức năng suất có giá trị bị loại bỏ. Giá trị của biểu thức năng suất làx = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]
1 bất cứ khi nào trình tạo được tiếp tục bằng lệnh gọix = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]
2 bình thường - Thêm một phương thức
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]
3 mới cho trình lặp-trình tạo, phương thức này sẽ tiếp tục trình tạo và gửi một giá trị trở thành kết quả của biểu thức năng suất hiện tại. Phương thứcx = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]
3 trả về giá trị tiếp theo do trình tạo tạo ra hoặc tăngx = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]
5 nếu trình tạo thoát mà không tạo ra giá trị khác - Thêm một phương thức
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
6 mới cho trình tạo-lặp, phương thức này đưa ra một ngoại lệ tại điểm mà trình tạo bị tạm dừng và trả về giá trị tiếp theo do trình tạo tạo ra, tăngx = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]
5 nếu trình tạo thoát mà không mang lại giá trị khác. [Nếu trình tạo không bắt được ngoại lệ được truyền vào hoặc đưa ra một ngoại lệ khác, thì ngoại lệ đó sẽ lan truyền đến trình gọi. ] - Thêm một phương thức
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
4 cho trình tạo vòng lặp, phương thức này tăngx = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
3 tại điểm mà trình tạo bị tạm dừng. Sau đó, nếu bộ tạo tăngx = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]
5 [bằng cách thoát bình thường hoặc do đã được đóng] hoặcx = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
3 [bằng cách không bắt ngoại lệ], thìx = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
4 trả về người gọi của nó. Nếu trình tạo mang lại một giá trị, mộtraise type, value, traceback
3 được nâng lên. Nếu trình tạo phát sinh bất kỳ ngoại lệ nào khác, nó sẽ được truyền tới người gọi.x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
4 không làm gì nếu trình tạo đã thoát do ngoại lệ hoặc thoát bình thường - Thêm hỗ trợ để đảm bảo rằng
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
4 được gọi khi bộ lặp trình tạo được thu gom rác - Cho phép sử dụng
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
0 trong các khốix = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
8, vì bộ sưu tập rác hoặc lệnh gọix = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
4 rõ ràng giờ đây sẽ cho phép mệnh đềraise type, value, traceback
9 thực thi
Một bản vá nguyên mẫu triển khai tất cả những thay đổi này so với Python CVS HEAD hiện tại có sẵn dưới dạng bản vá SourceForge #1223381 [https. // lỗi. con trăn. org/issue1223381]
Một phương pháp mới cho trình tạo vòng lặp được đề xuất, được gọi là
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]3. Nó nhận chính xác một đối số, đó là giá trị sẽ được gửi đến trình tạo. Gọi
def close[self]: try: self.throw[GeneratorExit] except [GeneratorExit, StopIteration]: pass else: raise RuntimeError["generator ignored GeneratorExit"] # Other exceptions are not caught2 hoàn toàn tương đương với việc gọi phương thức
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]2 của trình tạo. Gọi
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]3 với bất kỳ giá trị nào khác đều giống nhau, ngoại trừ giá trị được tạo bởi biểu thức năng suất hiện tại của trình tạo sẽ khác
Bởi vì trình tạo lặp bắt đầu thực thi ở đầu thân hàm của trình tạo, không có biểu thức năng suất nào để nhận giá trị khi trình tạo vừa được tạo. Do đó, việc gọi
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]3 với đối số không phải ____6_______1 bị cấm khi trình lặp trình tạo vừa mới bắt đầu và một số ______26_______7 được đưa ra nếu điều này xảy ra [có lẽ là do một loại lỗi logic nào đó]. Do đó, trước khi bạn có thể giao tiếp với một coroutine, trước tiên bạn phải gọi
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]2 hoặc
def close[self]: try: self.throw[GeneratorExit] except [GeneratorExit, StopIteration]: pass else: raise RuntimeError["generator ignored GeneratorExit"] # Other exceptions are not caught2 để chuyển quá trình thực thi của nó sang biểu thức năng suất đầu tiên
Cũng như phương thức
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]2, phương thức
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]3 trả về giá trị tiếp theo do trình tạo lặp tạo ra hoặc tăng
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]5 nếu trình tạo thoát bình thường hoặc đã thoát. Nếu trình tạo đưa ra một ngoại lệ chưa được phát hiện, nó sẽ được truyền tới người gọi của
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]3
Tuyên bố năng suất sẽ được phép sử dụng ở phía bên tay phải của một nhiệm vụ; . Giá trị của biểu thức năng suất này là ____6_______1 trừ khi ____6_______3 được gọi với một đối số không ____6_______1;
Một biểu thức năng suất phải luôn được đặt trong ngoặc đơn trừ khi nó xuất hiện ở biểu thức cấp cao nhất ở phía bên tay phải của một phép gán. Vì thế
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
đều hợp pháp, nhưng
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]
tất cả đều bất hợp pháp. [Một số trường hợp cạnh được thúc đẩy bởi tính hợp pháp hiện tại của
def consumer[func]: def wrapper[*args,**kw]: gen = func[*args, **kw] gen.next[] return gen wrapper.__name__ = func.__name__ wrapper.__dict__ = func.__dict__ wrapper.__doc__ = func.__doc__ return wrapper7. ]
Lưu ý rằng một tuyên bố năng suất hoặc biểu thức năng suất không có biểu thức hiện là hợp pháp. Điều này thật ý nghĩa. khi luồng thông tin trong lệnh gọi
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]2 bị đảo ngược, nó có thể mang lại kết quả mà không cần chuyển một giá trị rõ ràng [tất nhiên
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]0 tương đương với
@consumer def thumbnail_pager[pagesize, thumbsize, destination]: while True: page = new_image[pagesize] rows, columns = pagesize / thumbsize pending = False try: for row in xrange[rows]: for column in xrange[columns]: thumb = create_thumbnail[[yield], thumbsize] page.write[ thumb, col*thumbsize.x, row*thumbsize.y ] pending = True except GeneratorExit: # close[] was called, so flush any pending output if pending: destination.send[page] # then close the downstream consumer, and exit destination.close[] return else: # we finished a page full of thumbnails, so send it # downstream and keep on looping destination.send[page] @consumer def jpeg_writer[dirname]: fileno = 1 while True: filename = os.path.join[dirname,"page%04d.jpg" % fileno] write_jpeg[[yield], filename] fileno += 1 # Put them together to make a function that makes thumbnail # pages from a list of images and other parameters. # def write_thumbnails[pagesize, thumbsize, images, output_dir]: pipeline = thumbnail_pager[ pagesize, thumbsize, jpeg_writer[output_dir] ] for image in images: pipeline.send[image] pipeline.close[]0]
Khi send[value]
được gọi, biểu thức năng suất mà nó tiếp tục sẽ trả về giá trị được truyền vào. Khi ___6_______2 được gọi, biểu thức năng suất được tiếp tục sẽ trả về ___6_______1. Nếu biểu thức năng suất là một câu lệnh năng suất, thì giá trị được trả về này sẽ bị bỏ qua, tương tự như việc bỏ qua giá trị được trả về bởi lệnh gọi hàm được sử dụng làm câu lệnh
Trên thực tế, một biểu thức năng suất giống như một lệnh gọi hàm đảo ngược;
Ghi chú. các phần mở rộng cú pháp để yield làm cho việc sử dụng nó rất giống với phần mở rộng trong Ruby. Đây là cố ý. Xin lưu ý rằng trong Python, khối chuyển một giá trị cho trình tạo bằng cách sử dụng
@consumer def thumbnail_pager[pagesize, thumbsize, destination]: while True: page = new_image[pagesize] rows, columns = pagesize / thumbsize pending = False try: for row in xrange[rows]: for column in xrange[columns]: thumb = create_thumbnail[[yield], thumbsize] page.write[ thumb, col*thumbsize.x, row*thumbsize.y ] pending = True except GeneratorExit: # close[] was called, so flush any pending output if pending: destination.send[page] # then close the downstream consumer, and exit destination.close[] return else: # we finished a page full of thumbnails, so send it # downstream and keep on looping destination.send[page] @consumer def jpeg_writer[dirname]: fileno = 1 while True: filename = os.path.join[dirname,"page%04d.jpg" % fileno] write_jpeg[[yield], filename] fileno += 1 # Put them together to make a function that makes thumbnail # pages from a list of images and other parameters. # def write_thumbnails[pagesize, thumbsize, images, output_dir]: pipeline = thumbnail_pager[ pagesize, thumbsize, jpeg_writer[output_dir] ] for image in images: pipeline.send[image] pipeline.close[]5 thay vì
@consumer def thumbnail_pager[pagesize, thumbsize, destination]: while True: page = new_image[pagesize] rows, columns = pagesize / thumbsize pending = False try: for row in xrange[rows]: for column in xrange[columns]: thumb = create_thumbnail[[yield], thumbsize] page.write[ thumb, col*thumbsize.x, row*thumbsize.y ] pending = True except GeneratorExit: # close[] was called, so flush any pending output if pending: destination.send[page] # then close the downstream consumer, and exit destination.close[] return else: # we finished a page full of thumbnails, so send it # downstream and keep on looping destination.send[page] @consumer def jpeg_writer[dirname]: fileno = 1 while True: filename = os.path.join[dirname,"page%04d.jpg" % fileno] write_jpeg[[yield], filename] fileno += 1 # Put them together to make a function that makes thumbnail # pages from a list of images and other parameters. # def write_thumbnails[pagesize, thumbsize, images, output_dir]: pipeline = thumbnail_pager[ pagesize, thumbsize, jpeg_writer[output_dir] ] for image in images: pipeline.send[image] pipeline.close[]6 và cơ chế cơ bản theo đó điều khiển được chuyển giữa trình tạo và khối là hoàn toàn khác. Các khối trong Python không được biên dịch thành thunks; . Một số trường hợp cạnh hoạt động khác nhau; . [XXX - hiện tại nội dung về các khối này có vẻ không phù hợp, có lẽ Guido có thể chỉnh sửa để làm rõ. ]
Để một đối tượng trình tạo là trình vòng lặp được tạo bằng cách gọi hàm trình tạo. Dưới đây, g luôn đề cập đến một đối tượng trình tạo
Cú pháp cho các hàm tạo được mở rộng để cho phép một tuyên bố năng suất bên trong một câu lệnh
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]1
import collections class Trampoline: """Manage communications between coroutines""" running = False def __init__[self]: self.queue = collections.deque[] def add[self, coroutine]: """Request that a coroutine be executed""" self.schedule[coroutine] def run[self]: result = None self.running = True try: while self.running and self.queue: func = self.queue.popleft[] result = func[] return result finally: self.running = False def stop[self]: self.running = False def schedule[self, coroutine, stack=[], val=None, *exc]: def resume[]: value = val try: if exc: value = coroutine.throw[value,*exc] else: value = coroutine.send[value] except: if stack: # send the error back to the "caller" self.schedule[ stack[0], stack[1], *sys.exc_info[] ] else: # Nothing left in this pseudothread to # handle it, let it propagate to the # run loop raise if isinstance[value, types.GeneratorType]: # Yielded to a specific coroutine, push the # current one on the stack, and call the new # one with no args self.schedule[value, [coroutine,stack]] elif stack: # Yielded a result, pop the stack and send the # value to the caller self.schedule[stack[0], stack[1], value] # else: this pseudothread has ended self.queue.append[resume]2 gây ra ngoại lệ được chỉ định tại điểm mà trình tạo g hiện đang bị treo [i. e. tại một tuyên bố năng suất hoặc khi bắt đầu thân chức năng của nó nếu
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]2 chưa được gọi]. Nếu trình tạo bắt được ngoại lệ và mang lại một giá trị khác, thì đó là giá trị trả về của
import collections class Trampoline: """Manage communications between coroutines""" running = False def __init__[self]: self.queue = collections.deque[] def add[self, coroutine]: """Request that a coroutine be executed""" self.schedule[coroutine] def run[self]: result = None self.running = True try: while self.running and self.queue: func = self.queue.popleft[] result = func[] return result finally: self.running = False def stop[self]: self.running = False def schedule[self, coroutine, stack=[], val=None, *exc]: def resume[]: value = val try: if exc: value = coroutine.throw[value,*exc] else: value = coroutine.send[value] except: if stack: # send the error back to the "caller" self.schedule[ stack[0], stack[1], *sys.exc_info[] ] else: # Nothing left in this pseudothread to # handle it, let it propagate to the # run loop raise if isinstance[value, types.GeneratorType]: # Yielded to a specific coroutine, push the # current one on the stack, and call the new # one with no args self.schedule[value, [coroutine,stack]] elif stack: # Yielded a result, pop the stack and send the # value to the caller self.schedule[stack[0], stack[1], value] # else: this pseudothread has ended self.queue.append[resume]4. Nếu nó không bắt được ngoại lệ, thì ___________6 sẽ xuất hiện để đưa ra ngoại lệ tương tự đã vượt qua nó [nó rơi qua]. Nếu trình tạo đưa ra một ngoại lệ khác [điều này bao gồm
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]5 được tạo khi nó trả về] thì ngoại lệ đó được đưa ra bởi lệnh gọi ________0____6. Tóm lại,
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]6 hoạt động giống như
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]2 hoặc
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]3, ngoại trừ nó đưa ra một ngoại lệ tại thời điểm đình chỉ. Nếu trình tạo đã ở trạng thái đóng,
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]6 chỉ đưa ra ngoại lệ mà nó đã được thông qua mà không thực thi bất kỳ mã nào của trình tạo
Hiệu quả của việc tăng ngoại lệ chính xác như thể câu lệnh
raise type, value, traceback
đã được thực hiện tại điểm đình chỉ. Đối số loại không được là
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]1 và loại và giá trị phải tương thích. Nếu giá trị không phải là một thể hiện của loại, thì một thể hiện ngoại lệ mới được tạo bằng cách sử dụng giá trị đó, tuân theo các quy tắc tương tự mà câu lệnh
# coroutine function that echos data back on a connected # socket # def echo_handler[sock]: while True: try: data = yield nonblocking_read[sock] yield nonblocking_write[sock, data] except ConnectionLost: pass # exit normally if connection lost # coroutine function that listens for connections on a # socket, and then launches a service "handler" coroutine # to service the connection # def listen_on[trampoline, sock, handler]: while True: # get the next incoming connection connected_socket = yield nonblocking_accept[sock] # start another coroutine to handle the connection trampoline.add[ handler[connected_socket] ] # Create a scheduler to manage all our coroutines t = Trampoline[] # Create a coroutine instance to run the echo_handler on # incoming connections # server = listen_on[ t, listening_socket["localhost","echo"], echo_handler ] # Add the coroutine to the scheduler t.add[server] # loop forever, accepting connections and servicing them # "in parallel" # t.run[]3 sử dụng để tạo một thể hiện ngoại lệ. Truy nguyên, nếu được cung cấp, phải là một đối tượng truy nguyên Python hợp lệ, nếu không sẽ xảy ra
def close[self]: try: self.throw[GeneratorExit] except [GeneratorExit, StopIteration]: pass else: raise RuntimeError["generator ignored GeneratorExit"] # Other exceptions are not caught7
Ghi chú. Tên của phương pháp
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]6 đã được chọn vì nhiều lý do.
# coroutine function that echos data back on a connected # socket # def echo_handler[sock]: while True: try: data = yield nonblocking_read[sock] yield nonblocking_write[sock, data] except ConnectionLost: pass # exit normally if connection lost # coroutine function that listens for connections on a # socket, and then launches a service "handler" coroutine # to service the connection # def listen_on[trampoline, sock, handler]: while True: # get the next incoming connection connected_socket = yield nonblocking_accept[sock] # start another coroutine to handle the connection trampoline.add[ handler[connected_socket] ] # Create a scheduler to manage all our coroutines t = Trampoline[] # Create a coroutine instance to run the echo_handler on # incoming connections # server = listen_on[ t, listening_socket["localhost","echo"], echo_handler ] # Add the coroutine to the scheduler t.add[server] # loop forever, accepting connections and servicing them # "in parallel" # t.run[]6 là một từ khóa và do đó không thể được sử dụng làm tên phương thức. Không giống như
# coroutine function that echos data back on a connected # socket # def echo_handler[sock]: while True: try: data = yield nonblocking_read[sock] yield nonblocking_write[sock, data] except ConnectionLost: pass # exit normally if connection lost # coroutine function that listens for connections on a # socket, and then launches a service "handler" coroutine # to service the connection # def listen_on[trampoline, sock, handler]: while True: # get the next incoming connection connected_socket = yield nonblocking_accept[sock] # start another coroutine to handle the connection trampoline.add[ handler[connected_socket] ] # Create a scheduler to manage all our coroutines t = Trampoline[] # Create a coroutine instance to run the echo_handler on # incoming connections # server = listen_on[ t, listening_socket["localhost","echo"], echo_handler ] # Add the coroutine to the scheduler t.add[server] # loop forever, accepting connections and servicing them # "in parallel" # t.run[]3 [ngay lập tức đưa ra một ngoại lệ từ điểm thực thi hiện tại], trước tiên,
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]6 sẽ tiếp tục trình tạo và chỉ sau đó mới đưa ra ngoại lệ. Từ ném gợi ý đặt ngoại lệ ở một vị trí khác và đã được liên kết với các ngoại lệ trong các ngôn ngữ khác
tên phương pháp thay thế đã được xem xét.
# coroutine function that echos data back on a connected # socket # def echo_handler[sock]: while True: try: data = yield nonblocking_read[sock] yield nonblocking_write[sock, data] except ConnectionLost: pass # exit normally if connection lost # coroutine function that listens for connections on a # socket, and then launches a service "handler" coroutine # to service the connection # def listen_on[trampoline, sock, handler]: while True: # get the next incoming connection connected_socket = yield nonblocking_accept[sock] # start another coroutine to handle the connection trampoline.add[ handler[connected_socket] ] # Create a scheduler to manage all our coroutines t = Trampoline[] # Create a coroutine instance to run the echo_handler on # incoming connections # server = listen_on[ t, listening_socket["localhost","echo"], echo_handler ] # Add the coroutine to the scheduler t.add[server] # loop forever, accepting connections and servicing them # "in parallel" # t.run[]9,
send[value]
0, send[value]
1, send[value]
2 và send[value]
3. Không cái nào trong số này có vẻ phù hợp cũng như x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]6
Một ngoại lệ tiêu chuẩn mới được xác định,
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]3, kế thừa từ
send[value]
7. Máy phát điện nên giải quyết vấn đề này bằng cách nâng lại nó [hoặc đơn giản là không bắt nó] hoặc bằng cách nâng x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]5
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]00 được xác định bởi mã giả sau
def close[self]: try: self.throw[GeneratorExit] except [GeneratorExit, StopIteration]: pass else: raise RuntimeError["generator ignored GeneratorExit"] # Other exceptions are not caught
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]01 là một trình bao bọc cho
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]00. Điều này sẽ được gọi khi đối tượng trình tạo được thu gom rác [trong CPython, đây là khi số tham chiếu của nó về 0]. Nếu
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]4 đưa ra một ngoại lệ, một dấu vết ngược lại cho ngoại lệ đó được in thành
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]04 và tiếp tục bị bỏ qua; . Điều này phù hợp với việc xử lý các ngoại lệ trong các phương thức
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]05 trên các thể hiện của lớp
Nếu đối tượng trình tạo tham gia vào một chu trình, thì có thể không gọi được
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]01. Đây là hành vi của trình thu gom rác hiện tại của CPython. Lý do của sự hạn chế là mã GC cần phá vỡ một chu kỳ tại một điểm tùy ý để thu thập nó và từ đó trở đi, không mã Python nào được phép xem các đối tượng hình thành chu trình, vì chúng có thể nằm trong một . Các đối tượng bị treo trong một chu kỳ không phải tuân theo hạn chế này
Lưu ý rằng không có khả năng thấy một đối tượng trình tạo tham gia vào một chu trình trong thực tế. Tuy nhiên, việc lưu trữ một đối tượng trình tạo trong một biến toàn cục sẽ tạo ra một chu trình thông qua con trỏ
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]07 của khung trình tạo. Một cách khác để tạo chu trình là lưu trữ tham chiếu đến đối tượng trình tạo trong cấu trúc dữ liệu được chuyển tới trình tạo dưới dạng đối số [e. g. , nếu một đối tượng có một phương thức là trình tạo và giữ tham chiếu đến một trình vòng lặp đang chạy được tạo bởi phương thức đó]. Cả hai trường hợp này đều không có khả năng đưa ra các kiểu sử dụng máy phát điển hình
Ngoài ra, trong quá trình triển khai CPython của PEP này, đối tượng khung được trình tạo sử dụng sẽ được giải phóng bất cứ khi nào quá trình thực thi của nó bị chấm dứt do lỗi hoặc thoát bình thường. Điều này sẽ đảm bảo rằng các trình tạo không thể tiếp tục hoạt động sẽ không nằm trong chu kỳ tham chiếu không thể thu thập được. Điều này cho phép mã khác có khả năng sử dụng
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]4 trong khối
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]8 hoặc
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]10 [theo PEP 343] để đảm bảo rằng trình tạo nhất định được hoàn thiện đúng cách
Bản nháp trước đó của PEP này đã đề xuất một cú pháp
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]12 mới để sử dụng trong các vòng lặp for [được chuyển từ PEP 340], cú pháp này sẽ chuyển giá trị của EXPR vào trình vòng lặp đang được lặp lại. Hiện tại, tính năng này đã bị rút lại vì phạm vi của PEP này đã bị thu hẹp để chỉ tập trung vào việc chuyển các giá trị vào trình tạo vòng lặp chứ không phải các loại trình vòng lặp khác. Một số người trong danh sách Python-Dev cũng cảm thấy rằng việc thêm cú pháp mới cho tính năng cụ thể này sẽ là quá sớm.
Thảo luận về python-dev đã tiết lộ một số vấn đề mở. Tôi liệt kê chúng ở đây, với giải pháp ưa thích của tôi và động lực của nó. PEP hiện được viết phản ánh giải pháp ưu tiên này
- Ngoại lệ nào sẽ được đưa ra bởi
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
4 khi trình tạo mang lại một giá trị khác như một phản hồi cho ngoại lệx = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
3?Ban đầu, tôi chọn
def close[self]: try: self.throw[GeneratorExit] except [GeneratorExit, StopIteration]: pass else: raise RuntimeError["generator ignored GeneratorExit"] # Other exceptions are not caught
7 vì nó thể hiện hành vi sai trái nghiêm trọng của hàm tạo, điều này cần được khắc phục bằng cách thay đổi mã. Nhưng lớp trang tríx = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
16 trong PEP 343 sử dụngraise type, value, traceback
3 cho các hành vi phạm tội tương tự. Có thể cho rằng tất cả họ nên sử dụng cùng một ngoại lệ. Tôi không muốn giới thiệu một lớp ngoại lệ mới chỉ vì mục đích này, vì nó không phải là một ngoại lệ mà tôi muốn mọi người nắm bắt. Tôi muốn nó biến thành một dấu vết được lập trình viên nhìn thấy, sau đó sửa mã. Vì vậy, bây giờ tôi tin rằng cả hai nên tăngraise type, value, traceback
3. Có một số tiền lệ cho điều đó. nó được nâng lên bởi mã Python lõi trong các trường hợp phát hiện đệ quy vô tận và cho các đối tượng chưa được khởi tạo [và cho nhiều điều kiện linh tinh] - Oren Tirosh đã đề xuất đổi tên phương thức
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]
3 thànhx = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
20, để tương thích với giao diện người tiêu dùng [xem http. // effbot. tổ chức/khu vực/người tiêu dùng. htm cho thông số kỹ thuật. ]Tuy nhiên, nhìn kỹ hơn vào giao diện người tiêu dùng, có vẻ như ngữ nghĩa mong muốn cho ____0_______20 khác với ____6_______3, bởi vì _______6_______3 không thể được gọi một cách có ý nghĩa trên trình tạo mới bắt đầu. Ngoài ra, giao diện người tiêu dùng như được định nghĩa hiện tại không bao gồm việc xử lý đối với
x = 12 + yield 42 x = 12 + yield foo[yield 42, 12] foo[yield, 12]
5Do đó, có vẻ như sẽ hữu ích hơn nếu tạo một trình trang trí đơn giản bao bọc hàm tạo để làm cho nó phù hợp với giao diện người tiêu dùng. Ví dụ: nó có thể khởi động trình tạo bằng lệnh gọi ___6_______2 ban đầu, bẫy StopIteration và thậm chí có thể cung cấp ___0_______26 bằng cách gọi lại hàm trình tạo
- Một trình trang trí người tiêu dùng đơn giản làm cho hàm trình tạo tự động chuyển sang điểm năng suất đầu tiên khi được gọi ban đầu
def consumer[func]: def wrapper[*args,**kw]: gen = func[*args, **kw] gen.next[] return gen wrapper.__name__ = func.__name__ wrapper.__dict__ = func.__dict__ wrapper.__doc__ = func.__doc__ return wrapper
- Một ví dụ về việc sử dụng trình trang trí của người tiêu dùng để tạo trình tạo ngược nhận hình ảnh và tạo các trang hình thu nhỏ, gửi chúng cho người tiêu dùng khác. Các chức năng như thế này có thể được kết nối với nhau để tạo thành các quy trình xử lý hiệu quả của người tiêu dùng mà mỗi người có thể có trạng thái bên trong phức tạp
@consumer def thumbnail_pager[pagesize, thumbsize, destination]: while True: page = new_image[pagesize] rows, columns = pagesize / thumbsize pending = False try: for row in xrange[rows]: for column in xrange[columns]: thumb = create_thumbnail[[yield], thumbsize] page.write[ thumb, col*thumbsize.x, row*thumbsize.y ] pending = True except GeneratorExit: # close[] was called, so flush any pending output if pending: destination.send[page] # then close the downstream consumer, and exit destination.close[] return else: # we finished a page full of thumbnails, so send it # downstream and keep on looping destination.send[page] @consumer def jpeg_writer[dirname]: fileno = 1 while True: filename = os.path.join[dirname,"page%04d.jpg" % fileno] write_jpeg[[yield], filename] fileno += 1 # Put them together to make a function that makes thumbnail # pages from a list of images and other parameters. # def write_thumbnails[pagesize, thumbsize, images, output_dir]: pipeline = thumbnail_pager[ pagesize, thumbsize, jpeg_writer[output_dir] ] for image in images: pipeline.send[image] pipeline.close[]
- Một bộ lập lịch hoặc tấm bạt lò xo đồng thường trình đơn giản cho phép các coroutine gọi các coroutine khác bằng cách tạo ra coroutine mà chúng muốn gọi. Bất kỳ giá trị không phải trình tạo nào do một coroutine mang lại đều được trả về coroutine được gọi là giá trị mang lại giá trị. Tương tự, nếu một quy trình đăng ký đưa ra một ngoại lệ, thì ngoại lệ đó sẽ được chuyển đến người gọi của nó. Trên thực tế, ví dụ này mô phỏng các tác vụ nhỏ đơn giản như được sử dụng trong Stackless Python, miễn là bạn sử dụng biểu thức năng suất để gọi các quy trình mà nếu không thì sẽ chặn. Đây chỉ là một ví dụ rất đơn giản và có thể lập lịch trình phức tạp hơn nhiều. [Ví dụ: khung GTasklet hiện có cho Python [http. //www. thần lùn. org/~gjc/gtasklet/gtasklets. html] và đỉnh. khung sự kiện [http. //đỉnh cao. viễn thông. com/] đã triển khai các khả năng lập lịch trình tương tự, nhưng hiện tại phải sử dụng các giải pháp thay thế khó xử vì không thể chuyển các giá trị hoặc ngoại lệ vào trình tạo. ]
import collections class Trampoline: """Manage communications between coroutines""" running = False def __init__[self]: self.queue = collections.deque[] def add[self, coroutine]: """Request that a coroutine be executed""" self.schedule[coroutine] def run[self]: result = None self.running = True try: while self.running and self.queue: func = self.queue.popleft[] result = func[] return result finally: self.running = False def stop[self]: self.running = False def schedule[self, coroutine, stack=[], val=None, *exc]: def resume[]: value = val try: if exc: value = coroutine.throw[value,*exc] else: value = coroutine.send[value] except: if stack: # send the error back to the "caller" self.schedule[ stack[0], stack[1], *sys.exc_info[] ] else: # Nothing left in this pseudothread to # handle it, let it propagate to the # run loop raise if isinstance[value, types.GeneratorType]: # Yielded to a specific coroutine, push the # current one on the stack, and call the new # one with no args self.schedule[value, [coroutine,stack]] elif stack: # Yielded a result, pop the stack and send the # value to the caller self.schedule[stack[0], stack[1], value] # else: this pseudothread has ended self.queue.append[resume]
- Một máy chủ tiếng vang đơn giản và mã để chạy nó bằng cách sử dụng tấm bạt lò xo [giả sử có sự tồn tại của
x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
27,x = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
28 và các coroutine I/O khác, e. g. tăngx = yield 42 x = yield x = 12 + [yield 42] x = 12 + [yield] foo[yield 42] foo[yield]
29 nếu kết nối bị đóng]# coroutine function that echos data back on a connected # socket # def echo_handler[sock]: while True: try: data = yield nonblocking_read[sock] yield nonblocking_write[sock, data] except ConnectionLost: pass # exit normally if connection lost # coroutine function that listens for connections on a # socket, and then launches a service "handler" coroutine # to service the connection # def listen_on[trampoline, sock, handler]: while True: # get the next incoming connection connected_socket = yield nonblocking_accept[sock] # start another coroutine to handle the connection trampoline.add[ handler[connected_socket] ] # Create a scheduler to manage all our coroutines t = Trampoline[] # Create a coroutine instance to run the echo_handler on # incoming connections # server = listen_on[ t, listening_socket["localhost","echo"], echo_handler ] # Add the coroutine to the scheduler t.add[server] # loop forever, accepting connections and servicing them # "in parallel" # t.run[]
Một bản vá nguyên mẫu triển khai tất cả các tính năng được mô tả trong PEP này có sẵn dưới dạng bản vá SourceForge #1223381 [https. // lỗi. con trăn. org/issue1223381]
Bản vá này đã được cam kết với CVS 01-02 tháng 8 năm 2005
Raymond Hettinger [PEP 288] và Samuele Pedroni [PEP 325] lần đầu tiên chính thức đề xuất ý tưởng truyền các giá trị hoặc ngoại lệ vào bộ tạo và khả năng đóng bộ tạo. Timothy Delaney đã đề xuất tiêu đề của PEP này và Steven Bethard đã giúp chỉnh sửa phiên bản trước. Xem thêm phần Lời cảm ơn của PEP 340