Відповіді:
Об'єкти ітератора в python відповідають протоколу ітератора, що в основному означає, що вони забезпечують два методи: __iter__()
і __next__()
.
__iter__
Повертає об'єкт ітератора і неявно викликається на початку петлі.
__next__()
Метод повертає таке значення і неявно викликається при кожному збільшенні циклу. Цей метод створює виняток StopIteration, коли більше немає значення для повернення, яке неявно фіксується циклічними конструкціями, щоб зупинити ітерацію.
Ось простий приклад лічильника:
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)
Це надрукує:
3
4
5
6
7
8
Це простіше написати за допомогою генератора, як описано в попередній відповіді:
def counter(low, high):
current = low
while current < high:
yield current
current += 1
for c in counter(3, 9):
print(c)
Друкований вихід буде однаковим. Під кришкою об'єкт генератора підтримує протокол ітератора і робить щось приблизно подібне до класу Counter.
Стаття Девіда Мерца " Ітератори та прості генератори" - досить вдале вступ.
__next__
. counter
є ітератором, але це не послідовність. Він не зберігає своїх значень. Ви не повинні використовувати лічильник, наприклад, у подвійному вкладеному циклі for.
__iter__
(крім in __init__
). В іншому випадку об’єкт можна повторити лише один раз. Наприклад, якщо ви скажете ctr = Counters(3, 8)
, ви не можете використовувати for c in ctr
більше одного разу.
Counter
є ітератором, а ітератори повинні бути повторені лише один раз. При скиданні self.current
в __iter__
, то вкладений цикл над Counter
буде повністю зруйнований, і всі види передбачуваного поведінки ітераторів (що виклик iter
на них ідемпотентна) порушується. Якщо ви хочете мати можливість ctr
повторення повторення, він повинен бути ітератором, який можна повторити, коли він повертає абсолютно новий ітератор щоразу, коли __iter__
викликається. Намагання змішати та співставити (ітератор, який неявно скидається після __iter__
виклику), порушує протоколи.
Counter
був ітератором, який можна ітераберувати, ви видалили б визначення __next__
/ next
повністю та, ймовірно, повторно визначили __iter__
функцію генератора тієї ж форми, що і генератор, описану в кінці цієї відповіді (крім замість меж виходячи з аргументів __iter__
, вони були б аргументи , щоб __init__
зберегти на self
і доступні з self
в __iter__
).
Існує чотири способи побудови ітеративної функції:
__iter__
та__next__
(або next
в Python 2.x))__getitem__
)Приклади:
# 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]
Щоб побачити всі чотири способи в дії:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print(ch, end=' ')
print()
Результати:
A B C D E
A B C D E
A B C D E
A B C D E
Примітка :
Два типи генераторів ( uc_gen
і uc_genexp
) не можуть бути reversed()
; звичайний ітератор ( uc_iter
) потребував би __reversed__
магічного методу (який, на думку доків , повинен повернути новий ітератор, але повертає self
твори (принаймні в CPython)); і getitem iteratable ( uc_getitem
) повинен мати __len__
магічний метод:
# 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)
Щоб відповісти на вторинне запитання полковника Паніка про нескінченний ліниво оцінений ітератор, ось ці приклади, використовуючи кожен із чотирьох вищевказаних методів:
# 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
Результати (принаймні для мого зразка):
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
Як вибрати, який саме використовувати? Це здебільшого справа смаку. Два способи, які я бачу найчастіше, - це генератори та протокол ітератора, а також гібрид ( __iter__
повернення генератора).
Вирази генератора корисні для заміни розуміння списку (вони ліниві і так можуть економити ресурси).
Якщо вам потрібна сумісність з більш ранніми версіями Python 2.x, використовуйте __getitem__
.
uc_iter
повинен закінчитися, коли це буде зроблено (інакше це буде нескінченно); якщо ви хочете зробити це знову, вам потрібно отримати новий ітератор, зателефонувавши uc_iter()
ще раз.
self.index = 0
в __iter__
так що ви можете повторювати багато раз. Інакше ти не можеш.
Перш за все, модуль itertools неймовірно корисний для всіляких випадків, коли ітератор був би корисний, але ось усе, що потрібно для створення ітератора в python:
урожайність
Хіба це не круто? Вихід можна використовувати для заміни нормального повернення функції. Він повертає об'єкт так само, але замість руйнування стану та виходу, він зберігає стан, коли потрібно виконати наступну ітерацію. Ось приклад цього дії, витягнутого безпосередньо зі списку функцій itertools :
def count(n=0):
while True:
yield n
n += 1
Як зазначено в описі функцій (це функція count () з модуля itertools ...), він створює ітератор, який повертає послідовні цілі числа, починаючи з n.
Вирази генератора - це зовсім інша банка глистів (дивовижні черви!). Вони можуть використовуватися замість Зрозуміння списку для збереження пам’яті (розуміння списку створює список у пам’яті, який знищується після використання, якщо не присвоєний змінній, але вирази генератора можуть створити об’єкт генератора…, що є фантастичним способом приказка Ітератор). Ось приклад визначення виразу генератора:
gen = (n for n in xrange(0,11))
Це дуже схоже на наше вище визначення ітератора, за винятком того, що повний діапазон визначений між 0 і 10.
Я щойно знайшов xrange () (здивований, що я його раніше не бачив ...) і додав його до наведеного вище прикладу. xrange () - це ітерабельна версія діапазону (), яка має перевагу в тому, що не створювати список. Було б дуже корисно, якби у вас був гігантський корпус даних для повторення і у вас було лише стільки пам'яті, щоб це зробити.
Я бачу , що деякі з вас роблять return self
в __iter__
. Я просто хотів зазначити, що __iter__
сам по собі може бути генератором (таким чином усуваючи необхідність __next__
і збільшуючи StopIteration
винятки)
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
Звичайно, тут можна безпосередньо зробити генератор, але для складніших класів це може бути корисно.
return self
в __iter__
. Коли я збирався спробувати використовувати yield
його, я виявив, що ваш код робить саме те, що я хочу спробувати.
next()
? return iter(self).next()
?
self.current
чи будь-якого іншого лічильника. Це має бути відповідь, що голосує вище!
iter
до екземплярів класу, але вони самі не є екземплярами класу.
Це питання стосується ітерабельних об'єктів, а не ітераторів. У Python послідовності є ітерабельними, тому один із способів зробити ітерабельний клас - це змусити його вести себе як послідовність, тобто дати йому __getitem__
та __len__
методи. Я перевірив це на Python 2 і 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__()
метод. __getitem__
одне з очікуваною поведінкою достатньо.
Усі відповіді на цій сторінці справді чудові для складного об’єкта. Але для тих , які містять вбудований Iterable типів в якості атрибутів, як str
, list
, set
або dict
, або будь-яка реалізація collections.Iterable
, ви можете пропустити деякі речі в своєму класі.
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)
Його можна використовувати так:
for x in Test("abcde"):
print(x)
# prints
# a
# b
# c
# d
# e
return iter(self.string)
.
Це ітерабельна функція без yield
. Він використовує iter
функцію та закриття, яке зберігає стан у mutable ( list
) у додатковій області для 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)
Для Python 3 стан закриття зберігається в незмінному обсязі, що додається, і nonlocal
використовується в локальній області для оновлення змінної стану.
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)
Тест;
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
iter
, але просто для того, щоб було зрозуміло: Це складніше і менш ефективно, ніж просто використання yield
генераторної функції на основі; У Python є тонна підтримка інтерпретатора для yield
заснованих функцій генератора, якими ви тут не можете скористатися, що робить цей код значно повільнішим. Тим не менш, проголосували.
Якщо ви шукаєте щось коротке і просте, можливо, вам цього буде достатньо:
class A(object):
def __init__(self, l):
self.data = l
def __iter__(self):
return iter(self.data)
приклад використання:
In [3]: a = A([2,3,4])
In [4]: [i for i in a]
Out[4]: [2, 3, 4]
Натхненний відповіддю Метта Грегорі тут трохи складніший ітератор, який поверне 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)