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

Игорь Мартюшев
Бэкенд-разработчик на Python. Помог с подготовкой этого материала.
Базовые приёмы
zip() — объединение элементов из нескольких коллекций
Часто в задачах нужно пройтись по нескольким коллекциям параллельно. Например, у нас есть список игроков, их команды и количество очков.
Первое, что приходит в голову новичкам, — использовать индексы через range(len(...))
:
players = ["Сергей", "Антон", "Михаил"]
teams = ["Волки", "Тигры", "Ястребы"]
scores = [15, 20, 17]
for i in range(len(players)):
print(f"{players[i]} ({teams[i]}) — {scores[i]} очков")
Результат:
Сергей (Волки) — 15 очков
Антон (Тигры) — 20 очков
Михаил (Ястребы) — 17 очков
Так можно, но это не самый читаемый вариант, потому что приходится следить и за индексами, и за данными. А если один список окажется короче, можно получить IndexError
.
Вместо индексов можно использовать zip()
. Он сам пройдёт по спискам синхронно и вернёт кортежи:
for player, team, score in zip(players, teams, scores):
print(f"{player} ({team}) — {score} очков")
Результат:
Сергей (Волки) — 15 очков
Антон (Тигры) — 20 очков
Михаил (Ястребы) — 17 очков
Получим то же самое, но код при этом читается проще, а ошибок при разной длине списков не будет: zip()
просто остановится на самом коротком.
Объединяем строки и числа
zip()
работает не только со списками, но и с любыми итерируемыми объектами. Например, пронумеруем буквы:
letters = "Python"
positions = range(1, 7)
for letter, pos in zip(letters, positions):
print(f"{pos}: {letter}")
Результат:
1: P
2: y
3: t
4: h
5: o
6: n
Создаём словарь «ключ-значение»
Если у нас есть два списка, например даты матчей и количество зрителей, их можно объединить в словарь:
dates = ["2025-08-01", "2025-08-02", "2025-08-03"]
viewers = [1200, 980, 1430]
attendance = dict(zip(dates, viewers))
print(attendance)
Результат:
{'2025-08-01': 1200, '2025-08-02': 980, '2025-08-03': 1430}
Разделяем данные на части
zip()
умеет работать и «в обратную сторону». Если у нас есть координаты в виде пар (x, y)
, можно получить отдельные коллекции X и Y.
points = [(2, 5), (4, 8), (1, 3)]
x_coords, y_coords = zip(*points)
print(x_coords) # (2, 4, 1)
print(y_coords) # (5, 8, 3)
Здесь *
— это оператор распаковки списка аргументов в функцию.
Результат:
(2, 4, 1)
(5, 8, 3)
enumerate() — перебор с индексами
Иногда при работе с данными важно знать не только сам элемент, но и его положение в последовательности. Это может пригодиться в самых разных сценариях, например:
при поиске ошибок в логах, когда нужно вывести строку вместе с её номером в файле;
при анализе данных, когда важно не потерять исходный порядок записей;
при отладке алгоритма, чтобы видеть, на каком шаге что-то пошло не так.
Самый простой способ сделать — завести цикл с range(len(...))
и доставать элемент по индексу:
log = [
"INFO: старт системы",
"WARNING: низкий заряд батареи",
"ERROR: модуль датчика недоступен",
"INFO: перезапуск",
"ERROR: превышен лимит памяти"
]
for i in range(len(log)):
if log[i].startswith("ERROR"):
print(f"Строка {i + 1}: {log[i]}")
Результат:
Строка 3: ERROR: модуль датчика недоступен
Строка 5: ERROR: превышен лимит памяти
Но у этого подхода есть минусы: приходится отдельно получать длину, вручную обращаться к элементам по индексу и прибавлять 1, если хочешь начать не с нуля. А с генераторами и другими неиндексируемыми объектами он вообще не сработает.
Тот же пример с enumerate()
:
for line_num, entry in enumerate(log, start=1):
if entry.startswith("ERROR"):
print(f"Строка {line_num}: {entry}")
Результат:
Строка 3: ERROR: модуль датчика недоступен
Строка 5: ERROR: превышен лимит памяти
Теперь не нужно вручную таскать i
и следить за прибавлением единицы. Код короче и сразу показывает, что мы одновременно и перебираем элементы, и используем их порядковые номера.
В этом примере индекс (line_num
) указывает точное место ошибки в логе, а не просто служит для нумерации.
Сохранение исходного порядка при сортировке
Бывает, что нужно отсортировать данные, но при этом помнить их изначальный порядок, чтобы, например, потом вернуться к исходной версии или сопоставить с другими данными.
Пример. У нас есть список товаров с их ценами, и мы хотим отсортировать их по убыванию цены, но при этом знать, на каком месте они были изначально в каталоге.
products = ["Хлеб", "Сыр", "Молоко", "Шоколад"]
prices = [50, 320, 90, 150]
indexed_prices = list(enumerate(prices))
indexed_prices.sort(key=lambda x: x[1], reverse=True)
for index, price in indexed_prices:
print(f"{products[index]} (позиция {index + 1}): {price} ₽")
Результат:
Сыр (позиция 2): 320 ₽
Шоколад (позиция 4): 150 ₽
Молоко (позиция 3): 90 ₽
Хлеб (позиция 1): 50 ₽
Здесь используется анонимная функция lambda
(про неё подробно расскажем ниже). Она берёт второй элемент кортежа и возвращает его для сортировки.
Обновление части элементов на месте
Если нужно изменить не все элементы, а только часть по определённым правилам, индекс от enumerate()
меняет значения прямо в оригинальном списке, не создавая копию.
Пример. У нас есть список цен в магазине, и мы хотим поднять на 10% только те, что ниже 100 рублей.
prices = [50, 120, 80, 200, 90]
for i, price in enumerate(prices):
if price < 100:
prices[i] = round(price * 1.1, 2)
print(prices)
Результат:
[55.0, 120, 88.0, 200, 99.0]
Мы перебираем элементы вместе с индексами, проверяем условие и сразу обновляем нужные значения в исходном списке.
Списковые включения (list comprehension) — генерация и фильтрация списков
Один из самых частых шаблонов — создать список на основе другого списка или любого итерируемого объекта, возможно с фильтрацией или преобразованием элементов. Самый прямой способ — пройтись по исходным данным в цикле for
, добавляя новые элементы в пустой список.
Например, у нас есть список цен и мы хотим перевести их в доллары по курсу:
prices_rub = [1200, 850, 3100]
prices_usd = []
for price in prices_rub:
prices_usd.append(round(price / 90, 2))
print(prices_usd)
Результат:
[13.33, 9.44, 34.44]
Мы заводим пустой список, вызываем .append()
и пишем лишние строки, хотя задача простая.
Тот же пример через list comprehension. Списковое включение делает то же самое в одну строку:
prices_rub = [1200, 850, 3100]
prices_usd = [round(price / 90, 2) for price in prices_rub]
print(prices_usd)
Результат:
[13.33, 9.44, 34.44]
Внутри квадратных скобок описывается сразу и цикл, и логика преобразования.
Фильтрация элементов
Включения поддерживают условие if
для фильтрации. Например, оставим только цены выше 1 000 ₽ и сразу переведём их в доллары:
prices_rub = [1200, 850, 3100]
high_prices_usd = [round(p / 90, 2) for p in prices_rub if p > 1000]
print(high_prices_usd)
Результат:
[13.33, 34.44]
Генерация списков по формуле
List comprehension полезны не только для работы с готовыми данными, но и для генерации. Например, создадим список всех квадратов чисел от 1 до 10:
squares = [n**2 for n in range(1, 11)]
print(squares)
Результат:
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Вложенные циклы
Внутри включения можно писать и несколько циклов. Например, получим все координаты на маленькой сетке 2×3:
coords = [(x, y) for x in range(1, 3) for y in range(1, 4)]
print(coords)
Результат:
[(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3)]
Общая рекомендация. List comprehension стоит применять, когда:
нужно быстро и наглядно преобразовать или отфильтровать данные;
генерация списка может быть описана одной логической
конструкцией;
важно сократить количество вспомогательных строк, не жертвуя читаемостью.
Но если логика сложная, с несколькими условиями и вложенными циклами, — такой код будет труднее читать, чем обычный цикл.
map() и filter() — преобразование и фильтрация данных
В Python часто нужно либо преобразовать каждый элемент последовательности, либо отфильтровать элементы по условию.
Самый очевидный способ — пройтись циклом for
, проверяя или изменяя элементы.
Например, хотим перевести список температур из градусов Цельсия в градусы Фаренгейта:
temps_c = [0, 12, 24, 30]
temps_f = []
for t in temps_c:
temps_f.append(round(t * 9/5 + 32, 1))
print(temps_f)
Результат:
[32.0, 53.6, 75.2, 86.0]
В такой ситуации приходится вручную добавлять результат в новый список через append()
, растягивая код на несколько строк.
Преобразование с map()
Функция map(func, iterable)
применяет func
ко всем элементам iterable
и возвращает итератор с результатами. В нашем примере:
temps_c = [0, 12, 24, 30]
temps_f = list(map(lambda t: round(t * 9/5 + 32, 1), temps_c))
print(temps_f)
Результат:
[32.0, 53.6, 75.2, 86.0]
map()
полезен, если уже есть готовая функция для обработки одного элемента и её нужно применить ко всем данным.
Фильтрация с filter()
filter(func, iterable)
оставляет только те элементы, для которых func
вернёт True
. Например, выберем только положительные числа:
numbers = [-5, 0, 7, -2, 10]
positive = list(filter(lambda x: x > 0, numbers))
print(positive)
Результат:
[7, 10]
Общая рекомендация. List comprehension часто делают то же самое, что map()
и filter()
, но выглядят привычнее для разработчиков и читаются проще. При этом map()
и filter()
удобны, когда уже есть готовая функция для обработки или фильтрации (особенно если она импортируется из другого модуля) и её нужно применить ко всем элементам без написания отдельного цикла.
Синтаксические сокращения
lambda — компактные анонимные функции
В Python можно создать функцию без имени — через lambda
. Её используют, когда нужно что-то простое: посчитать длину строки при сортировке, проверить поле словаря при фильтрации.
Такая функция всегда состоит из одного выражения, результат которого возвращается. Обычно её пишут одной строкой и не выносят в отдельное определение через def
.
Сортировка
Допустим, нужно отсортировать список книг по длине названия:
books = ["Война и мир", "Мастер и Маргарита", "Тихий Дон"]
sorted_books = sorted(books, key=lambda title: len(title))
print(sorted_books)
Результат:
['Тихий Дон', 'Война и мир', 'Мастер и Маргарита']
Без lambda
пришлось бы писать отдельную функцию def by_length(word): return len(word)
ради одного вызова.
Применение в map()
map()
применяет функцию ко всем элементам последовательности. Здесь у нас список названий книг, и мы хотим сделать так, чтобы каждое слово начиналось с заглавной буквы. Функция lambda name: name.title()
делает это для одного названия, а map()
— для всего списка.
books = ["война и мир", "мастер и маргарита", "тихий дон"]
titles = list(map(lambda name: name.title(), books))
print(titles)
Результат:
['Война И Мир', 'Мастер И Маргарита', 'Тихий Дон']
Метод .title()
делает заглавной первую букву каждого слова, поэтому союз «и» тоже будет с большой буквы — это нормально для этого метода.
Фильтрация с filter()
Есть список отзывов о книгах, и мы хотим оставить только те, что длиннее 30 символов, — например, для дальнейшего анализа:
reviews = [
"Отличная книга!",
"Читал взахлёб, потрясающая история.",
"Неплохо, но местами скучно.",
"Самая сильная книга автора, советую."
]
long_reviews = list(filter(lambda r: len(r) > 30, reviews))
print(long_reviews)
Результат:
['Читал взахлёб, потрясающая история.', 'Самая сильная книга автора, советую.']
Сортировка словарей по полю
Есть список авторов и количество проданных книг. Нужно отсортировать их по тиражу:
authors = [
{"name": "Лев Толстой", "sold": 5000000},
{"name": "Михаил Шолохов", "sold": 3200000},
{"name": "Михаил Булгаков", "sold": 4100000}
]
sorted_authors = sorted(authors, key=lambda a: a["sold"], reverse=True)
print(sorted_authors)
Результат:
[{'name': 'Лев Толстой', 'sold': 5000000}, {'name': 'Михаил Булгаков', 'sold': 4100000}, {'name': 'Михаил Шолохов', 'sold': 3200000}]
Общая рекомендация. lambda
хороша для небольших операций, которые нужны один раз и не требуют отдельного объявления. Если же логика сложнее, удобнее вынести её в обычную функцию через def
.
Тернарный оператор (x if ... else y) — короткое ветвление
Обычно для выбора одного из двух значений в зависимости от условия используют полную конструкцию if
/ else
.
Допустим, приложение показывает прогноз погоды. Если температура ниже нуля — пишем «мороз», иначе — «плюс»:
temperature = -5
if temperature < 0:
weather = "мороз"
else:
weather = "плюс"
print(weather)
Результат:
мороз
Тернарный оператор позволяет сделать то же самое компактнее:
temperature = -5
weather = "мороз" if temperature < 0 else "плюс"
print(weather)
Результат:
мороз
Читается так: «Присвоить "мороз"
, если температура меньше нуля, иначе "плюс"
».
Пример 1: статус бронирования
В системе бронирования столика в ресторане нужно показать «Подтверждено» или «Ожидает подтверждения» в зависимости от статуса.
confirmed = False
status = "Подтверждено" if confirmed else "Ожидает подтверждения"
print(status)
Результат:
Ожидает подтверждения
Пример 2: расчёт стоимости доставки
Если сумма заказа больше 3 000 рублей — доставка бесплатная, иначе — 250 рублей.
order_total = 2800
delivery_cost = 0 if order_total > 3000 else 250
print(delivery_cost)
Результат:
250
Общая рекомендация. Тернарный оператор полезен, когда логика выбора простая и значение можно определить в одну строку. Если проверок несколько или условие сложное, лучше использовать обычный if
/ else
для читаемости.
Множественное присваивание (a, b = b, a) — замена значений и распаковка
В Python можно присваивать значения сразу нескольким переменным. Это упрощает код и избавляет от повторов, особенно если данные уже упакованы в список, кортеж или другую структуру.
Распаковка данных из списка или кортежа
Например, сайт доставки еды хранит координаты курьера в виде списка из двух чисел — широта и долгота.
Классический способ выглядел бы так:
courier_location = [55.7522, 37.6156]
lat = courier_location[0]
lon = courier_location[1]
print(lat, lon)
Результат:
55.7522 37.6156
С множественным присваиванием мы можем записать то же самое короче и понятнее:
courier_location = [55.7522, 37.6156]
lat, lon = courier_location
print(lat, lon)
Результат:
55.7522 37.6156
Обмен значений без временной переменной
Множественное присваивание часто используют, чтобы обменять значения местами без временной переменной. Например, сортируем очередь на аттракцион так, чтобы сначала шли дети, потом взрослые:
first = "взрослый"
second = "ребёнок"
first, second = second, first
print(first, second)
Результат:
ребёнок взрослый
Перебор кортежей в цикле
Например, у нас есть список заказов с названием блюда и именем клиента:
orders = [
("Пицца Маргарита", "Анна"),
("Суши с лососем", "Игорь"),
("Шаверма", "Марина")
]
for dish, client in orders:
print(f"{client} заказал(а) {dish}")
Результат:
Анна заказал(а) Пицца Маргарита
Игорь заказал(а) Суши с лососем
Марина заказал(а) Шаверма
Игнорирование ненужных данных с _
Если в кортеже или списке есть значения, которые в этом месте не нужны, их можно пропустить, присвоив переменной _
.
Например, к заказам добавили цену, но она нам сейчас не нужна.
order = ("Пицца Маргарита", "Анна", 650)
dish, client, _ = order
print(f"{client} заказал(а) {dish}")
Результат:
Анна заказал(а) Пицца Маргарита
Средний уровень
Дополнительный приём с zip()
В базовом уровне мы посмотрели, как zip()
объединяет списки или строки. Но на практике у этой функции есть и более гибкие сценарии. Один из самых распространённых — транспонирование таблицы с помощью zip(*temps)
.
Разберём его подробнее.
Представим, что у нас есть матрица температур за три дня по городам. Нужно изменить таблицу, чтобы по строкам были дни, а по столбцам — города. Сделать это можно одной строчкой с помощью распаковки *
в вызове zip()
.
temps = [
[20, 21, 19], # Москва, СПб, Казань
[18, 20, 17],
[22, 23, 21]
]
transposed = list(zip(*temps))
for row in transposed:
print(row)
Результат:
(20, 18, 22)
(21, 20, 23)
(19, 17, 21)
Генераторы — создание последовательностей без лишней нагрузки на память
Обычные списки в Python хранят все элементы в памяти сразу. Если данных много, это может быть накладно: лишняя нагрузка на оперативную память и время на создание.
Генераторы позволяют выдавать элементы по одному без хранения всего списка. Это полезно, когда:
данные нужно просто перебрать и больше не использовать;
элементов очень много;
значения вычисляются постепенно, а не известны сразу.
Есть два основных способа сделать генератор: генераторное выражение и функция-генератор с yield
.
Генераторное выражение ()
Похоже на списковое включение, только в круглых скобках. Разница в том, что оно возвращает не готовый список, а объект-генератор, который выдаёт элементы по запросу.
Например, у нас есть список книг и мы хотим посчитать количество символов в каждом названии. Списковое включение создаст сразу весь список, а генератор — нет.
books = ["Война и мир", "Преступление и наказание", "Мастер и Маргарита"]
title_lengths = (len(title) for title in books)
for length in title_lengths:
print(length)
Результат:
12
26
21
Генератор не хранит все значения в памяти, поэтому подходит для работы с большими наборами данных.
Функции-генераторы и yield
Иногда логику генерации данных сложно записать одной строчкой. В этом случае можно написать обычную функцию, но вместо return
использовать yield
. Каждый вызов yield
отдаёт значение и «замораживает» выполнение функции до следующего запроса.
Например, генерируем список доступных мест в зале, пока не дойдём до конца:
def seat_numbers(rows, seats_per_row):
for row in range(1, rows + 1):
for seat in range(1, seats_per_row + 1):
yield f"Ряд {row}, место {seat}"
for place in seat_numbers(2, 3):
print(place)
Результат:
Ряд 1, место 1
Ряд 1, место 2
Ряд 1, место 3
Ряд 2, место 1
Ряд 2, место 2
Ряд 2, место 3
Генератор остановится сам, когда закончатся значения, и при этом не будет хранить в памяти весь список мест.
Пример. У нас интернет-магазин, который каждую ночь сохраняет в файл список заказов за день. Файл может быть огромным, но нам нужно быстро найти только заказы с определённым товаром — скажем, с «кофемашиной»:
def read_orders(filename):
with open(filename, encoding="utf-8") as f:
for line in f:
yield line.strip()
for order in read_orders("orders_2025_08_09.txt"):
if "кофемашина" in order.lower():
print(f"Нашли заказ: {order}")
Результат:
Нашли заказ: Заказ #1524 — кофемашина DeLonghi, 2 шт.
Нашли заказ: Заказ #1589 — кофемашина Philips, 1 шт.
Множества (set) — работа с уникальными значениями и сравнением коллекций
В Python множество (set
) — это коллекция, которая автоматически хранит только уникальные значения и оптимизирована для быстрых операций сравнения и поиска. В отличие от списков, множества не запоминают порядок элементов, зато позволяют за один оператор найти пересечение, разность или объединение данных.
Создаются множества либо с помощью фигурных скобок {}
, либо функцией set()
для преобразования из других типов.
Пример. Интернет-магазин провёл две акции: на кофемашины и на тостеры. Покупатели могли участвовать в обеих, и в списках есть повторы.
coffee_buyers = {"Анна", "Игорь", "Марина", "Олег", "Анна"}
toaster_buyers = {"Марина", "Дмитрий", "Олег"}
Удаление дубликатов
print(coffee_buyers)
Результат:
{'Марина', 'Олег', 'Анна', 'Игорь'}
Дубликаты исчезли, порядок элементов может отличаться.
Пересечение множеств
Узнаем, кто в нашем примере участвовал в обеих акциях.
both = coffee_buyers & toaster_buyers
print(both)
Результат:
{'Марина', 'Олег'}
Разность множеств
А теперь узнаем, кто купил кофемашину, но не покупал тостер:
only_coffee = coffee_buyers - toaster_buyers
print(only_coffee)
Результат:
{'Анна', 'Игорь'}
Объединение множеств
Чтобы получить список всех клиентов, которые купили хотя бы один из товаров, используем объединение:
all_buyers = coffee_buyers | toaster_buyers
print(all_buyers)
Результат:
{'Анна', 'Олег', 'Дмитрий', 'Марина', 'Игорь'}
Проверка вхождения
Проверить, купил ли клиент конкретный товар, можно через оператор in. Это работает очень быстро, даже на больших данных:
if "Анна" in coffee_buyers:
print("Анна покупала кофемашину")
else:
print("Анна не покупала кофемашину")
Результат:
Анна покупала кофемашину
collections.Counter — подсчёт количества повторяющихся элементов
Есть задачи, когда нужно быстро посчитать, сколько раз каждый элемент встречается в коллекции. Можно написать цикл, вручную проверять словарь и увеличивать счётчики — а можно использовать collections.Counter
, который делает всё это за нас.
Counter
принимает любую последовательность (строку, список, кортеж) и возвращает словарь-подобный объект, где ключ — элемент, а значение — количество его повторений.
Пример 1. Представим, что у нас есть список товаров, которые клиенты добавляли в корзину за день. Нужно понять, что сегодня было в топе.
from collections import Counter
cart_items = [
"кофемашина", "чайник", "кофемашина", "тостер",
"чайник", "чайник", "кофемашина"
]
item_counts = Counter(cart_items)
print(item_counts)
Counter
можно сразу попросить показать только несколько самых популярных позиций:
top_items = item_counts.most_common(2)
print(top_items)
Результат:
[('кофемашина', 3), ('чайник', 3)]
Пример 2. Допустим, мы анализируем отзывы покупателей и хотим понять, какие слова встречаются чаще всего.
from collections import Counter
reviews = """
Кофемашина отличная, работает тихо.
Чайник хороший, но кофемашина лучше.
Кофемашина выглядит стильно и варит вкусный кофе.
"""
# Преобразовываем всё в нижний регистр и разбиваем на слова
words = reviews.lower().replace(".", "").split()
word_counts = Counter(words)
print(word_counts.most_common(3))
Результат:
[('кофемашина', 3), ('и', 1), ('отличная,', 1)]
В реальном проекте мы бы ещё очистили текст от пунктуации и стоп-слов («и», «но», «а» и т. п.), но даже базовый пример показывает, как Counter
помогает быстро получить статистику.
collections.defaultdict — словарь с автоматическим значением по умолчанию
Обычный словарь в Python вызывает ошибку KeyError
, если обратиться к несуществующему ключу. Поэтому часто перед записью приходится проверять, есть ли ключ, и, если нет, создавать его.
defaultdict
при обращении к новому ключу автоматически создаёт для него значение по умолчанию, которое мы задаём при создании словаря.
Пример. Представим, что у нас есть логи чата и каждое сообщение хранится в виде кортежа (дата, текст)
. Нужно сгруппировать их так, чтобы у каждой даты был список всех сообщений за этот день.
from collections import defaultdict
messages = [
("2025-08-08", "Встречаемся в 14:00"),
("2025-08-08", "Не забудь документы"),
("2025-08-09", "Презентация готова?"),
("2025-08-09", "Проверь почту"),
("2025-08-10", "Отправил отчёт")
]
messages_by_date = defaultdict(list)
for date, text in messages:
messages_by_date[date].append(text)
for date, texts in messages_by_date.items():
print(date)
for msg in texts:
print(f" - {msg}")
Результат:
2025-08-08
- Встречаемся в 14:00
- Не забудь документы
2025-08-09
- Презентация готова?
- Проверь почту
2025-08-10
- Отправил отчёт
В обычном словаре пришлось бы писать что-то вроде:
if date not in messages_by_date:
messages_by_date[date] = []
defaultdict(list)
убирает этот шаг: при первом обращении к новой дате он сам создаёт пустой список, куда можно сразу добавлять сообщения.
Распаковка списков — доступ к частям коллекции без использования индексов
В Python можно «распаковывать» коллекции в несколько переменных, не обращаясь к элементам по индексам.
Простая распаковка
Если известно, что в коллекции ровно столько элементов, сколько переменных слева, можно присвоить их напрямую.
Ситуация: у нас есть результат функции, которая возвращает город и страну. Вместо того чтобы доставать их по индексам, можно присвоить сразу в две переменные.
pair = ["Москва", "Россия"]
city, country = pair
print(city) # Москва
print(country) # Россия
Здесь задаётся команда: «Возьми первый элемент и положи в city
, а второй — в country
».
Результат:
Город: Москва
Страна: Россия
Расширенная распаковка с *
Если элементов больше, чем переменных, можно использовать *
, чтобы собрать «остаток» в список. Например, у нас есть список заказов за день, но мы хотим отдельно сохранить первый заказ, а остальные — сгруппировать.
orders = ["Заказ #101", "Заказ #102", "Заказ #103", "Заказ #104"]
first, *rest = orders
print(f"Первый заказ: {first}")
print(f"Остальные заказы: {rest}")
Результат:
Первый заказ: Заказ #101
Остальные заказы: ['Заказ #102', 'Заказ #103', 'Заказ #104']
*
собирает «остаток» элементов в отдельный список.
Распаковка внутри цикла
Пример 1. Пары ключ-значение в словаре.
Часто коллекции уже содержат данные в виде пар или кортежей. Допустим, у нас есть словарь столиц и мы хотим вывести фразу «<столица> — столица <страны>».
capitals = {"Россия": "Москва", "Франция": "Париж"}
for country, capital in capitals.items():
print(f"{capital} — столица {country}")
dict.items()
возвращает пары (ключ, значение)
.
Результат:
Москва — столица Россия
Париж — столица Франция
Пример 2. Несколько списков с zip()
.
У нас есть список студентов и список их оценок. Нужно вывести в формате «имя — баллы».
students = ["Аня", "Борис", "Сергей"]
scores = [85, 92, 78]
for name, score in zip(students, scores):
print(f"{name} — {score} баллов")
Результат:
Аня — 85 баллов
Борис — 92 балла
Сергей — 78 баллов
*args, **kwargs — работа с переменным числом аргументов в функциях
Иногда заранее неизвестно, сколько аргументов придётся передать в функцию. Чтобы не собирать их вручную в список или словарь, в Python есть специальный синтаксис:
*args
— собирает все позиционные аргументы в кортеж;**kwargs
— собирает все именованные аргументы в словарь.
Полезно при написании универсальных функций, которые должны принимать разное количество параметров.
*args
— произвольное количество позиционных аргументов
Представим, что мы делаем сервис, который считает суммарный балл игрока в настольной игре. Количество раундов каждый раз разное: в одной партии их может быть три, в другой — пять или больше. Заранее это число неизвестно, поэтому перечислять все параметры вручную неудобно.
def total_score(*args):
return sum(args)
print(total_score(5, 10, 15)) # три раунда
print(total_score(3, 7)) # два раунда
Результат:
30
10
Все переданные числа собираются в кортеж args
, а дальше мы просто используем sum()
для подсчёта общей суммы.
**kwargs
— произвольное количество именованных аргументов
Напишем функцию для вывода информации о пользователе. Обязателен только параметр name
, остальные данные могут быть любыми и приходить, например, из формы на сайте. В одном случае это может быть возраст и город, в другом — хобби и любимый фильм. Мы не знаем, какие именно поля будут переданы, поэтому фиксировать их в параметрах функции неудобно.
def show_profile(name, **kwargs):
print(f"Имя: {name}")
for key, value in kwargs.items():
print(f"{key.capitalize()}: {value}")
show_profile("Анна", age=28, city="Москва", hobby="чтение")
Результат:
Имя: Анна
Age: 28
City: Москва
Hobby: чтение
Все дополнительные именованные аргументы собираются в словарь kwargs
. Благодаря этому функция остаётся универсальной и подстраивается под набор данных.
Комбинирование *args
и **kwargs
Представим, что мы пишем функцию для отправки уведомлений в командный чат. Иногда нужно отправить сразу несколько сообщений, а ещё указать дополнительные настройки — например, через какой канал отправлять, насколько сообщение важное и кто его автор.
Используем *args
и **kwargs
:
def send_notifications(*args, **kwargs):
for message in args:
print(f"Отправка: {message}")
print("Параметры доставки:", kwargs)
send_notifications(
"Сервер перезапущен",
"Новая версия задеплоена",
channel="dev-team",
priority="high",
author="CI/CD бот"
)
Результат:
Отправка: Сервер перезапущен
Отправка: Новая версия задеплоена
Параметры доставки: {'channel': 'dev-team', 'priority': 'high', 'author': 'CI/CD бот'}
*args
позволяет перечислить сколько угодно сообщений, а **kwargs
— добавить к ним всё, что нужно для отправки: канал, автора, уровень важности. Функция остаётся одной и той же, но её можно использовать в самых разных ситуациях.
Продвинутый уровень
Дополнительные приёмы
Комбинация zip()
и enumerate()
В базовом уровне мы посмотрели простое объединение списков через zip()
. В среднем уровне — познакомились с приёмом транспонирования таблицы через zip(*temps)
.
Теперь разберём более сложный случай: когда данные нужно не только объединить, но и сразу пронумеровать. Для этого можно совместить zip()
и enumerate()
.
Например, у нас есть список игроков, их команд и количество очков. Нужно получить список строк вида "1. Иван (Волки) — 15 очков"
, но только для тех, кто набрал больше 10 очков.
players = ["Сергей", "Антон", "Михаил"]
teams = ["Волки", "Тигры", "Ястребы"]
scores = [15, 8, 17]
report = [
f"{i}. {player} ({team}) — {score} очков"
for i, (player, team, score) in enumerate(zip(players, teams, scores), start=1)
if score > 10
]
for line in report:
print(line)
Результат:
1. Сергей (Волки) — 15 очков
3. Михаил (Ястребы) — 17 очков
Здесь zip()
объединяет три списка, enumerate()
даёт нумерацию с единицы, а list comprehension фильтрует игроков по количеству очков и формирует готовые строки.
Комбинация map()
и filter()
В разделе для базового уровня мы разобрали map()
и filter()
по отдельности, теперь посмотрим, как они работают вместе.
Допустим, у нас список строк с числами и нужно:
Убрать пустые строки.
Преобразовать всё в числа.
Оставить только чётные.
raw_data = ["10", "15", "", "22", "9"]
result = list(
filter(lambda x: x % 2 == 0,
map(int, filter(None, raw_data)))
)
print(result)
Результат:
[10, 22]
Повторим здесь ремарку. Объединять map()
и filter()
в одну цепочку можно, когда нужно записать несколько шагов обработки максимально компактно. Но в большинстве случаев list comprehension выглядит нагляднее и читается проще.
Пример реальной задачи: обработка данных из CSV
Представим, что у нас есть список словарей с заказами и нужно получить список имён клиентов, оформивших заказ дороже 1 000 рублей:
orders = [
{"name": "Анна", "amount": 1250},
{"name": "Иван", "amount": 980},
{"name": "Мария", "amount": 1430}
]
big_orders = list(
map(lambda o: o["name"],
filter(lambda o: o["amount"] > 1000, orders))
)
print(big_orders)
Результат:
['Анна', 'Мария']
with — управление ресурсами с автоматическим закрытием
В Python with
используют, когда нужно поработать с ресурсом, который важно закрыть или освободить. Это может быть файл, соединение с базой или, например, блокировка в многопоточном коде.
Если делать по старинке, придётся открывать ресурс, оборачивать работу с ним в try…finally
и в конце вызывать .close()
. Например, читаем список пользователей из файла:
file = open("users.txt", encoding="utf-8")
try:
data = file.read()
finally:
file.close()
С with
код короче, плюс Python сам закроет ресурс, даже если в процессе возникнет ошибка:
with open("users.txt", encoding="utf-8") as file:
data = file.read()
Пример. Допустим, интернет-магазин сохраняет последние покупки клиента в файл, чтобы потом показать их в личном кабинете.
purchases = ["Ноутбук", "Беспроводная мышь", "Рюкзак для ноутбука"]
with open("last_purchases.txt", "w", encoding="utf-8") as file:
for item in purchases:
file.write(item + "\n")
Вышли из блока with
— значит, покупки уже записаны, файл закрыт и их можно будет без проблем прочитать позже.
with
применяют не только для файлов. Он работает с любыми объектами, у которых определены методы __enter__
и __exit__
. Это может быть, например:
работа с подключением к базе данных,
временное изменение настроек,
управление блокировками в многопоточном коде,
открытие и автоматическое закрытие сетевых соединений.
dataclasses — описание структур данных с минимальным кодом
Когда нужно хранить связанные между собой данные, например сведения о заказе, пользователе или товаре, можно сделать обычный класс. Но в нём придётся писать конструктор __init__
, методы сравнения, возможно, __repr__
для красивого вывода.
Пример. Представим, что у нас есть список участников с именами, городами и временем забега. Нам нужно его хранить и сортировать по времени.
Классический способ:
class Runner:
def __init__(self, name, city, time):
self.name = name
self.city = city
self.time = time # время в минутах
def __repr__(self):
return f"Runner({self.name!r}, {self.city!r}, {self.time})"
runners = [
Runner("Анна", "Москва", 245),
Runner("Игорь", "Казань", 230),
Runner("Марина", "СПб", 260)
]
print(sorted(runners, key=lambda r: r.time))
Результат:
[Runner('Игорь', 'Казань', 230), Runner('Анна', 'Москва', 245), Runner('Марина', 'СПб', 260)]
С модулем dataclasses
достаточно объявить класс с нужными полями и пометить его декоратором @dataclass
. Python сам сгенерирует все базовые методы.
from dataclasses import dataclass
@dataclass(order=True)
class Runner:
time: int
name: str
city: str
runners = [
Runner(245, "Анна", "Москва"),
Runner(230, "Игорь", "Казань"),
Runner(260, "Марина", "СПб")
]
print(sorted(runners))
Результат:
[Runner(time=230, name='Игорь', city='Казань'), Runner(time=245, name='Анна', city='Москва'), Runner(time=260, name='Марина', city='СПб')]
order=True
позволяет сразу сравнивать и сортировать такие объекты. Порядок определяется тем, какие поля объявлены первыми, поэтому здесь всё упорядочилось по времени.
functools.partial — создание версии функции с заданными аргументами
Иногда функция принимает много аргументов, но часть из них почти всегда одинаковая. Чтобы не повторять их каждый раз, можно сделать «заготовку» функции с уже подставленными значениями.
Удобно, когда логика используется в разных местах, но с одними и теми же параметрами.
Пример. Представим, что у нас есть функция отправки уведомлений, в которую нужно передавать канал (email
, sms
, push
), сообщение и имя получателя:
def send_notification(channel, message, user):
print(f"[{channel}] {user}: {message}")
В большинстве случаев мы отправляем письма на email, так что параметр channel
почти всегда одинаковый.
Можно писать каждый раз:
send_notification("email", "Ваш заказ готов", "Анна")
send_notification("email", "Пароль успешно изменён", "Игорь")
А можно вместо этого создать отдельную функцию для email с помощью partial
:
from functools import partial
email_notify = partial(send_notification, "email")
email_notify("Ваш заказ готов", "Анна")
email_notify("Пароль успешно изменён", "Игорь")
Результат:
[email] Анна: Ваш заказ готов
[email] Игорь: Пароль успешно изменён
Теперь первый аргумент всегда будет "email", а остальные передаются при вызове.
Ещё пример. Допустим, у нас есть функция, которая форматирует дату в заданном формате:
from datetime import datetime
def format_date(date_obj, fmt):
return date_obj.strftime(fmt)
Формат %d.%m.%Y
(день.месяц.год) у нас используется почти везде. Вместо того чтобы передавать его в каждом вызове, можно прописать:
from functools import partial
format_russian = partial(format_date, fmt="%d.%m.%Y")
today = datetime(2025, 8, 12)
print(format_russian(today))
Результат:
12.08.2025
Здесь мы фиксируем второй аргумент (fmt
), а дату передаём уже при вызове.
itertools — набор инструментов для работы с итераторами
Модуль itertools
в стандартной библиотеке Python — это набор функций, которые помогают удобно работать с последовательностями и генераторами. Он пригодится, когда нужно обрабатывать данные без копирования и хранения в памяти.
Рассмотрим три часто используемые функции.
chain
— объединение последовательностей
Вместо того чтобы соединять списки через + или вручную перебирать несколько коллекций, можно объединить их в один поток данных.
Пример. Допустим, у нас есть две очереди заказов из разных филиалов и мы хотим обработать их одной функцией, не создавая отдельный список.
from itertools import chain
orders_moscow = ["Заказ 101", "Заказ 102"]
orders_spb = ["Заказ 201", "Заказ 202"]
for order in chain(orders_moscow, orders_spb):
print(f"Обрабатываем {order}")
Результат:
Обрабатываем Заказ 101
Обрабатываем Заказ 102
Обрабатываем Заказ 201
Обрабатываем Заказ 202
Так мы перебираем все заказы, как будто они лежат в одной очереди.
groupby
— группировка данных
groupby
группирует соседние элементы с одинаковым ключом.
Важный момент: она ищет совпадения не по всей коллекции, а только те, что идут подряд. Поэтому перед использованием почти всегда нужно отсортировать данные по ключу группировки.
Пример. У нас есть выгрузка заказов с датами, и мы хотим сгруппировать их по дню, чтобы, например, сформировать отчёт по продажам.
from itertools import groupby
from operator import itemgetter
orders = [
{"date": "2025-08-10", "order_id": 1, "amount": 1200},
{"date": "2025-08-10", "order_id": 2, "amount": 800},
{"date": "2025-08-11", "order_id": 3, "amount": 1500},
{"date": "2025-08-11", "order_id": 4, "amount": 500},
{"date": "2025-08-12", "order_id": 5, "amount": 2000},
]
# groupby требует, чтобы данные были отсортированы по ключу группировки
orders.sort(key=itemgetter("date"))
for date, group in groupby(orders, key=itemgetter("date")):
total_amount = sum(order["amount"] for order in group)
print(f"{date}: всего заказов на {total_amount} руб.")
Результат:
2025-08-10: всего заказов на 2000 руб.
2025-08-11: всего заказов на 2000 руб.
2025-08-12: всего заказов на 2000 руб.
Если не отсортировать, группы будут разбиты по первому вхождению категории.
islice
— «срез» на итераторах
islice
в itertools
делает срез по итерируемому объекту, но без предварительного преобразования его в список. Это важно, если источник данных большой или бесконечный: элементы будут извлекаться только по мере необходимости, а не целиком в память.
Пример. Представим, что у нас есть лог-файл на несколько гигабайт и нам нужно быстро посмотреть первые 5 строк, не загружая весь файл.
from itertools import islice
with open("big_log.txt", encoding="utf-8") as f:
for line in islice(f, 5):
print(line.strip())
Результат (просто для примера):
[2025-08-10 12:01] INFO: старт системы
[2025-08-10 12:02] WARNING: низкий заряд батареи
[2025-08-10 12:03] ERROR: модуль датчика недоступен
[2025-08-10 12:04] INFO: перезапуск
[2025-08-10 12:05] ERROR: превышен лимит памяти
Несколько дополнительных примеров
accumulate
— накопление промежуточных значений
accumulate
из itertools
считает промежуточные итоги. По умолчанию он суммирует, но можно передать любую другую функцию (например, умножение или поиск максимума).
Пример. У нас есть список продаж за каждый день. Если просто вывести этот список, мы увидим только дневные суммы. Но часто в отчётах нужно показать, сколько всего накопилось с начала периода к каждой дате.
accumulate
делает это автоматически:
from itertools import accumulate
sales = [1000, 2000, 1500, 3000]
cumulative = list(accumulate(sales))
print(cumulative)
Результат:
[1000, 3000, 4500, 7500]
В первой позиции — сумма за первый день, во второй — первый + второй день, в третьей — сумма за первые три дня и так далее. Эту информацию часто используют для построения графиков или анализа динамики продаж.
takewhile
— отбор до первого несоответствия условию
takewhile
идёт по последовательности и возвращает элементы, пока условие истинно. Как только встречается первый элемент, который не проходит проверку, работа останавливается.
Пример. У нас есть статистика по просмотрам статьи за каждый час, и мы хотим построить график, показывающий, как росла суммарная аудитория в течение дня.
from itertools import accumulate
hourly_views = [120, 340, 290, 410, 560]
total_views = list(accumulate(hourly_views))
print(total_views)
Результат:
[120, 460, 750, 1160, 1720]
Мы видим, что к пятому часу статья набрала 1 720 просмотров.
zip_longest
— объединение с заполнением пропущенных элементов
Обычный zip
останавливается на самом коротком списке. zip_longest
из itertools
проходит до конца самого длинного и подставляет недостающие значения (по умолчанию None
).
Пример. Допустим, есть список авторов и список их последних статей. У кого-то публикации уже есть, а у кого-то пока нет. Если воспользоваться обычным zip
, то авторы без статьи вообще не попадут в результат.
Поэтому используем zip_longest
:
from itertools import zip_longest
authors = ["Анна", "Игорь", "Марина", "Дмитрий"]
articles = ["Python и данные", "Асинхронка в реальной жизни"]
for author, article in zip_longest(authors, articles, fillvalue="— нет статьи —"):
print(f"{author}: {article}")
Результат:
Анна: Python и данные
Игорь: Асинхронка в реальной жизни
Марина: — нет статьи —
Дмитрий: — нет статьи —
Этот метод можно использовать для сверок, отчётов и любых случаев, когда важно, чтобы в таблице были все участники, даже если данных для них пока нет.
Мы разобрали только несколько функций itertools
, полный список можно изучить в документации.
Резюмируем
Многие из этих приёмов могут показаться мелочами, но именно они постепенно формируют стиль работы — когда код становится короче, чище и понятнее. Такие конструкции необязательно запоминать сразу. Достаточно знать, что они есть, и потихоньку добавлять в свой инструментарий.
Чтобы расти, нужно выйти из привычной зоны и сделать шаг к переменам. Можно изучить новое, начав с бесплатных занятий:
открытой встречи «Безопасность промышленных систем управления: как повысить устойчивость к кибератакам»;
вводного курса магистратуры «Инженерия данных»;
записи занятия «Поговорим об эмоциях» совместно с благотворительным фондом «Антон тут рядом»;
вводного курса бакалавриата «Программные системы и автоматизация процессов разработки».
Или можно стать востребованным сотрудником и открыть открыть бóльшие перспективы в карьере с профессиональным обучением:
на курсе «Python: от кода к стартапу» с МФТИ;
в онлайн-магистратуре «Инженерия данных» с НИУ ВШЭ;
на курсе «Нейросети для финансов и инвестиций»;
в онлайн-бакалавриате «Программные системы и автоматизация процессов разработки» с НИУ ВШЭ.
Femistoklov
У вашего кота разное кол-во пальцев на передних лапах.