Câu trả lời:
Các đối tượng lặp trong python tuân thủ giao thức iterator, về cơ bản có nghĩa là chúng cung cấp hai phương thức: __iter__()
và __next__()
.
Trả __iter__
về đối tượng iterator và được gọi ngầm khi bắt đầu các vòng lặp.
Các __next__()
phương thức trả về giá trị tiếp theo và được mặc nhiên được gọi ở mỗi số gia vòng lặp. Phương thức này làm tăng ngoại lệ StopIteration khi không còn giá trị nào để trả về, được ngầm định nắm bắt bằng cách lặp các cấu trúc để dừng lặp.
Đây là một ví dụ đơn giản về bộ đếm:
class Counter:
def __init__(self, low, high):
self.current = low - 1
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 2: def next(self)
self.current += 1
if self.current < self.high:
return self.current
raise StopIteration
for c in Counter(3, 9):
print(c)
Điều này sẽ in:
3
4
5
6
7
8
Điều này dễ viết hơn bằng cách sử dụng một trình tạo, như được trình bày trong câu trả lời trước:
def counter(low, high):
current = low
while current < high:
yield current
current += 1
for c in counter(3, 9):
print(c)
Đầu ra được in sẽ giống nhau. Dưới mui xe, đối tượng trình tạo hỗ trợ giao thức iterator và thực hiện một cái gì đó gần giống với bộ đếm lớp.
Bài viết của David Mertz, Iterators và Simple Generators , là một giới thiệu khá hay.
__next__
. counter
là một trình vòng lặp, nhưng nó không phải là một chuỗi. Nó không lưu trữ giá trị của nó. Ví dụ, bạn không nên sử dụng bộ đếm trong một vòng lặp for lồng nhau.
__iter__
(ngoài in __init__
). Mặt khác, đối tượng có thể được lặp lại một lần. Ví dụ, nếu bạn nói ctr = Counters(3, 8)
, thì bạn không thể sử dụng for c in ctr
nhiều hơn một lần.
Counter
là một trình vòng lặp và các trình vòng lặp chỉ được cho là được lặp lại một lần. Nếu bạn đặt lại self.current
trong __iter__
, sau đó một vòng lặp lồng nhau trên Counter
sẽ được hoàn toàn bị phá vỡ, và tất cả các loại hành vi giả của vòng lặp (đó gọi iter
vào số đó là idempotent) bị vi phạm. Nếu bạn muốn có thể lặp lại ctr
nhiều lần, nó cần phải là một trình vòng lặp không lặp, nơi nó trả về một trình vòng lặp hoàn toàn mới mỗi lần __iter__
được gọi. Cố gắng trộn và kết hợp (một trình vòng lặp được đặt lại ngầm khi __iter__
được gọi) vi phạm các giao thức.
Counter
là một trình lặp không lặp, bạn sẽ xóa định nghĩa __next__
/ next
hoàn toàn và có thể xác định lại __iter__
là hàm tạo có cùng dạng với trình tạo được mô tả ở cuối câu trả lời này (ngoại trừ các giới hạn đến từ các đối số __iter__
, chúng sẽ là các đối số được __init__
lưu self
và truy cập từ self
trong __iter__
).
Có bốn cách để xây dựng hàm lặp:
__iter__
và__next__
(hoặc next
trong Python 2.x))__getitem__
)Ví dụ:
# generator
def uc_gen(text):
for char in text.upper():
yield char
# generator expression
def uc_genexp(text):
return (char for char in text.upper())
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text.upper()
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text.upper()
def __getitem__(self, index):
return self.text[index]
Để xem tất cả bốn phương thức đang hoạt động:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print(ch, end=' ')
print()
Kết quả nào trong:
A B C D E
A B C D E
A B C D E
A B C D E
Lưu ý :
Hai loại máy phát ( uc_gen
và uc_genexp
) không thể là reversed()
; iterator đơn giản ( uc_iter
) sẽ cần __reversed__
phương thức ma thuật (mà theo các tài liệu , phải trả về một trình vòng lặp mới, nhưng trả về self
các tác phẩm (ít nhất là trong CPython)); và getitem iterizable ( uc_getitem
) phải có __len__
phương thức ma thuật:
# for uc_iter we add __reversed__ and update __next__
def __reversed__(self):
self.index = -1
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += -1 if self.index < 0 else +1
return result
# for uc_getitem
def __len__(self)
return len(self.text)
Để trả lời câu hỏi phụ của Đại tá Panic về một trình vòng lặp được đánh giá lười biếng vô hạn, đây là những ví dụ, sử dụng một trong bốn phương pháp trên:
# generator
def even_gen():
result = 0
while True:
yield result
result += 2
# generator expression
def even_genexp():
return (num for num in even_gen()) # or even_iter or even_getitem
# not much value under these circumstances
# iterator protocol
class even_iter():
def __init__(self):
self.value = 0
def __iter__(self):
return self
def __next__(self):
next_value = self.value
self.value += 2
return next_value
# getitem method
class even_getitem():
def __getitem__(self, index):
return index * 2
import random
for iterator in even_gen, even_genexp, even_iter, even_getitem:
limit = random.randint(15, 30)
count = 0
for even in iterator():
print even,
count += 1
if count >= limit:
break
print
Kết quả nào (ít nhất là cho lần chạy mẫu của tôi):
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
Làm thế nào để chọn cái nào để sử dụng? Đây chủ yếu là một vấn đề của hương vị. Hai phương thức tôi thấy thường xuyên nhất là trình tạo và giao thức lặp, cũng như kết hợp ( __iter__
trả về một trình tạo).
Các biểu thức của trình tạo rất hữu ích để thay thế việc hiểu danh sách (chúng lười biếng và do đó có thể tiết kiệm tài nguyên).
Nếu một người cần tương thích với các phiên bản Python 2.x trước đó, hãy sử dụng __getitem__
.
uc_iter
nên hết hạn khi hoàn thành (nếu không thì sẽ vô hạn); nếu bạn muốn làm lại, bạn phải có một trình vòng lặp mới bằng cách gọi uc_iter()
lại.
self.index = 0
trong __iter__
để bạn có thể lặp lại nhiều lần. Nếu không thì bạn không thể.
Trước hết, mô-đun itertools cực kỳ hữu ích cho tất cả các loại trường hợp trong đó một trình vòng lặp sẽ hữu ích, nhưng đây là tất cả những gì bạn cần để tạo một trình vòng lặp trong python:
năng suất
Điều đó không tuyệt sao? Năng suất có thể được sử dụng để thay thế một lợi nhuận bình thường trong một chức năng. Nó trả về đối tượng giống nhau, nhưng thay vì hủy trạng thái và thoát, nó lưu trạng thái khi bạn muốn thực hiện lần lặp tiếp theo. Dưới đây là một ví dụ về nó trong hành động được lấy trực tiếp từ danh sách hàm itertools :
def count(n=0):
while True:
yield n
n += 1
Như đã nêu trong mô tả hàm (đó là hàm Count () từ mô đun itertools ...), nó tạo ra một trình vòng lặp trả về các số nguyên liên tiếp bắt đầu bằng n.
Các biểu thức của trình tạo là một loạt các con sâu khác (những con sâu tuyệt vời!). Chúng có thể được sử dụng thay cho Hiểu danh sách để lưu bộ nhớ (việc hiểu danh sách tạo ra một danh sách trong bộ nhớ bị hủy sau khi sử dụng nếu không được gán cho một biến, nhưng các biểu thức trình tạo có thể tạo Đối tượng trình tạo ... đó là một cách ưa thích nói lặp đi lặp lại). Dưới đây là một ví dụ về định nghĩa biểu thức trình tạo:
gen = (n for n in xrange(0,11))
Điều này rất giống với định nghĩa lặp của chúng tôi ở trên, ngoại trừ phạm vi đầy đủ được xác định trước là từ 0 đến 10.
Tôi chỉ tìm thấy xrange () (ngạc nhiên là tôi chưa từng thấy nó trước đây ...) và thêm nó vào ví dụ trên. xrange () là một phiên bản lặp lại của phạm vi () có lợi thế là không xây dựng trước danh sách. Sẽ rất hữu ích nếu bạn có một khối dữ liệu khổng lồ để lặp đi lặp lại và chỉ có quá nhiều bộ nhớ để thực hiện.
Tôi thấy một số bạn làm return self
trong __iter__
. Tôi chỉ muốn lưu ý rằng __iter__
chính nó có thể là một trình tạo (do đó loại bỏ sự cần thiết __next__
và nâng cao StopIteration
ngoại lệ)
class range:
def __init__(self,a,b):
self.a = a
self.b = b
def __iter__(self):
i = self.a
while i < self.b:
yield i
i+=1
Tất nhiên ở đây người ta cũng có thể trực tiếp tạo ra một trình tạo, nhưng đối với các lớp phức tạp hơn thì nó có thể hữu ích.
return self
trong __iter__
. Khi tôi định thử sử dụng yield
nó, tôi thấy mã của bạn đang hoạt động chính xác những gì tôi muốn thử.
next()
? return iter(self).next()
?
self.current
hoặc bất kỳ bộ đếm nào khác. Đây phải là câu trả lời được bình chọn hàng đầu!
iter
thể hiện của lớp, nhưng chúng không phải là các thể hiện của lớp.
Câu hỏi này là về các đối tượng lặp, không phải về các vòng lặp. Trong Python, các chuỗi cũng có thể lặp lại được vì vậy một cách để tạo một lớp lặp là làm cho nó hoạt động giống như một chuỗi, tức là đưa ra nó __getitem__
và __len__
các phương thức. Tôi đã thử nghiệm điều này trên Python 2 và 3.
class CustomRange:
def __init__(self, low, high):
self.low = low
self.high = high
def __getitem__(self, item):
if item >= len(self):
raise IndexError("CustomRange index out of range")
return self.low + item
def __len__(self):
return self.high - self.low
cr = CustomRange(0, 10)
for i in cr:
print(i)
__len__()
phương pháp. __getitem__
Một mình với hành vi dự kiến là đủ.
Tất cả các câu trả lời trên trang này thực sự tuyệt vời cho một đối tượng phức tạp. Nhưng đối với những người chứa được xây dựng trong các loại iterable như là thuộc tính, giống như str
, list
, set
hay dict
, hay bất cứ thực hiện collections.Iterable
, bạn có thể bỏ qua những điều nào đó trong lớp học của bạn.
class Test(object):
def __init__(self, string):
self.string = string
def __iter__(self):
# since your string is already iterable
return (ch for ch in self.string)
# or simply
return self.string.__iter__()
# also
return iter(self.string)
Nó có thể được sử dụng như:
for x in Test("abcde"):
print(x)
# prints
# a
# b
# c
# d
# e
return iter(self.string)
.
Đây là một chức năng lặp mà không có yield
. Nó sử dụng iter
hàm và một bao đóng giữ trạng thái của nó ở trạng thái có thể thay đổi ( list
) trong phạm vi kèm theo cho python 2.
def count(low, high):
counter = [0]
def tmp():
val = low + counter[0]
if val < high:
counter[0] += 1
return val
return None
return iter(tmp, None)
Đối với Python 3, trạng thái đóng được giữ ở mức không thay đổi trong phạm vi kèm theo và nonlocal
được sử dụng trong phạm vi cục bộ để cập nhật biến trạng thái.
def count(low, high):
counter = 0
def tmp():
nonlocal counter
val = low + counter
if val < high:
counter += 1
return val
return None
return iter(tmp, None)
Kiểm tra;
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
iter
, nhưng chỉ cần rõ ràng: Điều này phức tạp và kém hiệu quả hơn so với việc chỉ sử dụng yield
chức năng tạo dựa trên; Python có rất nhiều hỗ trợ trình thông dịch cho các yield
hàm tạo dựa trên mà bạn không thể tận dụng ở đây, làm cho mã này chậm hơn đáng kể. Tuy nhiên, đã bỏ phiếu.
Lấy cảm hứng từ câu trả lời của Matt Gregory ở đây là một trình vòng lặp phức tạp hơn một chút sẽ trả về a, b, ..., z, aa, ab, ..., zz, aaa, aab, ..., zzy, zzz
class AlphaCounter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 3: def __next__(self)
alpha = ' abcdefghijklmnopqrstuvwxyz'
n_current = sum([(alpha.find(self.current[x])* 26**(len(self.current)-x-1)) for x in range(len(self.current))])
n_high = sum([(alpha.find(self.high[x])* 26**(len(self.high)-x-1)) for x in range(len(self.high))])
if n_current > n_high:
raise StopIteration
else:
increment = True
ret = ''
for x in self.current[::-1]:
if 'z' == x:
if increment:
ret += 'a'
else:
ret += 'z'
else:
if increment:
ret += alpha[alpha.find(x)+1]
increment = False
else:
ret += x
if increment:
ret += 'a'
tmp = self.current
self.current = ret[::-1]
return tmp
for c in AlphaCounter('a', 'zzz'):
print(c)