Введение
Основы
Примитивы
Python — это набор примитивных типов — целых чисел, чисел с плавающей точкой, строк и т. д.:
42 # int целое число 4.2 # float число с плавающей точкой 'forty-two' # str строка True # bool логический/булев тип
Переменная — имя, указывающее на значение. Значение представляет объект некоторого типа:
x = 42
Иногда тип явно указывается для имени:
x: int = 42
Тип — лишь подсказка, упрощающая чтение кода. Он может использоваться сторонними инструментами проверки кода. В остальных случаях он полно- стью игнорируется. Указание типа никак не помешает вам присвоить пере- менной значение другого типа. Выражение — это комбинация примитивов, имен и операторов, в результате вычисления которой будет получено некоторое значение:
2 + 3 * 4 # -> 14
Следующая программа использует переменные и выражения для вычисления сложных процентов:
principal = 1000 # Исходная сумма rate = 0.05 # Процентная ставка numyears = 5 # Количество лет year = 1 while year <= numyears: principal = principal * (1 + rate) print(year, principal) year += 1
При выполнении программа выдает следующий результат:
1 1050.0 2 1102.5 3 1157.625 4 1215.5062500000001 5 1276.2815625000003
Команда while проверяет условное выражение, следующее сразу за ключе- вым словом. В случае истинности проверяемого условия выполняется тело команды while. Затем это условие проверяется повторно и тело выполняется снова, пока условие не станет ложным. Тело цикла обозначается отступами. Так, три оператора, следующие за while, выполняются при каждой итерации. В спецификации Python не указана величина отступов.
Важно лишь, чтобы отступ был единым в границах блока. Чаще всего ис- пользуются отступы из четырех пробелов на один уровень. Один из недостатков этой программы — не очень красивый вывод. Для его улучшения можно выровнять столбцы по правому краю и ограничить точность вывода чисел двумя знаками в дробной части. Попробуйте изменить функцию print(), чтобы в ней использовалась так называемая f-строка
print(f'{year:>3d} {principal:0.2f}')
В f-строках могут вычисляться выражения и имена переменных. Для этого они заключаются в фигурные скобки. К каждому заменяемому элементу может быть присоединен спецификатор формата. Так, '>3d' обозначает трехзнач- ное десятичное число, выравниваемое по правому краю, '0.2f' обозначает число с плавающей точкой, выводимое с двумя знаками точности. Теперь вывод программы выглядит так:
1 1050.00 2 1102.50 3 1157.62 4 1215.51 5 1276.28
#pythonbasics #pythonprimitives #python
Поблагодарить: https://pay.cloudtips.ru/p/a4aeb3dd
Интерпретатор
Что такое интерпретатор и как он работает?
Интерпретатор — простыми словами, это программа исполняющая другие программы. Когда вы запускаете, написанную на Python, программу, интерпретатор читает её и приводит в исполнение содержащиеся в ней инструкции, выступая прослойкой между кодом и «железом».
Самая простая программа, представляет собой текстовый файл, содержащий операторы Python. Например создайте файл example.py, откройте его в любом текстовом редакторе, который вам нравится и запишите, в него, простейший классический сценарий:
print('hello world')
И сохраните. Обычно, для общей согласованности, всем python файлам, x дают имена, оканчивающиеся на .py. Но если опустить формальности, такая схема именования, обязательна только для файлов, которые будут использоваться при импорте. Давайте переименуем, созданный нами файл "example.py" в "example" и попробуем его запустить, как обычно:
shell % python example hello world
Как видите он без проблем запустился. После запуска Python внутренне компилирует исходный код в байт-код. Под компиляцией, в данном случае, понимается трансляция, а под байт-кодом низкоуровневое представление исходного кода. Грубо говоря, Python транслирует каждый оператор исзодного кода в группу иструкций байт-кода, разбивая их на отдельные шаги. Трансляция в байт-код происходит по мере выполнения. Байт-код можно найти в каталоге с исходным кодом, который называется pycache, внутри будут файлы с расширением .рус — это и есть байт-код. При следующем запуске, Python пропустит шаг трансляции и сразу загрузить файлы .pyc, при условии, что файл с исходным кодом не менялся и не менялась используемая версия Python. Python автоматически проверяет отметки времени последней модификации для файлов исходного кода и байт-кода, чтобы выяснить, когда они должны быть перекомпилированы — если вы отредактируете и повторно сохраните исходный код, то байт-код будет автоматически создан заново при следующем запуске программы. Символ 'c', на конце расширения '.pyc' — означает compiled, т.е. скомпилированный.
Байт-код сохраняется лишь для тех файлов, которые импортируются, но не для файлов верхнего уровня программы, выполняемых только как сценарии. После того, как программа скомпилирована в байт-код , она отправляется на выполнение в виртуальную машину Python (PVM) — это компонент, который уже понастоящем исполняет ваш код и является, можно сказать, последним этапом "интерпретатора Python". В Python обычно отсутствует шаг “сборки”: код выполняется сразу, а байт-код Python не является двоичным машинным кодом. Байт-код — это представление, специфичное для Python. Поэтому некоторый код на Python выполняется медленнее, чем тот же код на C/C++.
#pythonbasics #pythoninterpreter #python
Поблагодарить: https://pay.cloudtips.ru/p/a4aeb3dd
Списки. Часть 1
Допустим, что мы хотим написать приложение, которое будет узнавать текущую погоду и выводить её нам на экран. А мы хотим сохранять данные за этот день, чтобы потом можно было узнать какая ранее была погода или посчитать статистику, за какой-то промежуток времени. Где же нам хранить эти данные? На помощь на придёт "список". Список - структура данных, которая может содержать в себе, упорядоченную коллекцию объектов.
Структура данных (англ. data structure) — программная единица, позволяющая хранить и обрабатывать (машиной) однотипные и/или логически связанные данные.
В Python, мы можем создать список, из самых разных объектов, т.к. списки являются гетерогенными, а ещё они динамические, т.е. могут расти и сокращаться по мере необходимости
Типичный список в коде выглядит вот так:
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Объекты(имеют тип Integer, целое число) отделяются друг от друга запятыми
Списки можно создавать явно, как на примере выше, так и программно. Под вторым способом, понимается постепенное добавление объектов в список, по мере выполнения программы.
Создадим пустой список:
nums = [] <- это эквивалентно этому -> nums = list()
Здесь через [] или list(), мы создаём новый объект в памяти - пустой список и связываем этот объект с именем nums. Теперь nums, ссылает на наш новый объект.
В списках можно хранить любые другие объекты:
temps_22_july = ["22.07.2024", "7:00", 15.3, "8:00", 17 ...]
temps_23_july = ["23.07.2024", "7:00", 15.3, "8:00", 17 ...]
july_temps = [temps_22_july, temps_23_july ...]
Теперь посмотрим, как добавлять данные, к уже созданному списку:
temp = [] # Создаём пустой список
len(temp) # С помощью функции len(), проверяем его длину, сейчас она равна 0
temp.append(20.0) # С помощью функции append() добавляем объект в список
len(temp) # Снова проверяем его длину, теперь она равна 1
Проверим есть ли объект в списке:
if 20.0 in temp: # С помощью оператора "in" проверяем есть ли объект в списке
print(True) # Если объект в списке, печатаем True
Или можем проверить что объекта нет списке:
if 20.0 not in temp: # С помощью оператора "not in" проверяем что объекта нет в списке
temp.append(20.0) # Если это так, то добавляем его в список
Теперь посмотрим, как удалять данные из списка:
temp = [] # Создаём пустой список
print(len(temp)) # С помощью функции len(), проверяем его длину, сейчас она равна 0
temp.append(20.0) # С помощью функции append() добавляем объект в список
temp.append(21.0)
temp.append(22.0)
print(len(temp)) # Снова проверяем его длину, теперь она равна 3
print(temp) # Печатаем temp, получится [20.0, 21.0, 22.0]
temp.remove(22.0) # С помощью функции remove() удаляем объект из списка
len(temp) # С помощью функции len(), проверяем его длину, сейчас она равна 2
print(temp) # Печатаем temp, получится [20.0, 21.0]
Метод remove() удобен только если мы заранее знаем значение объекта, чаще же требуется удалять элементы по определённому индексу. Для этих целей есть специальный метод pop(), он работает следующим образом:
temp.pop() # При вызове без параметров, извлекается последний элемент списка, 21.0
temp.pop(0) # При указании индекса, извлекается элемент списка с этим индексом, 20.0
print(len(temp)) # Длина списка будет равна 0
print(temp) # Печатаем temp, получится пустой список []
Теперь воспользуемся другими методами добавления объектов в список, например мы хотим расширить наш список с помощью другого списка:
temp.extend([17.0, 19.2, 23.3]) # Расширяем список, с помощью другого списка
print(len(temp)) # Длина списка будет равна 3
print(temp) # Печатаем temp, получится [17.0, 19.2, 23.3]
Методы append() и extend() добавляют объекты в конец списка, это надо иметь ввиду, но если нам понадобится добавить объект в произвольное место, что в таком случае делать?
Для этого существует ещё один метод, называется он insert():
temp.insert(1, 18.4) # Первым параметром передаётся индекс объекта,
# перед которым будет выполенеа вставка,
# вторым значение(объект) для вставки
print(len(temp)) # Длина списка будет равна 4
print(temp) # Печатаем temp, получится [17.0, 18.4, 19.2, 23.3]
Продолжение следует, а пока что, подписывайся на мой канал в телеграм там буду выкладывать все апдейты.
Списки. Часть 2
Обзор модуля itertools
Модуль itertools Python содержит 20 инструментов, о которых должен знать каждый разработчик Python. Мы делим итераторы из модуля itertools на 5 категорий, чтобы было легче их изучать, и также представляем короткий список наиболее полезных из них.
Все итераторы из itertools
Реструктурирующие итераторы: batched, chain*, groupby, islice, pairwise* Фильтрующие итераторы: compress, dropwhile, filterfalse, takewhile Комбинаторные итераторы: combinations, combinations_with_replacement, permutations, product* Бесконечные итераторы: count, cycle, repeat Итераторы, дополняющие другие инструменты: accumulate, starmap, zip_longest
Три самых полезных итератора из itertools
product - упрощает вложенные циклы. Итератор product — это комбинаторный итератор, который очень полезен, когда нужно упрощать серию вложенных циклов for. Например, вложенный цикл, который проходит по двумерной сетке, можно переписать как один цикл с product. Итак, всякий раз, когда у нас есть два или более независимых вложенных цикла for, как показано ниже:
for x in range(width):
for y in range(height):
# Do stuff...
Мы можем преобразовать их в единый цикл, если будем использовать product:
from itertools import product
for x, y in product(range(width), range(height)):
# Do stuff...
Плоская структура предоставляет вам больше горизонтального пространства для написания кода и упрощает управление выходом из цикла.
Это очень распространенный вариант использования продукта. Если вы вернетесь к своему старому коду, уверен, вы сможете найти места, где можно было бы переписать подобный цикл.
chain - создаёт единый итерируемый объект из нескольких. Итератор chain позволяет объединить два или более итерируемых объектов, чтобы можно было проходить по ним последовательно, без необходимости явного добавления.
Рассмотрим этот фрагмент кода, который объединяет два списка, чтобы мы могли пройтись по ним:
# Типичный случай
first_list = [...]
second_list = [...]
full_list = first_list + second_list # + third_list + ...
for element in full_list:
# Do stuff...
Используя цепочку, нам не понадобилось бы сложение:
# Using chain
from itertools import chain
first_list = [...]
second_list = [...]
for element in chain(first_list, second_list): # Можно использовать больше итерируемых объектов
# Do stuff...
Это также работает в ситуациях, когда вы не можете объединить повторяющиеся переменные:
first_gen = (x ** 2 for x in range(3))
second_gen = (x ** 3 for x in range(3)) # first_gen + second_gen # TypeError!
for value in chain(first_gen, second_gen):
print(value, end=" ") # 0 1 4 0 1 8
Возможно, вы также подумали о том, что можно просто использовать встроенный list в gen1 и gen2, чтобы преобразовать их в списки, а затем объединить списки. Это верно, но обычно это пустая трата ресурсов и не будет работать с бесконечными итераторами.
Итератор chain также предоставляет вспомогательный конструктор, называемый chain.from_iterable, который, как бы сглаживает итерацию. Типичным вариантом использования было бы сделать плоскую структуру списка списков:
nested = [[1, 2, 3], [4], [], [5, 6]]
flat = list(chain.from_iterable(nested))
print(flat) # [1, 2, 3, 4, 5, 6]
Прелесть chain.from_iterable в том, что вам даже не нужно преобразовывать конечный результат в список, если все, что вы хотите, - это пройтись по элементам:
nested = [[1, 2, 3], [4], [], [5, 6]]
for value in chain.from_iterable(nested):
print(value, end=" ") # 1 2 3 4 5 6
pairwise - создаёт перекрывающиеся пары последовательных элементов Итератор pairwise принимает любой итерируемый объект и создаёт перекрывающиеся пары последовательных элементов.
По сути, это эффективная и общая реализация шаблона zip(my_list[:-1], my_list[1:]). Таким образом, pairwise полезен по двум основным причинам:
- слайсинг может быть дорогостоящей, если вы имеете дело с большим итерируемым объектом
- не все итерируемые объекты поддерживают слайсинг.
Общий шаблон, который заменяет pairwise, следующий:
names = ["Harry", "Anne", "George"]
for left, right in zip(names[:-1], names[1:]):
print(f"{left} says hi to {right}")
"""Output:
Harry says hi to Anne
Anne says hi to George
"""
При использовании pairwise вам не понадобится ни zip, ни слайсинг:
from itertools import pairwise
names = ["Harry", "Anne", "George"]
for left, right in pairwise(names):
print(f"{left} says hi to {right}")
"""Output:
Harry says hi to Anne
Anne says hi to George
"""
Реструктурирующие итераторы
batched: Создаёт кортежи длиной n из итерируемого объекта, пока он не исчерпается. Последний кортеж может содержать меньше элементов, чем n.
chain: Создаёт единый итерируемый объект из нескольких.
chain.from_iterable: Создаёт плоскую структуру из итерируемых объектов.
islice: Получает срез элементов из итерируемого объекта. Похоже на lst[:stop]
.
islice(iterable, start, stop[, step]): Вырезает первые элементы из итерируемого объекта, отбрасывая первый элемент start и возвращая только по одному элементу на каждом шаге. Аналогично lst[start: stop:step]
.
groupby(iterable, key=None): Создаёт подитераторы для последовательных значений из итерируемого объекта, для которых функция key возвращает одно и то же значение.
pairwise(iterable): Создает перекрывающиеся пары последовательных элементов iterable. Аналогично zip(lst[:-1], lst[1:])
.
batched
# Read a file 5 lines at a time.
from itertools import batched
with open(some_path, "r") as f:
for lines in batched(f, 5):
print(lines) # Process the lines.
chain
# Traverse 2+ generators in order (we can't concatenate them).
from itertools import chain
first_gen = (x ** 2 for x in range(3))
second_gen = (x ** 3 for x in range(3))
for value in chain(first_gen, second_gen):
print(value, end=" ") # 0 1 4 0 1 8
islice
# Slice generators.
from itertools import islice
squares = (x ** 2 for x in range(999_999_999))
for square in islice(squares, 10):
print(square, end=" ") # 0 1 4 9 16 25 36 49 64 81
squares = (x ** 2 for x in range(999_999_999)) # Reset
for square in islice(squares, 5, 15, 3):
print(square, end=" ") # 25 64 121 196
groupby
# Compute longest winning streak.
from itertools import groupby
game_results = "WWWLLWWWWLWWWWWWL"
longest_streak = 0
for key, streak in groupby(game_results):
if key == "W":
longest_streak = max(longest_streak, len(list(streak)))
print(longest_streak) # 6
pairwise
from itertools import pairwise
names = ["Harry", "Anne", "George"]
for left, right in pairwise(names):
print(f"{left} says hi to {right}")
"""Output:
Harry says hi to Anne
Anne says hi to George
"""
Фильтрующие итераторы
Фильтрующие итераторы принимают итерируемый объект iterable и predicate и генерируют подмножество элементов исходного итерируемого объекта. Ниже можно посмотреть простой пример для каждого из них.
compress(data, selectors): Возвращает значения из data, для которых соответствующий элемент в selectors истинный.
dropwhile(predicate, iterable): Пропускает первые элементы, удовлетворяющие предикату, и возвращает остальные.
filterfalse(predicate, iterable): Возвращает элементы из итерируемого объекта, которые не удовлетворяют предикату.
takewhile(predicate, iterable): Возвращает первые элементы, удовлетворяющие предикату.
compress: compress обычно полезен, когда у вас уже есть вычисленные селекторы, например, они получены из другого источника данных. Если вам нужно вычислить их специально для compress то, лучше использовать встроенную функцию filter.
people = ["Harry", "Anne", "George"]
can_vote = [True, True, False]
for name in compress(people, can_vote):
print(name, end=" ") # Harry Anne
dropwhile:
from itertools import dropwhile
# Top chess grandmasters and ratings (July 2024)
grandmasters = [
("Magnus Carlsen", 2832),
("Hikaru Nakamura", 2802),
("Fabiano Caruana", 2796),
("Arjun Erigaisi", 2778),
("Ian Nepomniachtchi", 2770),
]
# Drop grandmasters with rating above 2800:
for gm in dropwhile(lambda gm: gm[1] > 2800, grandmasters):
print(gm[0], end=", ") # Fabiano Caruana, Arjun Erigaisi, Ian Nepomniachtchi,
filterfalse:
# Find people who are too young to vote.
from itertools import filterfalse
people = [
("Harry", 17),
("Anne", 21),
("George", 5),
]
def can_vote(person):
return person[1] >= 18
for name, _ in filterfalse(can_vote, people):
print(name, end=", ") # Harry, George,
takewhile:
from itertools import takewhile
# Top chess grandmasters and ratings (July 2024)
grandmasters = [
("Magnus Carlsen", 2832),
("Hikaru Nakamura", 2802),
("Fabiano Caruana", 2796),
("Arjun Erigaisi", 2778),
("Ian Nepomniachtchi", 2770),
]
# Take grandmasters with rating above 2800:
for gm in takewhile(lambda gm: gm[1] > 2800, grandmasters):
print(gm[0], end=", ") # Magnus Carlsen, Hikaru Nakamura,
Комбинаторные итераторы
Комбинаторные итераторы, описанные в этом разделе, по-разному комбинируют элементы одного или нескольких итерируемых объекта, и эти итераторы обычно имеют математический подтекст.
Несмотря на то, что product является комбинаторным итератором, так же он является наиболее универсальным итератором во всем модуле! Выше мы уже ознакомились с тем, как можно его использовать.
combinations(iterable, r): Возвращает кортежи длиной r из элементов итерируемого объекта, где элементы отсортированы по их первоначальным позициям.
combinations_with_replacement(iterable, r): То же, что и combinations, но каждый элемент может повторяться неограниченное число раз.
permutations(iterable, r=None): Возвращает все перестановки длиной r из элементов итерируемого объекта.
product(*iterables, repeat=1): Создаёт кортежи, комбинируя все элементы из всех заданных итерируемых объектов.
Комбинаторные итераторы используют положение элементов в качестве ключа, когда необходимо учитывать уникальность. Другими словами, сами фактические значения никогда не сравниваются между собой.
combinations
# Possible flavours for 2-scoop ice creams (no repetition)
from itertools import combinations
flavours = ["chocolate", "vanilla", "strawberry"]
for scoops in combinations(flavours, 2):
print(scoops)
"""Output:
('chocolate', 'vanilla')
('chocolate', 'strawberry')
('vanilla', 'strawberry')
"""
combinations_with_replacement
# Possible flavours for 2-scoop ice creams (repetition allowed)
from itertools import combinations_with_replacement
flavours = ["chocolate", "vanilla", "strawberry"]
for scoops in combinations_with_replacement(flavours, 2):
print(scoops)
"""Output:
('chocolate', 'chocolate')
('chocolate', 'vanilla')
('chocolate', 'strawberry')
('vanilla', 'vanilla')
('vanilla', 'strawberry')
('strawberry', 'strawberry')
"""
permutations
# Order in which the 2 scoops can be served (no repetition)
from itertools import permutations
flavours = ["chocolate", "vanilla", "strawberry"]
for scoops in permutations(flavours, 2):
print(scoops)
"""Output:
('chocolate', 'vanilla')
('chocolate', 'strawberry')
('vanilla', 'chocolate')
('vanilla', 'strawberry')
('strawberry', 'chocolate')
('strawberry', 'vanilla')
"""
product
# All the different ice-cream orders I could make
from itertools import product
possible_scoops = [2, 3]
possibly_served_on = ["cup", "cone"]
for scoop_n, served_on in product(possible_scoops, possibly_served_on):
print(f"{scoop_n} scoops served on a {served_on}.")
"""Output:
2 scoops served on a cup.
2 scoops served on a cone.
3 scoops served on a cup.
3 scoops served on a cone.
"""
Бесконечные итераторы
Бесконечные итераторы в этом разделе создают потенциально бесконечные итераторы. Обычно они используются в сочетании с другими итераторами, например, с zip.
count(start=0, step=1): То же, что и встроенная функция range, но без точки остановки.
cycle(iterable): Бесконечно итерирует по элементам заданного итерируемого объекта.
repeat(object[, times]): Создаёт итератор, который бесконечно повторяет заданный объект, заданное количество раз.
count
# Unique ID generator.
from itertools import count
ID_GENERATOR = count()
class Sandwich:
def __init__(self):
self.sandwich_id = next(ID_GENERATOR)
print(Sandwich().sandwich_id) # 0
print(Sandwich().sandwich_id) # 1
cycle
# Create a layered sandwich.
from itertools import cycle
ingredients = cycle(["tomato", "cheese", "chicken"])
layers = 5
print("<bread", end=" ")
for _, ingredient in zip(range(layers), ingredients):
print(ingredient, end=" ")
print("bread>")
# <bread tomato cheese chicken tomato cheese bread>
repeat
# Repeatedly produce the same object.
from itertools import repeat
bread_dispenser = repeat("bread")
people = ["Harry", "Anne", "George"]
for person, bread in zip(people, bread_dispenser):
print(f"{person}, here's some {bread}, make yourself a sandwich.")
"""Output:
Harry, here's some bread, make yourself a sandwich.
Anne, here's some bread, make yourself a sandwich.
George, here's some bread, make yourself a sandwich.
"""
Итераторы, дополняющие другие инструменты
Перечисленные здесь итераторы дополняют другие итераторы языка (например, как дополняют друг друга filter и filterfalse).
accumulate(iterable[, function, *, initial=None]): Работает аналогично functools.reduce, но возвращает промежуточные значения.
starmap(function, iterable): Как map, но принимает аргументы в виде кортежей.
zip_longest(*iterables, fillvalue=None): Как zip, но останавливается на самом длинном итерируемом объекте, заполняя пустые позиции заданным значением.
accumulate Итератор accumulate работает аналогично functools.reduce . В то время как reduce выдает только конечное значение сокращения(reduction), итератор accumulate также предоставляет промежуточные значения.
# Partial products to see investment growth over time.
from functools import reduce
from itertools import accumulate
from operator import mul
interest_rates = [1.005, 1.005, 1.008, 1.01, 1.01, 1.02]
initial_investment = 1000
# Same as `math.prod`:
print(reduce(mul, interest_rates, initial_investment)) # ~1059.34
print(list(
accumulate(
interest_rates,
mul,
initial=initial_investment,
)
)) # ~ [1000, 1005, 1010.02, 1018.11, 1028.29, 1038.57, 1059.34]
starmap
# Useful when arguments are packed but function expects different arguments.
from itertools import starmap
to_compute = [
(2, 3), # 8
(2, 4), # 16
(2, 5), # 32
(3, 2), # 9
(3, 3), # 27
]
print(list(
starmap(pow, to_compute) # [8, 16, 32, 9, 27]
))
# Compare to:
bases = [2, 2, 2, 3, 3]
exponents = [3, 4, 5, 2, 3]
print(list(
map(pow, bases, exponents) # [8, 16, 32, 9, 27]
))
zip_longest
# Go over multiple iterables until all are exhausted.
from itertools import repeat, zip_longest
# Available ingredients:
bread = repeat("bread", 4)
mayo = repeat("mayo", 2)
chicken = repeat("chicken", 4)
for ingredients in zip_longest(bread, mayo, chicken, fillvalue=""):
print(f"Here's a sandwich with {' '.join(ingredients)}.")
"""Output:
Here's a sandwich with bread mayo chicken.
Here's a sandwich with bread mayo chicken.
Here's a sandwich with bread chicken.
Here's a sandwich with bread chicken.
"""
Функция tee
Функция tee великолепна, потому что она, реализует то, что противоречит самому определению итераторов. Итератор предоставляет поток данных, который может быть использован только один раз, но tee(iterable, n=2) можно использовать для создания стольких независимых итераторов из одного источника данных, сколько вы захотите.
До того, как pairwise был представлен в Python 3.10, tee обеспечивал хороший способ его реализации:
from itertools import tee
def pairwise(iterable):
first, second = tee(iterable, 2)
next(second)
yield from zip(first, second)
Продолжение следует, а пока что, подписывайся на мой канал в телеграм там буду выкладывать все апдейты.