Sự khác biệt giữa các trình vòng lặp và máy phát điện là gì? Một số ví dụ khi bạn sử dụng mỗi trường hợp sẽ hữu ích.
Sự khác biệt giữa các trình vòng lặp và máy phát điện là gì? Một số ví dụ khi bạn sử dụng mỗi trường hợp sẽ hữu ích.
Câu trả lời:
iterator
là một khái niệm tổng quát hơn: bất kỳ đối tượng nào có lớp có một next
phương thức ( __next__
trong Python 3) và một __iter__
phương thức thực hiện return self
.
Mỗi trình tạo là một trình vòng lặp, nhưng không phải ngược lại. Một trình tạo được xây dựng bằng cách gọi một hàm có một hoặc nhiều yield
biểu thức (các yield
câu lệnh, trong Python 2.5 trở về trước) và là một đối tượng đáp ứng định nghĩa của đoạn trước đó về một iterator
.
Bạn có thể muốn sử dụng một trình lặp tùy chỉnh, thay vì trình tạo, khi bạn cần một lớp có hành vi duy trì trạng thái hơi phức tạp hoặc muốn đưa ra các phương thức khác bên cạnh next
( __iter__
và __init__
). Thông thường, một trình tạo (đôi khi, cho các nhu cầu đủ đơn giản, biểu thức của trình tạo ) là đủ và mã đơn giản hơn vì bảo trì trạng thái (trong giới hạn hợp lý) về cơ bản là "được thực hiện cho bạn" bởi khung bị treo và tiếp tục.
Ví dụ: một trình tạo như:
def squares(start, stop):
for i in range(start, stop):
yield i * i
generator = squares(a, b)
hoặc biểu thức trình tạo tương đương (genapi)
generator = (i*i for i in range(a, b))
sẽ lấy thêm mã để xây dựng như một trình vòng lặp tùy chỉnh:
class Squares(object):
def __init__(self, start, stop):
self.start = start
self.stop = stop
def __iter__(self): return self
def next(self): # __next__ in Python 3
if self.start >= self.stop:
raise StopIteration
current = self.start * self.start
self.start += 1
return current
iterator = Squares(a, b)
Nhưng, tất nhiên, với lớp Squares
bạn có thể dễ dàng cung cấp các phương thức bổ sung, tức là
def current(self):
return self.start
nếu bạn có bất kỳ nhu cầu thực tế nào cho chức năng bổ sung như vậy trong ứng dụng của bạn.
for ... in ...:
, được chuyển đến một chức năng hoặc bạn sẽ gọiiter.next()
for..in
cú pháp. Có lẽ tôi đã bỏ lỡ điều gì đó, nhưng đó là một thời gian trước đây, tôi không nhớ nếu tôi đã giải quyết. Cảm ơn bạn!
Sự khác biệt giữa các trình vòng lặp và máy phát điện là gì? Một số ví dụ khi bạn sử dụng mỗi trường hợp sẽ hữu ích.
Tóm lại: Iterators là các đối tượng có phương thức __iter__
và __next__
( next
trong Python 2). Trình tạo cung cấp một cách dễ dàng, tích hợp để tạo các phiên bản của Iterators.
Một hàm có năng suất trong nó vẫn là một hàm, khi được gọi, trả về một thể hiện của một đối tượng trình tạo:
def a_function():
"when called, returns generator object"
yield
Một biểu thức trình tạo cũng trả về một trình tạo:
a_generator = (i for i in range(0))
Đối với một ví dụ sâu hơn và ví dụ, tiếp tục đọc.
Cụ thể, trình tạo là một kiểu con của iterator.
>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True
Chúng ta có thể tạo ra một số cách. Một cách rất phổ biến và đơn giản để làm như vậy là với một hàm.
Cụ thể, một hàm có năng suất trong nó là một hàm, khi được gọi sẽ trả về một trình tạo:
>>> def a_function():
"just a function definition with yield in it"
yield
>>> type(a_function)
<class 'function'>
>>> a_generator = a_function() # when called
>>> type(a_generator) # returns a generator
<class 'generator'>
Và một máy phát điện, một lần nữa, là một Iterator:
>>> isinstance(a_generator, collections.Iterator)
True
Một Iterator là một Iterable,
>>> issubclass(collections.Iterator, collections.Iterable)
True
trong đó yêu cầu một __iter__
phương thức trả về Iterator:
>>> collections.Iterable()
Traceback (most recent call last):
File "<pyshell#79>", line 1, in <module>
collections.Iterable()
TypeError: Can't instantiate abstract class Iterable with abstract methods __iter__
Một số ví dụ về iterables là các bộ dữ liệu, danh sách, từ điển, bộ, bộ đóng băng, chuỗi, chuỗi byte, mảng byte, phạm vi và bộ nhớ:
>>> all(isinstance(element, collections.Iterable) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
next
hoặc __next__
phương thứcTrong Python 2:
>>> collections.Iterator()
Traceback (most recent call last):
File "<pyshell#80>", line 1, in <module>
collections.Iterator()
TypeError: Can't instantiate abstract class Iterator with abstract methods next
Và trong Python 3:
>>> collections.Iterator()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Iterator with abstract methods __next__
Chúng ta có thể lấy các trình vòng lặp từ các đối tượng tích hợp (hoặc các đối tượng tùy chỉnh) với iter
chức năng:
>>> all(isinstance(iter(element), collections.Iterator) for element in (
(), [], {}, set(), frozenset(), '', b'', bytearray(), range(0), memoryview(b'')))
True
Các __iter__
phương pháp được gọi là khi bạn cố gắng sử dụng một đối tượng với một vòng lặp for. Sau đó, __next__
phương thức được gọi trên đối tượng iterator để lấy từng mục ra cho vòng lặp. Trình lặp tăng lên StopIteration
khi bạn đã sử dụng hết nó và nó không thể được sử dụng lại tại thời điểm đó.
Từ phần Kiểu máy phát điện của phần Kiểu lặp trong tài liệu Kiểu tích hợp :
Các trình tạo của Python cung cấp một cách thuận tiện để thực hiện giao thức iterator. Nếu
__iter__()
phương thức của đối tượng chứa được triển khai như một trình tạo, nó sẽ tự động trả về một đối tượng lặp (về mặt kỹ thuật, một đối tượng trình tạo) cung cấp các phương thức__iter__()
vànext()
[__next__()
trong Python 3]. Thông tin thêm về máy phát điện có thể được tìm thấy trong tài liệu cho biểu thức năng suất.
(Nhấn mạnh thêm.)
Vì vậy, từ đó chúng ta biết rằng Generators là một loại Iterator (tiện lợi).
Bạn có thể tạo đối tượng thực hiện giao thức Iterator bằng cách tạo hoặc mở rộng đối tượng của riêng bạn.
class Yes(collections.Iterator):
def __init__(self, stop):
self.x = 0
self.stop = stop
def __iter__(self):
return self
def next(self):
if self.x < self.stop:
self.x += 1
return 'yes'
else:
# Iterators must raise when done, else considered broken
raise StopIteration
__next__ = next # Python 3 compatibility
Nhưng đơn giản hơn là sử dụng Trình tạo để thực hiện việc này:
def yes(stop):
for _ in range(stop):
yield 'yes'
Hoặc có lẽ đơn giản hơn, một Expression Expression (hoạt động tương tự như việc hiểu danh sách):
yes_expr = ('yes' for _ in range(stop))
Tất cả đều có thể được sử dụng theo cùng một cách:
>>> stop = 4
>>> for i, y1, y2, y3 in zip(range(stop), Yes(stop), yes(stop),
('yes' for _ in range(stop))):
... print('{0}: {1} == {2} == {3}'.format(i, y1, y2, y3))
...
0: yes == yes == yes
1: yes == yes == yes
2: yes == yes == yes
3: yes == yes == yes
Bạn có thể sử dụng giao thức Iterator trực tiếp khi bạn cần mở rộng một đối tượng Python như một đối tượng có thể được lặp lại.
Tuy nhiên, trong phần lớn các trường hợp, bạn phù hợp nhất để sử dụng yield
để xác định hàm trả về Trình tạo vòng lặp hoặc xem xét Biểu thức trình tạo.
Cuối cùng, lưu ý rằng máy phát điện cung cấp nhiều chức năng hơn như coroutines. Tôi giải thích về Máy phát điện, cùng với yield
tuyên bố, chuyên sâu về câu trả lời của tôi về "Từ khóa năng suất trực tuyến làm gì?".
Lặp lại:
Iterator là các đối tượng sử dụng next()
phương thức để nhận giá trị tiếp theo của chuỗi.
Máy phát điện:
Trình tạo là một hàm tạo hoặc tạo ra một chuỗi các giá trị bằng yield
phương thức.
Mỗi next()
phương thức gọi đối tượng trình tạo (ví dụ f
như trong ví dụ dưới đây) được trả về bởi hàm trình tạo (ví dụ: foo()
hàm trong ví dụ bên dưới), tạo ra giá trị tiếp theo theo thứ tự.
Khi một hàm tạo được gọi, nó trả về một đối tượng trình tạo mà không bắt đầu thực hiện hàm. Khi next()
phương thức được gọi lần đầu tiên, hàm bắt đầu thực thi cho đến khi đạt được câu lệnh năng suất trả về giá trị mang lại. Sản lượng theo dõi tức là ghi nhớ thực hiện cuối cùng. Và next()
cuộc gọi thứ hai tiếp tục từ giá trị trước đó.
Ví dụ sau đây cho thấy sự tương tác giữa năng suất và gọi phương thức tiếp theo trên đối tượng trình tạo.
>>> def foo():
... print "begin"
... for i in range(3):
... print "before yield", i
... yield i
... print "after yield", i
... print "end"
...
>>> f = foo()
>>> f.next()
begin
before yield 0 # Control is in for loop
0
>>> f.next()
after yield 0
before yield 1 # Continue for loop
1
>>> f.next()
after yield 1
before yield 2
2
>>> f.next()
after yield 2
end
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
Thêm một câu trả lời vì không có câu trả lời nào hiện có giải quyết cụ thể sự nhầm lẫn trong tài liệu chính thức.
Các hàm tạo là các hàm thông thường được định nghĩa bằng cách sử dụngyield
thay vìreturn
. Khi được gọi, một hàm tạo sẽ trả về một đối tượng trình tạo , đó là một loại trình vòng lặp - nó có mộtnext()
phương thức. Khi bạn gọinext()
, giá trị tiếp theo mang lại bởi hàm tạo được trả về.
Hàm hoặc đối tượng có thể được gọi là "trình tạo" tùy thuộc vào tài liệu nguồn Python nào bạn đọc. Các Python thuật ngữ nói chức năng máy phát điện, trong khi Python wiki ngụ ý đối tượng máy phát điện. Các Python hướng dẫn rõ rệt quản lý để bao hàm cả tập quán trong khoảng thời gian ba câu:
Trình tạo là một công cụ đơn giản và mạnh mẽ để tạo các trình vòng lặp. Chúng được viết như các hàm thông thường nhưng sử dụng câu lệnh lợi suất bất cứ khi nào chúng muốn trả về dữ liệu. Mỗi lần tiếp theo () được gọi trên nó, trình tạo lại tiếp tục ở nơi nó rời đi (nó nhớ tất cả các giá trị dữ liệu và câu lệnh nào được thực hiện lần cuối).
Hai câu đầu xác định trình tạo với các hàm tạo, trong khi câu thứ ba xác định chúng với các đối tượng trình tạo.
Bất chấp tất cả sự nhầm lẫn này, người ta có thể tìm kiếm tài liệu tham khảo ngôn ngữ Python cho từ rõ ràng và cuối cùng:
Biểu thức năng suất chỉ được sử dụng khi xác định hàm tạo và chỉ có thể được sử dụng trong phần thân của định nghĩa hàm. Sử dụng biểu thức năng suất trong định nghĩa hàm là đủ để khiến định nghĩa đó tạo ra hàm tạo thay vì hàm bình thường.
Khi một hàm tạo được gọi, nó trả về một iterator được gọi là một trình tạo. Máy phát đó sau đó kiểm soát việc thực hiện chức năng của máy phát.
Vì vậy, trong cách sử dụng chính thức và chính xác, "trình tạo" không đủ tiêu chuẩn có nghĩa là đối tượng trình tạo, không phải là hàm tạo.
Các tham chiếu trên dành cho Python 2 nhưng tham chiếu ngôn ngữ Python 3 nói điều tương tự. Tuy nhiên, thuật ngữ Python 3 nói rằng
trình tạo ... Thường đề cập đến chức năng của trình tạo, nhưng có thể đề cập đến trình lặp của trình tạo trong một số ngữ cảnh. Trong trường hợp ý nghĩa dự định không rõ ràng, sử dụng thuật ngữ đầy đủ sẽ tránh sự mơ hồ.
Mọi người đều có một câu trả lời thực sự hay và dài dòng với các ví dụ và tôi thực sự đánh giá cao nó. Tôi chỉ muốn đưa ra một vài câu trả lời ngắn gọn cho những người vẫn chưa hoàn toàn rõ ràng về mặt khái niệm:
Nếu bạn tạo iterator của riêng mình, nó có một chút liên quan - bạn phải tạo một lớp và ít nhất là thực hiện iter và các phương thức tiếp theo. Nhưng điều gì sẽ xảy ra nếu bạn không muốn trải qua rắc rối này và muốn nhanh chóng tạo ra một trình vòng lặp. May mắn thay, Python cung cấp một cách rút gọn để xác định một trình vòng lặp. Tất cả những gì bạn cần làm là xác định một hàm có ít nhất 1 lệnh gọi và bây giờ khi bạn gọi hàm đó, nó sẽ trả về " một cái gì đó " sẽ hoạt động như một trình vòng lặp (bạn có thể gọi phương thức tiếp theo và sử dụng nó trong một vòng lặp for). Đây một cái gì đó có một cái tên bằng Python gọi Generator
Hy vọng rằng làm rõ một chút.
Các câu trả lời trước đã bỏ qua phần bổ sung này: một trình tạo có một close
phương thức, trong khi các trình vòng lặp điển hình thì không. Các close
trigger phương pháp một StopIteration
ngoại lệ trong các máy phát điện, có thể được bắt gặp trong một finally
điều khoản trong iterator rằng, để có được một cơ hội để chạy một số dọn dẹp. Sự trừu tượng hóa này làm cho nó có thể sử dụng nhiều nhất trong các vòng lặp lớn hơn đơn giản. Người ta có thể đóng một trình tạo vì người ta có thể đóng một tệp mà không phải bận tâm về những gì bên dưới.
Điều đó nói rằng, câu trả lời cá nhân của tôi cho câu hỏi đầu tiên sẽ là: iterizable chỉ có một __iter__
phương thức, các trình vòng lặp thông thường chỉ có một __next__
phương thức, các trình tạo có cả __iter__
một __next__
và một bổ sung close
.
Đối với câu hỏi thứ hai, câu trả lời cá nhân của tôi sẽ là: trong giao diện công cộng, tôi có xu hướng thích máy phát điện hơn, vì nó có khả năng phục hồi tốt hơn: close
phương pháp có khả năng kết hợp tốt hơn yield from
. Tại địa phương, tôi có thể sử dụng các trình vòng lặp, nhưng chỉ khi đó là một cấu trúc đơn giản và phẳng (các trình vòng lặp không dễ sáng tác) và nếu có lý do để tin rằng chuỗi này khá ngắn đặc biệt là nếu nó có thể bị dừng trước khi nó kết thúc. Tôi có xu hướng xem các trình vòng lặp như một nguyên thủy cấp thấp, ngoại trừ theo nghĩa đen.
Đối với các vấn đề dòng điều khiển, máy phát điện là một khái niệm quan trọng như lời hứa: cả hai đều trừu tượng và có thể ghép lại được.
__iter__
phương thức, tại sao một iterator chỉ có thể có __next__
? Nếu chúng được cho là lặp đi lặp lại, tôi sẽ mong chúng cũng nhất thiết phải có __iter__
.
__iter__
iterables để trả về một iterator, chỉ yêu cầu một next
phương thức ( __next__
trong Python3). Xin đừng nhầm lẫn các tiêu chuẩn (đối với việc gõ vịt) với cách triển khai của chúng (cách một trình thông dịch Python cụ thể thực hiện nó). Điều này hơi giống với sự nhầm lẫn giữa các hàm tạo (định nghĩa) và các đối tượng của trình tạo (thực hiện). ;)
Chức năng máy phát điện, đối tượng máy phát điện, máy phát điện:
Hàm Generator giống như một hàm thông thường trong Python nhưng nó chứa một hoặc nhiều yield
câu lệnh. Các hàm tạo là một công cụ tuyệt vời để tạo các đối tượng Iterator dễ dàng nhất có thể. Các Iterator đối tượng returend bởi chức năng máy phát điện còn được gọi là Generator đối tượng hoặc Generator .
Trong ví dụ này tôi đã tạo ra một hàm Generator trả về một đối tượng Generator <generator object fib at 0x01342480>
. Cũng giống như các trình vòng lặp khác, các đối tượng Trình tạo có thể được sử dụng trong một for
vòng lặp hoặc với hàm tích next()
hợp trả về giá trị tiếp theo từ trình tạo.
def fib(max):
a, b = 0, 1
for i in range(max):
yield a
a, b = b, a + b
print(fib(10)) #<generator object fib at 0x01342480>
for i in fib(10):
print(i) # 0 1 1 2 3 5 8 13 21 34
print(next(myfib)) #0
print(next(myfib)) #1
print(next(myfib)) #1
print(next(myfib)) #2
Vì vậy, một hàm tạo là cách dễ nhất để tạo một đối tượng Iterator.
Lặp lại :
Mỗi đối tượng máy phát là một trình vòng lặp nhưng không phải ngược lại. Một đối tượng iterator tùy chỉnh có thể được tạo nếu lớp của nó thực hiện __iter__
và __next__
phương thức (còn được gọi là giao thức iterator).
Tuy nhiên, việc sử dụng chức năng trình tạo để tạo vòng lặp dễ dàng hơn nhiều vì chúng đơn giản hóa việc tạo của chúng, nhưng Trình lặp tùy chỉnh cho phép bạn tự do hơn và bạn cũng có thể thực hiện các phương thức khác theo yêu cầu của mình như trong ví dụ dưới đây.
class Fib:
def __init__(self,max):
self.current=0
self.next=1
self.max=max
self.count=0
def __iter__(self):
return self
def __next__(self):
if self.count>self.max:
raise StopIteration
else:
self.current,self.next=self.next,(self.current+self.next)
self.count+=1
return self.next-self.current
def __str__(self):
return "Generator object"
itobj=Fib(4)
print(itobj) #Generator object
for i in Fib(4):
print(i) #0 1 1 2
print(next(itobj)) #0
print(next(itobj)) #1
print(next(itobj)) #1
Ví dụ từ Ned Batchelder rất được khuyến nghị cho các trình vòng lặp và trình tạo
Một phương thức không có trình tạo mà làm một số thứ chẵn
def evens(stream):
them = []
for n in stream:
if n % 2 == 0:
them.append(n)
return them
trong khi bằng cách sử dụng một máy phát điện
def evens(stream):
for n in stream:
if n % 2 == 0:
yield n
return
tuyên bốGọi evens
phương thức (trình tạo) là như bình thường
num = [...]
for n in evens(num):
do_smth(n)
Lặp lại
Một cuốn sách đầy các trang là một lần lặp , Một dấu trang là một trình vòng lặp
và dấu trang này không có gì để làm ngoài việc di chuyển next
litr = iter([1,2,3])
next(litr) ## 1
next(litr) ## 2
next(litr) ## 3
next(litr) ## StopIteration (Exception) as we got end of the iterator
Để sử dụng Trình tạo ... chúng ta cần một hàm
Để sử dụng Iterator ... chúng ta cần next
vàiter
Như đã nói:
Hàm Generator trả về một đối tượng lặp
Toàn bộ lợi ích của Iterator:
Lưu trữ một yếu tố một lần trong bộ nhớ
Bạn có thể so sánh cả hai cách tiếp cận cho cùng một dữ liệu:
def myGeneratorList(n):
for i in range(n):
yield i
def myIterableList(n):
ll = n*[None]
for i in range(n):
ll[i] = i
return ll
# Same values
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)
for i1, i2 in zip(ll1, ll2):
print("{} {}".format(i1, i2))
# Generator can only be read once
ll1 = myGeneratorList(10)
ll2 = myIterableList(10)
print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))
# Generator can be read several times if converted into iterable
ll1 = list(myGeneratorList(10))
ll2 = myIterableList(10)
print("{} {}".format(len(list(ll1)), len(ll2)))
print("{} {}".format(len(list(ll1)), len(ll2)))
Ngoài ra, nếu bạn kiểm tra dấu chân bộ nhớ, trình tạo sẽ chiếm ít bộ nhớ hơn vì không cần lưu trữ tất cả các giá trị trong bộ nhớ cùng một lúc.
Tôi đang viết riêng cho người mới sử dụng Python theo một cách rất đơn giản, mặc dù sâu bên dưới Python làm rất nhiều việc.
Hãy bắt đầu với những điều rất cơ bản:
Hãy xem xét một danh sách,
l = [1,2,3]
Hãy viết một hàm tương đương:
def f():
return [1,2,3]
o / p của print(l): [1,2,3]
& o / p củaprint(f()) : [1,2,3]
Hãy tạo danh sách l iterable: Trong danh sách python luôn có thể lặp lại có nghĩa là bạn có thể áp dụng iterator bất cứ khi nào bạn muốn.
Hãy áp dụng iterator trong danh sách:
iter_l = iter(l) # iterator applied explicitly
Hãy tạo một hàm lặp, tức là viết một hàm tạo tương đương.
Trong python ngay khi bạn giới thiệu từ khóa yield
; nó trở thành một hàm tạo và iterator sẽ được áp dụng hoàn toàn.
Lưu ý: Mọi trình tạo luôn luôn có thể lặp lại với trình lặp lặp ẩn được áp dụng và ở đây trình lặp ẩn là mấu chốt Vì vậy, hàm tạo sẽ là:
def f():
yield 1
yield 2
yield 3
iter_f = f() # which is iter(f) as iterator is already applied implicitly
Vì vậy, nếu bạn đã quan sát, ngay khi bạn thực hiện chức năng tạo fa, nó đã được lặp lại (f)
Hiện nay,
l là danh sách, sau khi áp dụng phương thức iterator "iter", nó trở thành, iter (l)
f đã là iter (f), sau khi áp dụng phương thức iterator "iter", nó trở thành, iter (iter (f)), một lần nữa là iter (f)
Thật là bạn đang truyền int cho int (x) đã là int và nó sẽ vẫn là int (x).
Ví dụ: o / p của:
print(type(iter(iter(l))))
Là
<class 'list_iterator'>
Không bao giờ quên đây là Python và không phải C hay C ++
Do đó, kết luận từ lời giải thích trên là:
danh sách l ~ = iter (l)
Hàm tạo f == iter (f)