Trình tạo so với Python coroutine

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à

  1. 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ọi
    x = 12 + yield 42
    x = 12 + yield
    foo[yield 42, 12]
    foo[yield, 12]
    
    2 bình thường
  2. 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ứ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 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 mà không tạo ra giá trị khác
  3. 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ăng
    x = 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. ]
  4. 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ăng
    x = 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ăng
    x = 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ặc
    x = 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ột
    raise 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
  5. 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
  6. 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ối
    x = 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ọi
    x = 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 caught
2 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 caught
2 để 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 wrapper
7. ]

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 caught
7

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

  1. 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ụng
    raise 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ăng
    raise 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]

  2. 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ành
    x = 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]
    
    5

    Do đó, 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

  1. 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
    

  2. 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[]
    

  3. 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]
    

  4. 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ăng
    x = 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

Sự khác biệt giữa máy phát điện và coroutine là gì?

Trình tạo cho phép bạn tạo các hàm trông giống như trình vòng lặp đối với người tiêu dùng. Coroutine là một phần mở rộng của khái niệm chức năng truyền thống . Một hàm sẽ trao lại quyền điều khiển cho người gọi của nó một lần thông qua câu lệnh return. Một coroutine sẽ trả lại quyền kiểm soát nhiều lần bằng cách gọi năng suất.

Là coroutine một máy phát điện?

Coroutines là Trình tạo , nhưng lợi nhuận của chúng chấp nhận các giá trị. Các coroutine có thể tạm dừng và tiếp tục thực thi [tuyệt vời cho đồng thời].

Máy phát điện có hiệu quả hơn Python không?

Trình tạo có một số lợi thế so với các trình lặp khác trong Python. Chúng rất dễ triển khai và có thể tiết kiệm bộ nhớ hơn so với lưu trữ toàn bộ chuỗi trong bộ nhớ . Chúng có thể được sử dụng để tạo ra một chuỗi giá trị "vô hạn", bằng cách sử dụng vòng lặp While True và tạo ra các giá trị vô thời hạn.

Trình tạo hoặc trình lặp nào tốt hơn trong Python?

Trình tạo cung cấp một cách tốt hơn để tạo trình lặp trong Python . Điều này có thể được thực hiện bằng cách xác định một hàm thích hợp thay vì sử dụng câu lệnh trả về sử dụng từ khóa năng suất. Hãy xem điều này với sự trợ giúp của một ví dụ.

Chủ Đề