Để hiểu những gì yield
làm, bạn phải hiểu máy phát điện là gì . Và trước khi bạn có thể hiểu máy phát điện, bạn phải hiểu iterables .
Lặp lại
Khi bạn tạo một danh sách, bạn có thể đọc từng mục một. Đọc các mục của nó từng cái một được gọi là lặp:
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
là một lần lặp . Khi bạn sử dụng khả năng hiểu danh sách, bạn tạo một danh sách và do đó, có thể lặp lại:
>>> 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...
" trên là một lần lặp; lists
,, strings
tập tin ...
Các iterables 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à đây không phải lúc nào cũng là điều bạn muốn khi bạn có nhiều giá trị.
Máy phát điện
Trình tạo là các trình vòng lặp, một loại lặp mà bạn chỉ có thể lặp lại một lần . Các trình tạo không lưu trữ tất cả các giá trị trong bộ nhớ, chúng tạo ra các giá trị một cách nhanh chóng :
>>> 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ể thực hiện for i in mygenerator
lần thứ hai vì máy phát điện chỉ có thể được sử dụng một lần: họ 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ư thế return
, ngoại trừ hàm sẽ trả về một trình tạo.
>>> 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 thật tiện lợi khi bạn biết chức năng của mình sẽ trả về một tập hợp giá trị khổng lồ mà bạn sẽ chỉ cần đọc một lần.
Để thành thạoyield
, bạn phải hiểu rằng khi bạn gọi hàm, mã bạn đã viết trong thân hàm không chạy. Hàm chỉ trả về đối tượng trình tạo, điều này hơi khó :-)
Sau đó, mã của bạn sẽ tiếp tục từ nơi nó dừng lại mỗi lần for
sử dụng trình tạo.
Bây giờ là phần khó:
Lần đầu tiên for
gọi đối tượng trình tạo được tạo từ hàm của bạn, nó sẽ chạy mã trong hàm của bạn từ đầu cho đến khi 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 tiếp theo sẽ chạy một vòng lặp khác của vòng lặp mà bạn đã viết trong hàm và trả về giá trị tiếp theo. Điều này sẽ tiếp tục cho đến khi trình tạo được coi là trống, xảy ra khi chức năng chạy mà không nhấn yield
. Điều đó có thể là do vòng lặp đã kết thúc hoặc do bạn không còn thỏa mãn "if/else"
.
Mã của bạn đã 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 the 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 the 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 candidate's 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 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 này ngay cả khi nó hơi nguy hiểm vì bạn có thể kết thúc bằng 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))
làm cạn kiệt tất cả các giá trị của trình tạo, nhưng while
tiếp tục tạo các đối tượng trình tạo mới sẽ tạo ra các giá trị khác với các giá trị trước đó vì nó không được áp dụng trên cùng một nút.
Các extend()
phương pháp là một phương pháp đối tượng trong danh sách đó hy vọng một iterable và thêm giá trị của nó vào danh sách.
Thông thường chúng ta 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ó có một trình tạo, điều này tốt bởi vì:
- Bạn không cần phải đọc các giá trị hai lần.
- 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 xem đối số của phương thức có phải là danh sách hay không. Python mong đợi các lần 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! Đây được gọi là gõ vịt và là một trong những lý do tại sao Python rất tuyệt. 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 để thấy cách sử dụng nâng cao của trình tạo:
Kiểm soát kiệt sức máy phát điện
>>> 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
...
Lưu ý: Đối với Python 3, sử dụng print(corner_street_atm.__next__())
hoặcprint(next(corner_street_atm))
Nó có thể hữu ích cho những thứ khác nhau như kiểm soát 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 chức năng đặc biệt để thao tác các lần lặp. Bao giờ muốn nhân đôi một máy phát điện? Xích hai máy phát điện? Giá trị nhóm trong danh sách lồng nhau với một lớp lót? Map / Zip
mà không tạo ra một danh sách khác?
Sau đó chỉ là import itertools
.
Một ví dụ? Chúng ta hãy xem các đơn đặt hàng có thể đến cho một cuộc đua bốn con 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 phép lặp
Lặp lại là một quá trình ngụ ý lặp (thực hiện __iter__()
phương thức) và lặp (thực hiện __next__()
phương thức). Lặp lại là bất kỳ đối tượng bạn có thể nhận được một trình vòng lặp từ. Lặp đi lặp lại là các đối tượng cho phép bạn lặp trên các vòng lặp.
Có nhiều hơn về nó trong bài viết này về cách for
các vòng lặp hoạt động .
yield
không phải là huyền diệu câu trả lời này cho thấy. Khi bạn gọi một hàm có chứa mộtyield
câu lệnh ở bất cứ đâu, bạn sẽ nhận được một đối tượng trình tạo, 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ó đến mộtyield
câu lệnh, 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 tiếp tục ngay sauyield
và tiếp tục cho đến khi nó đến một đối tượng khácyield
(thường là cùng một đối tượng, 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 đó, trình tạo được coi là hết.