Рубрики
Без рубрики

Исследующий Python: Использование и злоупотребление протоколом итератором

Концепция итераторов не каким-либо образом специфично для Python. В самых общих чертах это обжим … Помечено Python, Computerscience, программированием.

Концепция итераторов не каким-либо образом специфично для Python. В самых общих чертах это объект, который используется для итерации (цикл) над последовательностью элементов. Тем не менее, разные языки программирования реализуют их по-разному или нет вообще. В Python каждый для петли использует итератор, отличающийся от многих других языков. В этом посте я охвачу, как итераторы определены в Python, и в пути мы посмотрим на iTerables и что я звоню.

Итализатор является объектом, который может быть итерацией, например, список. В Python, для объекта, чтобы быть потенциал Это должно реализовать __er____ метод. Этот метод Dunder принимает только экземпляр объекта, Я в качестве ввода и должен вернуть объект итератора. Вы можете использовать встроенный функцию ИТЕР Чтобы получить итератор итеративного объекта.

Обратите внимание, что не обязательно является итератор, так как он на самом деле не выполняет итерацию. Вы можете иметь отдельный объект ITERATOR, который возвращается из укладываемого класса, а не классный, обрабатывающий свою собственную итерацию. Но, больше на это позже.

Давайте перейдем к фактическим итераторам, рабочей лошади итерации (особенно в Python). Итераторы являются абстракционным слоем, который инкапсулирует знание того, как принимать предметы из некоторой последовательности. Здесь я намеренно очень общий, так как «последовательность» может быть чем угодно, из списков и файлов в потоках данных из базы данных или удаленного обслуживания. Что круто о итераторах, заключается в том, что код с использованием итератора даже не должен знать, какой источник используется. Вместо этого он может сосредоточиться на одном только и то есть «Что я должен делать с каждым элементом?».

Итерация без итератора

Чтобы лучше понять преимущества итераторов, давайте кратко посмотрим на итерацию без итераторов. Примером нетепараторов итерации является классическим стилем C-стилем. Этот стиль существует не только в C, но и в Е.Г. C ++, GO и JavaScript.

Пример этого в JavaScript:

let numbers = [1,2,3,4,5]
for(let i=0; i < numbers.length; i++){
    // Extract element
    const current_number = numbers[i]
    // Perform action on element
    const squared = current_number ** 2
    console.log(squared)
}

Здесь мы видим, как этот тип для петли должен иметь как извлечение, так и действие для каждого элемента в комплекте.

Все петель Python использует итераторы

В Python у нас нет C-стиля для петель. Вместо этого Pythons для петлей – это то, что на других языках можно назвать «для каждого» -Loops. Тип цикла, который использует итераторы. Это означает, что каждый за петлю вы пишете в Python, должны использовать итератор.

Сначала давайте посмотрим на синтаксическую шкаф эквиваленту предыдущего примера:

numbers = [1,2,3,4,5]
for i in range(len(numbers)):
    number = numbers[i]
    squared = number**2
    print(squared)

Да, я знаю, что я должен просто затереться числа , но я хотел нажимать его немного ближе к JavaScript для петли. Здесь нам нужно выполнить добычу себя, поэтому мы не используем итератор на Числа Отказ Скорее, мы создаем Диапазон который проходит через индексы числа (итератор). Хотя это относительно близко к примеру JavaScript, он все еще работает на более высоком уровне абстракции.

Если вы внимательно посмотрите на пример JavaScript, вы можете увидеть, как мы фактически говорим цикл, когда к концу Я а также как увеличить I ++ . Итак, чтобы получить код Python ближе к этому уровню абстракции, нам действительно нужно было написать что-то вроде этого:

numbers = [1,2,3,4,5]
i = 0
while i < len(numbers):
    number = numbers[i]
    squared = number**2
    print(squared)
    i += 1

Здесь мы инициализируем Я Определите, какое состояние должно быть выполнено для выхода из цикла и как увеличить. Нам нужно очень явно управлять значением Я что-то не требуется при использовании спектр .

Python iTerator протокол

В документации Python итератор определяется как класс, который реализует __Next__ и __er____ Отказ По этому определению итератор также является потенциал (Это реализует __er____ ). Это также то, что я позвоню Ужелистины , как это реализует __Next__ метод. Нет, у меня нет часто используемого срока, и это потому, что вы могли бы сделать его в итератор. Вы видите __er____ Метод тривиален для реализации для итераторов. Он фактически записывается явно в определении итератора, какой метод должен сделать:

class MyABCIterator:
    ...
    def __iter__(self):
        return self

Вот и все, метод просто возвращает ссылку на саму итерацию. Так что если вы Copypste это __er____ Код в ваш устойчивый, у вас есть итератор. Я назвал класс Myabciterator. Как я построю это в итератор, который итерации по алфавиту.

Теперь давайте сделаем это в итератор, сделав его также. __Next__ Метод должен вернуть следующий объект в последовательности. Это также должно поднять Затруднительность Когда мы достигли конца последовательности, часто упоминаются как имеющие «исчерпали итератор». В этом случае, когда мы достигли конца алфавита.

Я буду использовать удобный импорт, ascii_lowercass , из Строка Модуль в стандартной библиотеке Pythons. Это строка всех строчных букв в английском алфавите.

>>> from string import ascii_lowercase
>>> ascii_lowercase
'abcdefghijklmnopqrstuvwxyz'

Хорошо, теперь давайте посмотрим на код для нашего класса, то я объясню реализацию.

from string import ascii_lowercase
class MyABCIterator:
    def __init__(self):
        self.index = 0
    def __next__(self):
        if self.index >= len(ascii_lowercase):
            raise StopIteration()
        char = ascii_lowercase[self.index]
        self.index +=1
        return char
    def __iter__(self):
        return self

Знать, какой символ возвращается каждый раз __Next__ называется, нам понадобится индекс. Поэтому мы добавляем __init__ в наш класс, где мы инициализируем Self.index до нуля. Затем для каждого звонка в __Next__ Сначала мы проверим, достигнуто ли мы конец алфавита. Если индекс находится вне длины строки, мы поднимаем Заставка Как продиктовано в документации Python. Далее мы извлекаем текущий символ перед увеличением Self.index Отказ В противном случае мы бы начали с B вместо А Отказ Наконец, мы увеличиваем индекс и вернемся, ранее извлеченный персонаж.

Теперь, давайте попробуем это в фотке:

>>> for char in MyABCIterator():
...    print(char)
a   
b   
c   
d   
e   
...

Я урезал вывод, так как алфавит не так интересно, не так ли? Этот итератор, как вы могли ожидать, совершенно бесполезно. Мы могли бы просто и что-либо итерацию за ascii_lowercass напрямую. Но я надеюсь, что вы узнали что-то о итераторах по крайней мере.

Последнее примечание о протоколе итератора. Обратите внимание, как For-Loop делает всю работу в использовании протокола. Он автоматически получает итератор, используя __er____ и неоднократно итарает над ним, используя __Next__ Отказ Это следует за картиной Pythons из методов Dunder, не используемых, а скорее способа для нас подключить к синтаксису Pythons или функцию верхнего уровня.

Теперь, когда мы правильно внедрены итератор, давайте немного поэкспериментировать. Первый вопрос, который я хотел бы спросить, это:

Что Python думает о петле над устойчивом?

Для этого я удалил __er____ Способ из предыдущего примера, что приводит к:

class MyABCNextable:
    def __init__(self):
        self.index = 0
    def __next__(self):
        if self.index >= len(ascii_lowercase):
            raise StopIteration()
        char = ascii_lowercase[self.index]
        self.index +=1
        return char

Попытка создать для цикла с этим объектом, дает нам: TypeError: «MyabCnextable» объект не является истечением Отказ Что, вы знаете, не так удивительно. Переводчик не может найти __er____ Позвонить и может, поэтому не создавать итератор.

Python отделяет итератор от последовательности

Я начал играть со встроенными последовательностями и повеселился маленьким открытием. В Python последовательности не являются итераторы сами. Скорее у каждого есть соответствующий класс ITERATOR, отвечающий за итерацию. Давайте посмотрим на Диапазон Например.

>>> range_0_to_9 = range(10)
>>> type(range_0_to_9)

Диапазон () дает нам объект типа типа. Теперь давайте посмотрим, что произойдет, когда мы пытаемся использовать Следующий на этом объекте.

>>> next(range_0_to_9)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: 'range' object is not an iterator

Так что если Диапазон объект не является итератор, что мы получаем от ИТЕР ?

>>> range_0_to_9_iterator = iter(range_0_to_9)
>>> type(range_0_to_9_iterator)

Просто чтобы проверить, мы можем использовать Следующий с Range_iterator Отказ

>>> next(range_0_to_9_iterator)
0
>>> next(range_0_to_9_iterator)
1

Здесь мы вернемся Range_iterator объект, который фактически несет ответственность за выполнение итерации. Похоже, это имеет место для всех встроенных типов; Включая списки, словари и наборы. Кажется, что в CPYthon разработчики решили отделить итерацию объекта из самого объекта.

Создание отдельных именных и устойчивых

Вооруженные этими новыми знаниями о разделении утеряемого от итератора, я получил новую идею:

Могу ли я вернуться в обычную у

Итализатор действительно легко:

class MyABCIterable:
    def __iter__(self):
        return MyABCNextable()

Это всего лишь обертка вокруг нашего прибора Nextable. Тогда мы пишем цикл:

for char in MyABCIterable():
    print(char)

Оно работает! Итак, в то время как документация Python говорит, что __er____ Метод должен вернуть итератор (реализация как __er____ и __Next__ ), это не востребовано на петле.

Однако эта настройка хрупкая. Возвращаясь к нашему правильно реализованному итератору, этот код будет работать:

my_abc = iter(MyABCIterator())
for char in my_abc:
    print(char)

Тем не менее, с нашим потенциалом + удержимым комбо, это не:

my_abc = iter(MyABCIterable())
for char in my_abc:
    print(char)

Это поднимает TypeError: «MyabCnextable» объект не является истеченным . Это причина, по которой протокол итераторский протокол определяется так, как оно есть. Это позволяет пройти через итератор и все еще петлю над ним. Например, если вы можете сначала создать итератор, а затем сдать его в другую функцию. Наше хаковое решение не будет работать на это.

Я просто хочу заканчиваться некоторыми ключевыми выходами из этого поста. Первый, я думаю, большинство из вас знают, Все для петель в Python использует итераторы! . Мы также посмотрели на то, как итераторы позволяют нам отделить код, выполняющий итерацию из кода, работающего с каждым элементом. Я также надеюсь, что вы узнали немного больше о том, как работают итераторы в Python, и что такое протокол итерации.

Последний вынос немного более абстрактный, на самом деле он не имеет ничего общего с итераторами или питоном вообще. Скорее, именно так, как код может по-видимому, хорошо работать, пока вы не наткнулись на случай, когда он ломается. Если бы я позвонил ему в день после решения просто пытаясь потенциал + моментую комбинацию в плане для петли, я бы не обнаружил, как это сломается при прохождении вокруг итератора.

Это сейчас И я надеюсь, что вам понравился этот более глубокий взгляд на протокол Pythons iTerator!

Оригинал: “https://dev.to/fronkan/exploring-python-using-and-abusing-the-iterator-protocol-28cf”