a

Câu hỏi Từ khóa “lợi nhuận” làm gì?


Việc sử dụng yield từ khóa bằng Python? Nó làm gì?

Ví dụ, tôi đang cố gắng hiểu mã này1:

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

Và đây là người gọi:

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

Điều gì xảy ra khi phương pháp _get_child_candidates được gọi là? Danh sách có được trả lại không? Một yếu tố duy nhất? Nó được gọi là một lần nữa? Khi nào các cuộc gọi tiếp theo sẽ dừng lại?


1. Mã đến từ Jochen Schulz (jrschulz), người đã tạo ra một thư viện Python tuyệt vời cho các không gian metric. Đây là liên kết đến nguồn hoàn chỉnh: Mô-đun mspace.


8325
2017-10-23 22:21


gốc




Các câu trả lời:


Để hiểu những gì yield bạn có hiểu gì không máy phát điện là. Và trước khi máy phát điện đến vòng lặp.

Iterables

Khi bạn tạo danh sách, bạn có thể đọc từng mục một. Đọc từng mục của nó được gọi là lặp:

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylist là một có thể lặp lại. Khi bạn sử dụng một danh sách hiểu, bạn tạo một danh sách, và vì vậy một iterable:

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

Mọi thứ bạn có thể sử dụng "for... in..."on là một iterable; lists, strings, các tập tin...

Các vòng lặp này rất tiện dụng vì bạn có thể đọc chúng nhiều như bạn muốn, nhưng bạn lưu trữ tất cả các giá trị trong bộ nhớ và điều này không phải lúc nào cũng là những gì bạn muốn khi bạn có nhiều giá trị.

Máy phát điện

Máy phát điện là bộ lặp, một loại có thể lặp lại bạn chỉ có thể lặp lại một lần. Máy phát không lưu trữ tất cả các giá trị trong bộ nhớ, chúng tạo ra các giá trị trên bay:

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

Nó chỉ giống nhau ngoại trừ bạn đã sử dụng () thay vì []. Nhưng bạn không thể biểu diễn for i in mygenerator lần thứ hai kể từ khi máy phát điện chỉ có thể được sử dụng một lần: chúng tính 0, sau đó quên nó và tính 1, và kết thúc tính toán 4, từng cái một.

Năng suất

yield là một từ khóa được sử dụng như return, ngoại trừ chức năng sẽ trả về một máy phát điện.

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

Đây là một ví dụ vô dụng, nhưng nó tiện dụng khi bạn biết hàm của bạn sẽ trả về một tập hợp lớn các giá trị mà bạn sẽ chỉ cần đọc một lần.

Để làm chủ yield, bạn phải hiểu rằng khi bạn gọi hàm, mã bạn đã viết trong phần thân hàm không chạy. Hàm chỉ trả về đối tượng trình tạo, đây là một chút phức tạp :-)

Sau đó, mã của bạn sẽ được chạy mỗi lần for sử dụng máy phát.

Bây giờ phần khó khăn:

Lần đầu tiên for gọi đối tượng máy phát được tạo ra từ hàm của bạn, nó sẽ chạy mã trong hàm của bạn từ đầu cho đến khi nó chạm yield, sau đó nó sẽ trả về giá trị đầu tiên của vòng lặp. Sau đó, mỗi cuộc gọi khác sẽ chạy vòng lặp mà bạn đã viết trong hàm một lần nữa và trả về giá trị tiếp theo, cho đến khi không có giá trị trả về.

Máy phát điện được coi là rỗng khi chức năng chạy, nhưng không bị đánh yield nữa không. Có thể là do vòng lặp đã kết thúc hoặc do bạn không thỏa mãn "if/else" nữa không.


Mã của bạn đã được giải thích

Máy phát điện:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

Người gọi:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidates list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

Mã này chứa một số phần thông minh:

  • Vòng lặp lặp lại trên một danh sách, nhưng danh sách mở rộng trong khi vòng lặp đang được lặp lại :-) Đó là một cách ngắn gọn để đi qua tất cả các dữ liệu lồng nhau ngay cả khi nó hơi nguy hiểm vì bạn có thể kết thúc với một vòng lặp vô hạn. Trong trường hợp này, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) xả tất cả các giá trị của máy phát, nhưng while tiếp tục tạo các đối tượng máy phát mới sẽ tạo ra các giá trị khác nhau từ các đối tượng trước vì nó không được áp dụng trên cùng một nút.

  • Các extend() phương thức là một phương thức đối tượng danh sách dự kiến ​​có thể lặp lại và thêm các giá trị của nó vào danh sách.

Thông thường chúng tôi chuyển một danh sách cho nó:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

Nhưng trong mã của bạn nó nhận được một máy phát điện, đó là tốt bởi vì:

  1. Bạn không cần phải đọc các giá trị hai lần.
  2. Bạn có thể có rất nhiều trẻ em và bạn không muốn tất cả chúng được lưu trữ trong bộ nhớ.

Và nó hoạt động vì Python không quan tâm nếu đối số của một phương thức là một danh sách hay không. Python hy vọng các vòng lặp để nó sẽ hoạt động với các chuỗi, danh sách, bộ dữ liệu và trình tạo! Điều này được gọi là gõ vịt và là một trong những lý do tại sao Python là rất mát mẻ. Nhưng đây là một câu chuyện khác, cho một câu hỏi khác ...

Bạn có thể dừng ở đây hoặc đọc một chút để xem việc sử dụng máy phát điện nâng cao:

Kiểm soát cạn kiệt máy phát

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

Chú thích: Đối với Python 3, sử dụngprint(corner_street_atm.__next__()) hoặc là print(next(corner_street_atm))

Nó có thể hữu ích cho nhiều thứ khác nhau như kiểm soát quyền truy cập vào tài nguyên.

Itertools, người bạn tốt nhất của bạn

Mô đun itertools chứa các hàm đặc biệt để thao tác các vòng lặp. Bao giờ muốn sao chép một máy phát điện? Chuỗi hai máy phát điện? Các giá trị nhóm trong một danh sách lồng nhau với một lớp lót? Map / Zip mà không tạo danh sách khác?

Sau đó chỉ import itertools.

Một ví dụ? Hãy xem các mệnh lệnh có thể đến cho một cuộc đua bốn ngựa:

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

Hiểu các cơ chế bên trong của sự lặp lại

Lặp lại là một quá trình ngụ ý các vòng lặp (thực hiện __iter__() phương thức) và các trình vòng lặp (thực hiện __next__() phương pháp). Iterables là bất kỳ đối tượng nào bạn có thể nhận được một iterator từ. Các Iterator là các đối tượng cho phép bạn lặp lại trên các vòng lặp.

Có nhiều thông tin trong bài viết này về làm sao for vòng làm việc.


12189
2017-10-23 22:48



Tất cả các các trình vòng lặp chỉ có thể được lặp lại một lần, không chỉ các trình lặp được tạo ra bởi các hàm máy phát. Nếu bạn không tin tôi, hãy gọi iter() trên bất kỳ đối tượng có thể lặp nào và cố gắng lặp lại kết quả nhiều lần. - augurar
@Craicerjack Bạn có cụm từ của bạn trộn lẫn. An iterable là một cái gì đó với một __iter__ phương pháp. Trình lặp là kết quả của việc gọi iter() trên một iterable. Iterator chỉ có thể được lặp lại một lần. - augurar
yield không phải là điều kỳ diệu mà câu trả lời này gợi ý. Khi bạn gọi một hàm có chứa yield tuyên bố ở bất cứ đâu, bạn nhận được một đối tượng máy phát điện, nhưng không có mã nào chạy. Sau đó, mỗi lần bạn trích xuất một đối tượng từ trình tạo, Python thực thi mã trong hàm cho đến khi nó xuất hiện yield tuyên bố, sau đó tạm dừng và phân phối đối tượng. Khi bạn trích xuất một đối tượng khác, Python sẽ tiếp tục ngay sau yield và tiếp tục cho đến khi nó đạt đến yield (thường là cùng một, nhưng một lần lặp lại sau). Điều này tiếp tục cho đến khi chức năng chạy hết, tại thời điểm đó máy phát điện được coi là đã cạn kiệt. - Matthias Fripp
@MatthiasFripp đã chỉ ra chính xác điều gì làm tôi vấp ngã khi đọc câu trả lời này. "mã của bạn sẽ được chạy mỗi lần" là một tuyên bố gây hiểu lầm. "Mã của bạn sẽ tiếp tục từ nơi nó bị tắt" là một cách chính xác hơn để cụm từ nó. - Indigenuity
"Những vòng lặp này có ích ... nhưng bạn lưu trữ tất cả các giá trị trong bộ nhớ và điều này không phải lúc nào cũng là thứ bạn muốn", hoặc là sai hoặc gây nhầm lẫn. Một iterable trả về một iterator khi gọi iter () trên iterable, và một iterator không phải lúc nào cũng phải lưu trữ các giá trị của nó trong bộ nhớ, tùy thuộc vào việc thực thi lặp lại , nó cũng có thể tạo ra các giá trị theo thứ tự theo yêu cầu. - picmate 涅


Phím tắt để Grokking  yield

Khi bạn thấy một hàm có yield báo cáo, áp dụng thủ thuật dễ dàng này để hiểu điều gì sẽ xảy ra:

  1. Chèn một dòng result = [] ở đầu hàm.
  2. Thay thế từng yield expr với result.append(expr).
  3. Chèn một dòng return result ở dưới cùng của hàm.
  4. Yay - không còn nữa yield các câu lệnh! Đọc và tìm ra mã.
  5. So sánh hàm với định nghĩa gốc.

Bí quyết này có thể cung cấp cho bạn ý tưởng về logic đằng sau hàm, nhưng điều thực sự xảy ra với yield khác biệt đáng kể so với những gì xảy ra trong cách tiếp cận dựa trên danh sách. Trong nhiều trường hợp, cách tiếp cận năng suất sẽ có nhiều bộ nhớ hiệu quả hơn và nhanh hơn. Trong các trường hợp khác, thủ thuật này sẽ khiến bạn bị mắc kẹt trong một vòng lặp vô hạn, mặc dù hàm ban đầu chỉ hoạt động tốt. Đọc tiếp để tìm hiểu thêm ...

Đừng nhầm lẫn giữa Iterables, Iterators và Generators của bạn

Đầu tiên giao thức lặp - khi bạn viết

for x in mylist:
    ...loop body...

Python thực hiện hai bước sau:

  1. Nhận một trình lặp cho mylist:

    Gọi điện iter(mylist) -> điều này trả về một đối tượng với một next() phương pháp (hoặc __next__() trong Python 3).

    [Đây là bước mà hầu hết mọi người quên nói với bạn về]

  2. Sử dụng trình vòng lặp để lặp qua các mục:

    Tiếp tục gọi next() phương thức trên trình vòng lặp được trả về từ bước 1. Giá trị trả về từ next() được gán cho x và thân vòng lặp được thực hiện. Nếu một ngoại lệ StopIteration được nâng lên từ bên trong next(), nó có nghĩa là không còn giá trị nào trong vòng lặp và vòng lặp được thoát.

Sự thật là Python thực hiện hai bước trên bất cứ lúc nào nó muốn vòng qua nội dung của một đối tượng - vì vậy nó có thể là một vòng lặp for, nhưng nó cũng có thể là mã như otherlist.extend(mylist) (Ở đâu otherlist là một danh sách Python).

Đây mylist là một có thể lặp lại vì nó thực hiện giao thức lặp. Trong lớp do người dùng xác định, bạn có thể triển khai __iter__() để tạo các phiên bản của lớp của bạn có thể lặp lại. Phương thức này sẽ trả về một người lặp. Trình lặp là một đối tượng với một next() phương pháp. Có thể thực hiện cả hai __iter__() và next() trên cùng một lớp, và có __iter__() trở về self. Điều này sẽ làm việc cho các trường hợp đơn giản, nhưng không phải khi bạn muốn hai vòng lặp lặp trên cùng một đối tượng cùng một lúc.

Vì vậy, đó là giao thức lặp, nhiều đối tượng thực hiện giao thức này:

  1. Danh sách tích hợp, từ điển, bộ dữ liệu, tập hợp, tệp.
  2. Các lớp do người dùng định nghĩa triển khai __iter__().
  3. Máy phát điện.

Lưu ý rằng for vòng lặp không biết loại đối tượng mà nó đang xử lý - nó chỉ tuân theo giao thức vòng lặp và rất vui khi nhận được mục sau mục khi nó gọi next(). Danh sách tích hợp trả về từng mục của họ, từ điển sẽ trả về phím từng cái một, các tệp trả về dòng từng người một, vv Và máy phát điện trở lại ... đó là nơi yield vào đi:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Thay vì yield báo cáo, nếu bạn có ba return báo cáo trong f123() chỉ lần đầu tiên sẽ được thực hiện, và hàm sẽ thoát. Nhưng f123() không có chức năng bình thường. Khi nào f123() được gọi là nó không làm trả về bất kỳ giá trị nào trong báo cáo lợi nhuận! Nó trả về một đối tượng máy phát điện. Ngoài ra, hàm không thực sự thoát - nó đi vào trạng thái treo. Khi mà for vòng lặp cố gắng lặp qua đối tượng máy phát, hàm sẽ tiếp tục từ trạng thái bị treo của nó ở dòng tiếp theo sau yield trước đây nó đã quay trở lại từ, thực hiện dòng mã tiếp theo, trong trường hợp này là yield và trả về điều đó làm mục tiếp theo. Điều này xảy ra cho đến khi chức năng thoát, lúc đó máy phát điện tăng StopIterationvà vòng lặp thoát.

Vì vậy, đối tượng máy phát điện là loại giống như một bộ chuyển đổi - ở một đầu nó thể hiện giao thức lặp, bằng cách lộ __iter__() và next() phương pháp để giữ for vòng lặp hạnh phúc. Tuy nhiên, ở đầu kia, nó chạy chức năng vừa đủ để có được giá trị tiếp theo của nó, và đặt nó trở lại trong chế độ bị treo.

Tại sao sử dụng máy phát điện?

Thông thường bạn có thể viết mã mà không sử dụng máy phát điện nhưng thực hiện cùng một logic. Một lựa chọn là sử dụng danh sách tạm thời 'lừa' tôi đã đề cập trước đây. Điều đó sẽ không hoạt động trong mọi trường hợp, ví dụ: nếu bạn có vòng lặp vô hạn, hoặc nó có thể làm cho việc sử dụng bộ nhớ không hiệu quả khi bạn có một danh sách thực sự dài. Cách tiếp cận khác là triển khai một lớp lặp mới SomethingIter giữ trạng thái trong các cá thể thành viên và thực hiện bước hợp lý tiếp theo trong đó next() (hoặc là __next__() trong Python 3) phương pháp. Tùy thuộc vào logic, mã bên trong next() phương pháp có thể kết thúc trông rất phức tạp và dễ bị lỗi. Ở đây máy phát điện cung cấp một giải pháp sạch sẽ và dễ dàng.


1638
2017-10-25 21:22



"Khi bạn nhìn thấy một hàm có báo cáo lợi nhuận, hãy áp dụng thủ thuật đơn giản này để hiểu điều gì sẽ xảy ra" Điều này không hoàn toàn bỏ qua thực tế là bạn có thể send vào một máy phát điện, đó là một phần rất lớn của các điểm của máy phát điện? - DanielSank
"nó có thể là một vòng lặp for, nhưng nó cũng có thể là mã như otherlist.extend(mylist)"-> Điều này là không chính xác. extend() sửa đổi danh sách tại chỗ và không trả về một lần lặp. Đang cố gắng lặp lại otherlist.extend(mylist) sẽ thất bại với một TypeError bởi vì extend() hoàn trả Nonevà bạn không thể lặp lại None. - Pedro
@pedro Bạn đã hiểu lầm câu đó. Nó có nghĩa là python thực hiện hai bước được đề cập trên mylist (không phải trên otherlist) khi thực thi otherlist.extend(mylist). - today


Nghĩ theo cách này:

Một iterator chỉ là một thuật ngữ âm thanh lạ mắt cho một đối tượng có một phương thức next (). Vì vậy, một chức năng năng suất-ed kết thúc lên được một cái gì đó như thế này:

Phiên bản gốc:

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

Về cơ bản, đây là những gì mà trình thông dịch Python thực hiện với mã trên:

class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i

Để hiểu rõ hơn về những gì đang diễn ra đằng sau hậu trường, for vòng lặp có thể được viết lại cho điều này:

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

Điều đó có ý nghĩa hơn hay chỉ gây nhầm lẫn cho bạn nhiều hơn? :)

Tôi nên lưu ý rằng điều này  một sự đơn giản hóa cho các mục đích minh họa. :)


397
2017-10-23 22:28



__getitem__ có thể được xác định thay vì __iter__. Ví dụ: class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i), Nó sẽ in: 0, 10, 20, ..., 90 - jfs
Tôi đã thử ví dụ này trong Python 3.6 và nếu tôi tạo iterator = some_function(), biến iterator không có hàm gọi là next() nữa, nhưng chỉ một __next__() chức năng. Nghĩ rằng tôi sẽ đề cập đến nó. - Peter


Các yield từ khóa được giảm xuống thành hai sự kiện đơn giản:

  1. Nếu trình biên dịch phát hiện yield từ khóa bất cứ đâu bên trong một hàm, hàm đó không còn trả về thông qua return tuyên bố. Thay thế, nó ngay trả về một đối tượng lười biếng "đang chờ xử lý" được gọi là máy phát điện
  2. Một máy phát có thể lặp lại được. Cái gì là có thể lặp lại? Nó giống như một list hoặc là set hoặc là range hoặc dict-view, với một được xây dựng trong giao thức để truy cập từng phần tử theo một thứ tự nhất định.

Tóm lại: một máy phát điện là một danh sách lười biếng, tăng dầnyield câu lệnh cho phép bạn sử dụng ký pháp hàm để lập trình các giá trị danh sách máy phát điện sẽ tăng dần.

generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

Thí dụ

Hãy định nghĩa một hàm makeRange giống như Python range. Đang gọi makeRange(n) RETURNS A GENERATOR:

def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

Để buộc máy phát điện trả lại ngay các giá trị đang chờ xử lý của nó, bạn có thể chuyển nó vào list() (giống như bạn có thể lặp lại):

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

So sánh ví dụ về "chỉ trả lại danh sách"

Ví dụ trên có thể được coi là chỉ đơn thuần tạo danh sách mà bạn thêm vào và trả về:

# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]

Có một sự khác biệt lớn, mặc dù; xem phần cuối cùng.


Cách bạn có thể sử dụng máy phát điện

Một iterable là phần cuối cùng của một danh sách hiểu, và tất cả các máy phát điện có thể lặp lại, vì vậy chúng thường được sử dụng như vậy:

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

Để có cảm giác tốt hơn cho máy phát điện, bạn có thể chơi với itertools module (hãy chắc chắn sử dụng chain.from_iterable thay vì chain khi được bảo hành). Ví dụ, bạn thậm chí có thể sử dụng máy phát điện để thực hiện các danh sách lười biếng dài vô hạn như itertools.count(). Bạn có thể thực hiện của riêng bạn def enumerate(iterable): zip(count(), iterable), hoặc cách khác làm như vậy với yieldtừ khóa trong một vòng lặp while.

Xin lưu ý: máy phát điện thực sự có thể được sử dụng cho nhiều thứ khác, chẳng hạn như triển khai coroutines hoặc lập trình không xác định hoặc những thứ tao nhã khác. Tuy nhiên, quan điểm "danh sách lười biếng" mà tôi trình bày ở đây là cách sử dụng phổ biến nhất mà bạn sẽ tìm thấy.


Đằng sau hậu trường

Đây là cách "giao thức lặp lại Python" hoạt động. Đó là, những gì đang xảy ra khi bạn làm list(makeRange(5)). Đây là những gì tôi mô tả trước đó như là một "danh sách gia tăng, lười biếng".

>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Chức năng tích hợp sẵn next() chỉ cần gọi các đối tượng .next() , là một phần của "giao thức lặp" và được tìm thấy trên tất cả các trình lặp. Bạn có thể sử dụng thủ công next() chức năng (và các phần khác của giao thức lặp lại) để thực hiện những điều ưa thích, thường là tại các chi phí dễ đọc, vì vậy hãy cố gắng tránh làm điều đó ...


Minutiae

Thông thường, hầu hết mọi người sẽ không quan tâm đến những khác biệt sau và có thể muốn dừng đọc ở đây.

Trong Python-nói, một có thể lặp lại là bất kỳ đối tượng nào "hiểu khái niệm về vòng lặp" như danh sách [1,2,3]và một người lặp là một trường hợp cụ thể của yêu cầu cho vòng lặp như [1,2,3].__iter__(). A máy phát điện chính xác giống như bất kỳ trình lặp nào, ngoại trừ cách nó được viết (với cú pháp hàm).

Khi bạn yêu cầu một trình vòng lặp từ một danh sách, nó tạo ra một trình lặp mới. Tuy nhiên, khi bạn yêu cầu một iterator từ một iterator (mà bạn hiếm khi làm), nó chỉ cung cấp cho bạn một bản sao của chính nó.

Vì vậy, trong trường hợp không chắc rằng bạn không làm điều gì đó như thế này ...

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

... thì hãy nhớ rằng một máy phát điện là một người lặp; đó là, nó là một lần sử dụng. Nếu bạn muốn sử dụng lại nó, bạn nên gọi myRange(...) lần nữa. Nếu bạn cần sử dụng kết quả hai lần, hãy chuyển đổi kết quả thành danh sách và lưu trữ nó trong một biến x = list(myRange(5)). Những người hoàn toàn cần phải sao chép một máy phát điện (ví dụ, những người đang làm terrifyingly hackish metaprogramming) có thể sử dụng itertools.tee nếu hoàn toàn cần thiết, kể từ khi Python lặp lặp lại được PEP đề xuất tiêu chuẩn đã được hoãn lại.


348
2018-06-19 06:33





Cái gì yield từ khóa nào trong Python?

Trả lời Tóm tắt / Tóm tắt

  • Một hàm với yield, khi được gọi, trả về một Máy phát điện.
  • Các trình tạo là các trình vòng lặp vì chúng triển khai thực hiện giao thức lặp, vì vậy bạn có thể lặp lại chúng.
  • Một máy phát điện cũng có thể thông tin đã gửi, làm cho nó một cách khái niệm coroutine.
  • Trong Python 3, bạn có thể ủy quyền từ máy phát này sang máy phát khác theo cả hai hướng với yield from.
  • (Phụ lục phê bình một vài câu trả lời, kể cả câu trả lời hàng đầu và thảo luận về việc sử dụng return trong một máy phát điện.)

Máy phát điện:

yield chỉ là hợp pháp bên trong định nghĩa hàm và sự bao gồm yield trong định nghĩa hàm làm cho nó trả về một trình tạo.

Ý tưởng cho máy phát điện đến từ các ngôn ngữ khác (xem chú thích 1) với các triển khai khác nhau. Trong các Trình tạo của Python, việc thực thi mã là đông lạnh tại điểm sản lượng. Khi máy phát được gọi (phương pháp được thảo luận dưới đây) thực hiện hồ sơ và sau đó đóng băng ở sản lượng tiếp theo.

yield cung cấp một cách dễ dàng triển khai giao thức lặp, được xác định bởi hai phương pháp sau: __iter__ và next (Python 2) hoặc __next__ (Python 3). Cả hai phương pháp đó tạo một đối tượng một trình lặp mà bạn có thể gõ-kiểm tra với Iterator Cơ sở trừu tượng Lớp học từ collections mô-đun.

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

Loại máy phát điện là một loại phụ của trình lặp:

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

Và nếu cần thiết, chúng ta có thể gõ-kiểm tra như thế này:

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

Một tính năng của một Iterator  là một khi kiệt sức, bạn không thể sử dụng lại hoặc đặt lại:

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

Bạn sẽ phải tạo một cái khác nếu bạn muốn sử dụng lại chức năng của nó (xem chú thích 2):

>>> list(func())
['I am', 'a generator!']

Người ta có thể tạo dữ liệu theo chương trình, ví dụ:

def func(an_iterable):
    for item in an_iterable:
        yield item

Trình tạo đơn giản ở trên cũng tương đương với dưới đây - như của Python 3.3 (và không có sẵn trong Python 2), bạn có thể sử dụng yield from:

def func(an_iterable):
    yield from an_iterable

Tuy nhiên, yield from cũng cho phép ủy quyền cho các nhà phát triển phụ, Điều này sẽ được giải thích trong phần sau đây về hợp tác xã với các tiểu ban.

Coroutines:

yield tạo biểu thức cho phép dữ liệu được gửi vào trình tạo (xem chú thích 3)

Đây là một ví dụ, lưu ý received biến, sẽ trỏ đến dữ liệu được gửi tới trình tạo:

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

Đầu tiên, chúng ta phải xếp hàng máy phát điện với hàm dựng sẵn, next. Nó sẽ gọi thích hợp next hoặc là __next__ phương pháp, tùy thuộc vào phiên bản Python bạn đang sử dụng:

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

Và bây giờ chúng ta có thể gửi dữ liệu vào máy phát. (Gửi None Là giống như gọi next.):

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

Đoàn hợp tác với Tiểu ban với yield from

Bây giờ, nhớ lại rằng yield from có sẵn trong Python 3. Điều này cho phép chúng tôi ủy quyền coroutines đến một subcoroutine:

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

Và bây giờ chúng ta có thể ủy quyền chức năng cho một máy phát điện phụ và nó có thể được sử dụng bởi một máy phát điện như trên:

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

Bạn có thể đọc thêm về ngữ nghĩa chính xác của yield from trong PEP 380.

Các phương pháp khác: đóng và ném

Các close phương pháp tăng GeneratorExit tại điểm chức năng thực hiện đã bị đóng băng. Điều này cũng sẽ được gọi bởi __del__ vậy bạn có thể đặt bất kỳ mã dọn dẹp nào mà bạn xử lý GeneratorExit:

>>> my_account.close()

Bạn cũng có thể ném một ngoại lệ có thể được xử lý trong trình tạo hoặc được truyền lại cho người dùng:

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

Phần kết luận

Tôi tin rằng tôi đã đề cập đến tất cả các khía cạnh của câu hỏi sau:

Cái gì yield từ khóa nào trong Python?

Nó chỉ ra rằng yield rất nhiều. Tôi chắc rằng tôi có thể thêm nhiều hơn các ví dụ kỹ lưỡng cho điều này. Nếu bạn muốn nhiều hơn hoặc có một số lời chỉ trích mang tính xây dựng, hãy cho tôi biết bằng cách bình luận phía dưới.


Ruột thừa:

Phê bình câu trả lời hàng đầu / được chấp nhận **

  • Nó là bối rối về những gì làm cho một có thể lặp lại, chỉ cần sử dụng danh sách làm ví dụ. Xem các tham chiếu của tôi ở trên, nhưng tóm lại: một lần lặp lại có một __iter__ phương thức trả về một người lặp. An người lặp cung cấp một .next (Python 2 hoặc .__next__ (Python 3) phương pháp, được ngầm gọi bởi forvòng cho đến khi nó tăng StopIterationvà một khi có, nó sẽ tiếp tục làm như vậy.
  • Sau đó nó sử dụng một biểu thức máy phát điện để mô tả máy phát điện là gì. Vì máy phát điện chỉ đơn giản là cách thuận tiện để tạo người lặp, nó chỉ gây nhầm lẫn cho vấn đề, và chúng tôi vẫn chưa đến được yield phần.
  • Trong Kiểm soát cạn kiệt máy phát anh ấy gọi .next phương pháp, khi thay vào đó, anh ta nên sử dụng hàm dựng sẵn, next. Nó sẽ là một lớp vô hướng thích hợp, bởi vì mã của anh ta không hoạt động trong Python 3.
  • Itertools? Điều này không liên quan đến những gì yield làm gì cả.
  • Không có thảo luận về các phương pháp yield cung cấp cùng với chức năng mới yield from trong Python 3. Câu trả lời hàng đầu / được chấp nhận là một câu trả lời rất không đầy đủ.

Phê bình câu trả lời gợi ý yield trong biểu thức máy phát hoặc hiểu.

Ngữ pháp hiện cho phép bất kỳ biểu thức nào trong một danh sách hiểu.

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

Vì năng suất là một biểu thức, nó đã được chào mời bởi một số thú vị để sử dụng nó trong sự hiểu biết hoặc biểu thức máy phát điện - mặc dù trích dẫn không có trường hợp sử dụng đặc biệt tốt.

Các nhà phát triển lõi CPython thảo luận về việc không chấp nhận trợ cấp. Đây là bài đăng có liên quan từ danh sách gửi thư:

Vào ngày 30 tháng 1 năm 2017 lúc 19:05, Brett Cannon đã viết:

On Sun, ngày 29 tháng 1 năm 2017 lúc 16:39 Craig Rodrigues viết:

Tôi đồng ý với một trong hai cách tiếp cận. Để lại mọi thứ theo cách của chúng trong Python 3       không tốt, IMHO.

Bỏ phiếu của tôi là nó là một SyntaxError vì bạn không nhận được những gì bạn mong đợi từ     cú pháp.

Tôi đồng ý rằng đó là một nơi hợp lý để chúng tôi kết thúc, như bất kỳ mã nào   dựa vào hành vi hiện tại thực sự là quá thông minh để   có thể duy trì.

Về việc đạt được điều đó, chúng tôi có thể sẽ muốn:

  • Cú phápWarning hoặc DeprecationCảnh báo trong 3.7
  • Cảnh báo Py3k trong 2.7.x
  • Cú pháp Cú pháp trong 3.8

Chúc mừng, Nick.

- Nick Coghlan | ncoghlan tại gmail.com | thành phố ven sông Brisbane, là thủ phủ của Qeensland, miền đông nước Úc

Hơn nữa, có một vấn đề nổi bật (10544) dường như chỉ theo hướng này không bao giờ là một ý tưởng hay (PyPy, một triển khai Python được viết bằng Python, đã tăng cảnh báo cú pháp.)

Tóm lại, cho đến khi các nhà phát triển của CPython cho chúng tôi biết cách khác: Đừng đặt yield trong biểu thức máy phát hoặc hiểu.

Các return tuyên bố trong một máy phát điện

Trong Python 2:

Trong một chức năng máy phát điện, return tuyên bố không được phép bao gồm expression_list. Trong bối cảnh đó, một trần nhà return chỉ ra rằng máy phát điện được thực hiện và sẽ gây ra StopIteration được nuôi dưỡng.

An expression_list về cơ bản là bất kỳ số biểu thức nào được phân tách bằng dấu phẩy - về cơ bản, trong Python 2, bạn có thể dừng trình tạo return, nhưng bạn không thể trả về một giá trị.

Trong Python 3:

Trong một chức năng máy phát điện, return tuyên bố chỉ ra rằng máy phát điện được thực hiện và sẽ gây ra StopIteration được nuôi dưỡng. Giá trị trả về (nếu có) được sử dụng như một đối số để xây dựng StopIteration và trở thành StopIteration.valuethuộc tính.

Chú thích

  1. Các ngôn ngữ CLU, Sather và Icon được tham chiếu trong đề xuất giới thiệu khái niệm về máy phát điện cho Python. Ý tưởng chung là rằng một hàm có thể duy trì trạng thái bên trong và năng suất trung gian điểm dữ liệu theo yêu cầu của người dùng. Điều này hứa hẹn sẽ là vượt trội về hiệu suất với các cách tiếp cận khác, bao gồm cả luồng Python, thậm chí không có sẵn trên một số hệ thống.

  2.  Điều này có nghĩa là, ví dụ, xrange các đối tượng (range trong Python 3) không Iterators, mặc dù chúng có thể lặp lại được, vì chúng có thể được sử dụng lại. Giống như danh sách, __iter__ các phương thức trả về các đối tượng iterator.

  3. yield ban đầu được giới thiệu như một tuyên bố, có nghĩa là nó chỉ có thể xuất hiện ở đầu dòng trong một khối mã. Hiện nay yield tạo biểu thức lợi nhuận. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt  Thay đổi này là đề xuất để cho phép người dùng gửi dữ liệu vào máy phát người ta có thể nhận được nó. Để gửi dữ liệu, người ta phải có khả năng gán nó cho một cái gì đó, và cho rằng, một tuyên bố sẽ không hoạt động.


254
2018-06-25 06:11





yield giống như return - nó trả về bất cứ điều gì bạn nói với nó (như một máy phát điện). Sự khác biệt là lần sau khi bạn gọi máy phát, thực hiện bắt đầu từ cuộc gọi cuối cùng đến yield tuyên bố. Không giống như trả lại, khung ngăn xếp không được làm sạch khi một sản lượng xảy ra, tuy nhiên điều khiển được chuyển trở lại cho người gọi, vì vậy trạng thái của nó sẽ tiếp tục trong lần tiếp theo chức năng.

Trong trường hợp mã của bạn, hàm get_child_candidates hoạt động như một trình lặp để khi bạn mở rộng danh sách, nó sẽ thêm một phần tử vào một danh sách mới.

list.extend gọi một trình lặp cho đến khi nó cạn kiệt. Trong trường hợp mẫu mã bạn đã đăng, sẽ rõ ràng hơn nhiều khi chỉ trả lại một bộ dữ liệu và thêm vào danh sách đó.


230
2017-10-23 22:24



Điều này là gần, nhưng không chính xác. Mỗi khi bạn gọi một hàm với một tuyên bố lợi nhuận trong nó, nó trả về một đối tượng máy phát điện mới. Chỉ khi bạn gọi phương thức .next () của máy phát điện đó thực hiện tiếp tục sau lần sinh lợi cuối cùng. - kurosch
Một cái gì đó dường như bị mất tích "sẽ tiếp tục lần sau khi hàm". Nó phải được "sẽ tiếp tục lần sau khi hàm chạy"? - Peter Mortensen


Có một điều nữa cần đề cập: một chức năng mang lại không thực sự phải chấm dứt. Tôi đã viết mã như thế này:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

Sau đó, tôi có thể sử dụng nó trong mã khác như thế này:

for f in fib():
    if some_condition: break
    coolfuncs(f);

Nó thực sự giúp đơn giản hóa một số vấn đề, và làm cho một số điều dễ dàng hơn để làm việc với.


182
2017-10-24 08:44





Đối với những người thích một ví dụ làm việc tối thiểu, hãy suy nghĩ về tương tác này Python phiên:

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed

155
2018-01-18 17:25



Điều này không trả lời câu hỏi - ppperry


Năng suất cung cấp cho bạn một máy phát điện.

def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

Như bạn thấy, trong trường hợp đầu tiên foo giữ toàn bộ danh sách trong bộ nhớ cùng một lúc. Nó không phải là một việc lớn đối với một danh sách với 5 yếu tố, nhưng nếu bạn muốn một danh sách 5 triệu? Không chỉ là một người ăn nhớ lớn, nó cũng tốn rất nhiều thời gian để xây dựng tại thời điểm hàm được gọi. Trong trường hợp thứ hai, thanh chỉ cung cấp cho bạn một máy phát điện. Một máy phát là một vòng lặp - có nghĩa là bạn có thể sử dụng nó trong vòng lặp for, vv, nhưng mỗi giá trị chỉ có thể được truy cập một lần. Tất cả các giá trị cũng không được lưu trữ trong bộ nhớ cùng một lúc; đối tượng máy phát điện "ghi nhớ" nơi nó đang ở trong vòng lặp lần cuối bạn gọi nó - theo cách này, nếu bạn đang sử dụng số lần lặp lại (nói) lên tới 50 tỷ, bạn không phải tính đến 50 tỷ tất cả cùng một lúc và lưu trữ 50 tỷ con số để đếm qua. Một lần nữa, đây là một ví dụ khá giả tạo, bạn có thể sẽ sử dụng itertools nếu bạn thực sự muốn đếm đến 50 tỷ. :)

Đây là trường hợp sử dụng đơn giản nhất của máy phát điện. Như bạn đã nói, nó có thể được sử dụng để viết hoán vị hiệu quả, sử dụng năng suất để đẩy mọi thứ lên qua ngăn xếp cuộc gọi thay vì sử dụng một số loại biến ngăn xếp. Các máy phát điện cũng có thể được sử dụng cho việc di chuyển cây đặc biệt và tất cả các cách khác.


133
2018-01-16 06:42





Nó trả về một máy phát điện. Tôi không quen thuộc với Python, nhưng tôi tin rằng đó là một thứ giống như Các khối lặp của C # nếu bạn quen thuộc với những người đó.

Có một Bài viết của IBM giải thích nó một cách hợp lý (đối với Python) theo như tôi thấy.

Ý tưởng quan trọng là trình biên dịch / thông dịch viên / bất cứ điều gì làm một số trickery để như xa như người gọi là có liên quan, họ có thể tiếp tục gọi tiếp theo () và nó sẽ tiếp tục trở về giá trị - như thể phương pháp máy phát đã bị tạm dừng. Bây giờ rõ ràng bạn không thể thực sự "tạm dừng" một phương thức, do đó trình biên dịch xây dựng một máy trạng thái để bạn nhớ vị trí hiện tại của bạn và các biến cục bộ, v.v. Điều này dễ dàng hơn nhiều so với việc tự viết một trình lặp.


125
2017-10-23 22:26





Có một loại câu trả lời mà tôi chưa cảm thấy được đưa ra, trong số rất nhiều câu trả lời tuyệt vời mô tả cách sử dụng máy phát điện. Đây là câu trả lời về lý thuyết ngôn ngữ lập trình:

Các yield câu lệnh trong Python trả về một trình tạo. Một máy phát điện trong Python là một hàm trả về tiếp tục (và đặc biệt là một loại coroutine, nhưng sự tiếp tục đại diện cho cơ chế tổng quát hơn để hiểu những gì đang xảy ra).

Sự tiếp tục trong lý thuyết ngôn ngữ lập trình là một loại tính toán cơ bản hơn nhiều, nhưng chúng không thường được sử dụng, bởi vì chúng cực kỳ khó để giải thích và cũng rất khó thực hiện. Nhưng ý tưởng về việc tiếp tục là gì, là đơn giản: đó là trạng thái của một tính toán chưa hoàn thành. Trong trạng thái này, các giá trị hiện tại của các biến, các hoạt động chưa được thực hiện, vv được lưu lại. Sau đó, tại một số điểm sau đó trong chương trình, việc tiếp tục có thể được gọi, như vậy các biến của chương trình được đặt lại về trạng thái đó và các thao tác đã được lưu được thực hiện.

Tiếp tục, trong hình thức tổng quát hơn này, có thể được thực hiện theo hai cách. bên trong call/cc cách, ngăn xếp của chương trình là nghĩa đen được lưu và sau đó khi tiếp tục được gọi, ngăn xếp được khôi phục.

Trong kiểu chuyển tiếp tiếp tục (CPS), các phép tiếp tục chỉ là các hàm bình thường (chỉ trong các ngôn ngữ mà các hàm là lớp đầu tiên) mà lập trình viên quản lý một cách rõ ràng và truyền xung quanh các chương trình con. Theo kiểu này, trạng thái chương trình được biểu diễn bằng các bao đóng (và các biến xảy ra để được mã hóa trong chúng) thay vì các biến nằm ở đâu đó trên ngăn xếp. Các hàm quản lý luồng điều khiển chấp nhận tiếp tục làm đối số (trong một số biến thể của CPS, hàm có thể chấp nhận nhiều lần tiếp tục) và thao tác luồng điều khiển bằng cách gọi chúng bằng cách đơn giản gọi chúng và quay trở lại sau đó. Một ví dụ rất đơn giản về kiểu chuyển tiếp tiếp tục như sau:

def save_file(filename):
  def write_file_continuation():
    write_stuff_to_file(filename)

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

Trong ví dụ này (rất đơn giản), lập trình viên lưu hoạt động thực sự ghi tệp vào một sự tiếp tục (có khả năng là một hoạt động rất phức tạp với nhiều chi tiết để viết ra), và sau đó chuyển tiếp đó (ví dụ, như một đóng cửa lớp) cho một nhà điều hành khác xử lý thêm, và sau đó gọi nó nếu cần thiết. (Tôi sử dụng mẫu thiết kế này rất nhiều trong lập trình GUI thực tế, vì nó giúp tôi tiết kiệm các dòng mã hoặc quan trọng hơn là quản lý luồng điều khiển sau khi các sự kiện GUI kích hoạt.)

Phần còn lại của bài viết này sẽ không mất tính tổng quát, khái niệm hóa các sự tiếp tục như CPS, bởi vì nó dễ hiểu và dễ đọc hơn nhiều.


Bây giờ chúng ta hãy nói về các máy phát điện bằng Python. Máy phát điện là một loại phụ cụ thể của việc tiếp tục. Trong khi tiếp tục có thể nói chung để lưu trạng thái của một tính toán (tức là, ngăn xếp cuộc gọi của chương trình), máy phát điện chỉ có thể lưu trạng thái lặp lại qua người lặp. Mặc dù, định nghĩa này hơi gây nhầm lẫn cho một số trường hợp sử dụng máy phát điện. Ví dụ:

def f():
  while True:
    yield 4

Điều này rõ ràng là một phép lặp hợp lý mà hành vi của nó được xác định rõ - mỗi lần máy phát lại lặp lại nó, nó trả về 4 (và thực hiện mãi mãi). Nhưng nó không phải là kiểu nguyên mẫu có thể lặp lại mà bạn nghĩ đến khi nghĩ về các trình lặp (tức là, for x in collection: do_something(x)). Ví dụ này minh họa sức mạnh của các máy phát điện: nếu bất cứ thứ gì là một trình lặp, máy phát có thể lưu trạng thái của phép lặp của nó.

Để nhắc lại: Tiếp tục có thể lưu trạng thái ngăn xếp của một chương trình và máy phát có thể lưu trạng thái lặp lại. Điều này có nghĩa là việc tiếp tục mạnh hơn rất nhiều so với máy phát điện, nhưng cũng có thể tạo ra rất nhiều, dễ dàng hơn rất nhiều. Chúng dễ dàng hơn cho các nhà thiết kế ngôn ngữ để thực hiện, và họ dễ dàng hơn cho các lập trình viên để sử dụng (nếu bạn có thời gian để đốt cháy, cố gắng đọc và hiểu trang này về tiếp tục và gọi / cc).

Nhưng bạn có thể dễ dàng thực hiện (và khái niệm hóa) máy phát điện như là một trường hợp đơn giản, cụ thể của kiểu chuyển tiếp tiếp tục:

Bất cứ khi nào yield được gọi, nó báo cho hàm trả về một sự tiếp tục. Khi chức năng được gọi lại, nó bắt đầu từ bất cứ nơi nào nó rời đi. Vì vậy, trong giả mã giả (tức là, không giả mã, nhưng không mã) của máy phát điện next phương pháp cơ bản như sau:

class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

ở đâu yield từ khóa thực sự là cú pháp đường cho chức năng máy phát thực sự, về cơ bản giống như:

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

Hãy nhớ rằng đây chỉ là giả mã và việc triển khai thực tế các trình tạo trong Python phức tạp hơn. Nhưng như là một bài tập để hiểu những gì đang xảy ra, hãy cố gắng sử dụng kiểu chuyển tiếp tiếp tục để thực hiện các đối tượng máy phát mà không cần sử dụng yield từ khóa.


120
2018-04-04 14:56