В первых двух статьях о срезах 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 на официальном сайте》
"Совет от Кеннета Рейца: Избегайте ненужного объектно-ориентированного программирования.》
-----------------
Эта статья является оригинальной и впервые опубликована в общедоступной учетной записи WeChat [Python Cat]. Фоновый ответ «Люблю учиться», и вы можете получить более 20 избранных электронных книг бесплатно.