Python Advanced: итераторы и фрагменты итераторов

Python
Python Advanced: итераторы и фрагменты итераторов

В первых двух статьях о срезах Python мы узнали об основном использовании, расширенном использовании, недопонимании использования и о том, как пользовательские объекты реализуют использование срезов (ссылки по теме см. в конце статьи). Эта статья является третьей в серии статей о срезах, посвященных слайсам итераторов.

Итераторы — это уникальная расширенная функция Python, и срезы также являются расширенной функцией.Что даст их комбинация?

1. Итерация и итераторы

Во-первых, необходимо прояснить несколько основных понятий: итерация, итерации, итераторы.

迭代Это способ обхода объектов контейнерного типа (таких как строки, списки, словари и т. д.) Например, мы говорим об итерации по строке "abc", что означает последовательное взятие всех ее символов слева направо. процесс. (PS: Слово «итерация» в китайском языке имеет значение повторения и прогрессии, но это слово в Python следует понимать какОдносторонний горизонтальный линейныйДа, если вы с ним не знакомы, предлагаю понимать его прямо как обход. )

Итак, как написать инструкции для итерационных операций? Самый распространенный синтаксис записи — цикл for.

# for循环实现迭代过程
for char in "abc":
    print(char, end=" ")
# 输出结果:a b c

Цикл for может реализовать итеративный процесс, но не все объекты могут быть использованы в цикле for.Например, если строка "abc" заменена любым целым числом в приведенном выше примере, будет сообщено об ошибке: 'int' объект не является итерируемым.

Слово «итерируемый» в этом сообщении об ошибке относится к «итерируемому», то есть тип int не является итерируемым. Тип string (строка) является итерируемым, также итерируемы списки, кортежи, словари и другие типы.

Так как же определить, является ли объект итерируемым? Почему они повторяемые? Как сделать объект итерируемым?

Чтобы сделать объект итерируемым, необходимо реализовать итерируемый протокол, то есть нужно реализовать__iter__()Магический метод, другими словами, до тех пор, пока объект, который реализует этот магический метод, является итерируемым объектом.

Итак, как определить, реализует ли объект этот метод? В дополнение к описанному выше циклу for я знаю еще четыре способа:

# 方法1:dir()查看__iter__
dir(2)     # 没有,略
dir("abc") # 有,略

# 方法2:isinstance()判断
import collections
isinstance(2, collections.Iterable)     # False
isinstance("abc", collections.Iterable) # True

# 方法3:hasattr()判断
hasattr(2,"__iter__")     # False
hasattr("abc","__iter__") # True

# 方法4:用iter()查看是否报错
iter(2)     # 报错:'int' object is not iterable
iter("abc") # <str_iterator at 0x1e2396d8f28>

### PS:判断是否可迭代,还可以查看是否实现__getitem__,为方便描述,本文从略。

Наиболее достойным упоминания из этих методов является метод iter(), который является встроенным методом Python, и его роль заключается вПревратите итерируемый объект в итератор. Это предложение можно разобрать на два значения: (1) итерируемые объекты и итераторы — это две вещи; (2) итерируемые объекты могут стать итераторами.

На самом деле итераторы обязательно являются итерируемыми, но итерируемые объекты не обязательно являются итераторами. Насколько велика разница между ними?

Как показано в синем кружке на рисунке выше, наиболее важное различие между обычными итерируемыми объектами и итераторами можно резюмировать следующим образом:одинаковые два разных, так называемое "вместе", то есть итерируемы оба (__iter__), так называемые "два разных", то есть после преобразования итерируемого объекта в итератор он потеряет некоторые свойства (__getitem__), а также добавив некоторые свойства (__next__).

Первый взгляд на добавленные свойства__next__, это ключ к тому, почему итераторы являются итераторами, на самом деле мы реализуем оба__iter__Методы и__next__Объект метода определяется как итератор.

Благодаря этому дополнительному свойству итерируемые объекты могут реализовать свой собственный процесс итерации/обхода, не прибегая к внешнему синтаксису цикла for. Я придумал две концепции для описания этих двух процессов обхода (PS: Для простоты понимания здесь называется обходом, который на самом деле можно назвать итерацией):它遍历Относится к обходу внешнего синтаксиса,自遍历Относится к обходу, реализованному собственным методом.

С помощью этих двух понятий мы говорим, что итерируемый объект — это объект, который может быть «обойден им», а итератор — это объект, который также может «обходить себя» на этой основе.

ob1 = "abc"
ob2 = iter("abc")
ob3 = iter("abc")

# ob1它遍历
for i in ob1:
    print(i, end = " ")   # a b c
for i in ob1:
    print(i, end = " ")   # a b c
# ob1自遍历
ob1.__next__()  # 报错: 'str' object has no attribute '__next__'

# ob2它遍历
for i in ob2:
    print(i, end = " ")   # a b c    
for i in ob2:
    print(i, end = " ")   # 无输出
# ob2自遍历
ob2.__next__()  # 报错:StopIteration

# ob3自遍历
ob3.__next__()  # a
ob3.__next__()  # b
ob3.__next__()  # c
ob3.__next__()  # 报错:StopIteration

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

В связи с этим я думаю об аналогии: нормальный итерируемый объект подобен магазину для пуль, его обход состоит в том, чтобы вынуть пулю и положить ее обратно после завершения операции, чтобы его можно было обходить многократно (т. loop вызывается несколько раз, возвращая один и тот же результат); и Итератор похож на несъемный пистолет с заряженным магазином. Он перемещается или перемещается самостоятельно, чтобы стрелять пулями. обход будет иметь конец).

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

2. Срез итератора

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

Итак, вопрос: почему итератор не наследует это свойство?

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

Отсюда возникает новый вопрос: зачем использовать итераторы, когда такие важные атрибуты (и другие неопознанные атрибуты) потеряны?

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

Еще не все, вот и возникает вопрос: может ли итератор иметь это свойство, даже если итератор продолжает поддерживать срезы?

hi = "欢迎关注公众号:Python猫"
it = iter(hi)

# 普通切片
hi[-7:] # Python猫

# 反例:迭代器切片
it[-7:] # 报错:'str_iterator' object is not subscriptable

итератор, потому что отсутствует__getitem__, поэтому обычный синтаксис нарезки использовать нельзя. Для реализации слайсинга есть не более двух идей: одна — построить колесо и написать логику реализации, другая — найти упакованное колесо.

Модуль Python itertools — это колесо, которое мы ищем, предоставляющее методы для простой реализации нарезки итератора.

import itertools

# 例1:简易迭代器
s = iter("123456789")
for x in itertools.islice(s, 2, 6):
    print(x, end = " ")   # 输出:3 4 5 6
for x in itertools.islice(s, 2, 6):
    print(x, end = " ")   # 输出:9

# 例2:斐波那契数列迭代器
class Fib():
    def __init__(self):
        self.a, self.b = 1, 1

    def __iter__(self):
        while True:
            yield self.a
            self.a, self.b = self.b, self.a + self.b
f = iter(Fib())
for x in itertools.islice(f, 2, 6):
    print(x, end = " ")  # 输出:2 3 5 8
for x in itertools.islice(f, 2, 6):
    print(x, end = " ")  # 输出:34 55 89 144

Метод islice() модуля itertools прекрасно сочетает в себе итераторы и слайсы, наконец, отвечая на предыдущий вопрос. Однако срезы итераторов имеют много ограничений по сравнению с обычными срезами. Прежде всего, этот метод не является «чистой функцией» (чистая функция должна подчиняться принципу «один и тот же входной сигнал для получения такого же выходного сигнала», который ранее был описан в «Совет от Кеннета Рейца: Избегайте ненужного объектно-ориентированного программирования."упомянутый); во-вторых, он поддерживает только прямую нарезку и не поддерживает отрицательные индексы, которые определяются характером итераторов с потерями.

Итак, не могу не спросить: какова логика реализации метода slice модуля itertools? Ниже приведен исходный код, предоставленный официальным сайтом:

def islice(iterable, *args):
    # islice('ABCDEFG', 2) --> A B
    # islice('ABCDEFG', 2, 4) --> C D
    # islice('ABCDEFG', 2, None) --> C D E F G
    # islice('ABCDEFG', 0, None, 2) --> A C E G
    s = slice(*args)
    # 索引区间是[0,sys.maxsize],默认步长是1
    start, stop, step = s.start or 0, s.stop or sys.maxsize, s.step or 1
    it = iter(range(start, stop, step))
    try:
        nexti = next(it)
    except StopIteration:
        # Consume *iterable* up to the *start* position.
        for i, element in zip(range(start), iterable):
            pass
        return
    try:
        for i, element in enumerate(iterable):
            if i == nexti:
                yield element
                nexti = next(it)
    except StopIteration:
        # Consume to *stop*.
        for i, element in zip(range(i + 1, stop), iterable):
            pass

Направление индексации метода islice() ограничено, но он также предоставляет возможность: возможность разрешить вам нарезать бесконечный (насколько система поддерживает) итератор. Это самый творческий вариант использования срезов итераторов.

Кроме того, существует вполне реальный сценарий применения итераторной нарезки: чтение данных в заданном диапазоне строк в файловом объекте.

существует"Руководство по чтению и написанию файлов для изучающих Python (включая базовую и расширенную, рекомендуемую коллекцию)", я представил несколько методов для чтения содержимого из файла: readline() относительно бесполезен и бесполезен; read() подходит для ситуаций, когда требуется прочитать меньше содержимого или когда все содержимое необходимо обработать за один раз. ; А функция readlines() используется чаще и более гибкая.Чтение содержимого каждый раз итеративно снижает нагрузку на память и облегчает обработку данных построчно.

Хотя readlines() имеет преимущество итеративного чтения, она читает строку за строкой от начала до конца.Если в файле тысячи строк, а мы хотим прочитать только несколько определенных строк (например, строки 1000-1009), тогда это все еще слишком неэффективно. учитываяФайловые объекты по своей природе являются итераторами., мы можем использовать слайс итератора, чтобы сначала перехватить, а затем обработать его, так что эффективность будет значительно повышена.

# test.txt 文件内容
'''
猫
Python猫
python is a cat.
this is the end.
'''

from itertools import islice
with open('test.txt','r',encoding='utf-8') as f:
    print(hasattr(f, "__next__"))  # 判断是否迭代器
    content = islice(f, 2, 4)
    for line in content:
        print(line.strip())
### 输出结果:
True
python is a cat.
this is the end.

3. Резюме

Хорошо, это все для сегодняшнего обучения.Подводя итог: Итератор - это специальный итерируемый объект, который можно использовать для его обхода и самообхода, но процесс обхода с потерями и не имеет возможности циклического повторного использования.Поэтому итеративный сам итератор не поддержка операций нарезки; с помощью модуля itertools мы можем реализовать нарезку итератора и объединить преимущества двух. Его основная цель — перехватывать фрагменты больших итераторов (таких как бесконечные массивы, большие файлы и т. д.) для достижения точной обработки, тем самым значительно повышая производительность и эффективность.

Серия фрагментов:

"Python Advanced: недоразумения и расширенное использование нарезки

"Python Advanced: пользовательские объекты для реализации функций нарезки

Ссылки по теме:

"Знакомство с модулем itertools на официальном сайте

"Совет от Кеннета Рейца: Избегайте ненужного объектно-ориентированного программирования.

"Руководство по чтению и написанию файлов для изучающих Python (включая базовую и расширенную, рекомендуемую коллекцию)

-----------------

Эта статья является оригинальной и впервые опубликована в общедоступной учетной записи WeChat [Python Cat]. Фоновый ответ «Люблю учиться», и вы можете получить более 20 избранных электронных книг бесплатно.