Как писать выразительные циклы на Python разбираемся на примерах

Синтаксис оператора цикла в Python одновременно и прост, и не традиционен. По сравнению с C-подобными языками циклы в Python лишены общей трехступенчатой структуры for (init, condition, increment) . В большинстве случаев достаточно for <item> in <iterable> . Цикл while <condition> используется реже.

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

У всякого языка программирования есть более или менее удачные подходы решения одних и тех же задач. Представим, вы спросили кого-то, кто только изучает Python: «Как получить текущий индекс при обходе списка?». Ответ может быть следующим:

Хотя приведенный цикл верен, это решение не в стиле Python. Разработчик с трехлетним опытом предложит такой код:

enumerate() – это встроенная функция Python, которая принимает итерируемый объект в качестве параметра, а затем возвращает новый объект – генератор кортежей вида (текущий индекс, текущий элемент) . Это лучший способ для данного случая: используется более интуитивно понятный код, к тому же он и продуктивнее.

Копнем приведенный пример поглубже. Цикл for состоит из структуры for <item> in <iterable> . Левая половина присваивает значение переменной item . В правой половине находится итерируемый объект, в качестве которого мы использовали функцию enumerate() . Это подводит нас к первой рекомендации.

Использование декоратора для обработки итерируемых объектов может по-разному влиять на код цикла. Прекрасный пример – встроенный модуль itertools. Это набор инструментальных функций, содержащий множество полезных итерируемых объектов. О самом модуле мы писали в статье «Итерируем правильно». В этом материале мы рассмотрим примеры использования функций модуля в практических задачах.

Используйте product() для компактности

Все мы знаем, что «плоский» код лучше вложенного. Но иногда приходится писать многоуровневые вложенные циклы:

Чтобы оптимизировать такие циклы, выполняющие обход объектов, можно использовать функцию product() . Функция принимает несколько итерируемых объектов и создает их декартово произведение.

По сравнению с предыдущим кодом, цикл, использующий product() , нуждается только в одном уровне вложенности. Код становится более лаконичным.

Используйте islice(), чтобы обрабатывать только часть объектов цикла

Рассмотрим файл с заголовками постов Reddit следующего вида:

Между каждой парой заголовков, присутствует разделитель — , а нам нужны только заголовки. Основываясь на том, что мы уже знаем о функции enumerate() , можно отфильтровать разделители по нечетным номерам:

Однако использование функции islice() из библиотеки itertools позволяет изменить сам итерируемый объект и упростить код. Функция islice (seq, start, end, step) имеет почти те же параметры, что оператор среза (list[start:stop:step]) . Установим значение параметра step в 2 (по умолчанию 1).

Используйте takewhile вместо break

Иногда необходимо определить, надо ли закончить цикл в самом его начале. Например:

Для раннего прерывания циклов можно использовать функцию takewhile() . Функция takewhile (predicate, iterable) проходит по всем объектам из iterable и вызывает функцию предиката, передав текущий объект в качестве аргумента, и проверяет возвращаемый результат.

Если функция предиката возвращает True , генерируется объект и цикл продолжается, в обратном случае цикл прерывается.

В itertools есть и другие интересные функции, которые можно использовать вместе с циклами:

  • функция chain() позволяет сделать плоскими двухуровневые вложенные циклы;
  • функция zip_longest() может организовать цикл сразу по нескольким объектам.

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

Используйте генераторы для написания своего декоратора

Кроме функций itertools, мы можем использовать генераторы в сочетании с декораторами . Возьмем простой пример.

Для фильтрации всех нечетных чисел в теле цикла здесь используется дополнительный оператор if . Но если конструкция встречается часто и мы хотим упростить тело цикла, можно определить функцию-генератор для фильтрации четных чисел:

После декорирования переменной numbers функцией even_only , функции sum_even_only_v2 не приходится фильтровать четные номера – остается только просуммировать.

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

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

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

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

Как справиться со сложными циклами

Каковы недостатки такого кода? В один прекрасный день выяснилось, что некоторые пользователи не спят после полуночи по выходным и сидят на сайте. Появилось новое требование: «отправить уведомление юзерам, которые вошли в систему между 3:00 и 5:00 в выходные дни за последние 30 дней».

Легко понять, что новая проверка очень похожа на описанную перед кодом. Но, если посмотреть на тело цикла, станет ясно, что код не может быть использован повторно. Внутри цикла слишком тесно связаны друг с другом разные логики: «выбрать время» и «разослать наградные баллы».

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

Разделение тела цикла с помощью функции-генератора

Чтобы отвязать выбор времени от цикла, определим функцию-генератор gen_weekend_ts_ranges() , которая используется для генерации меток времени UNIX:

С помощью новой функции-генератора старую задачу «разослать наградные баллы» и новую задачу «разослать уведомления» можно реализовать повторным использованием одного и того же цикла:

В данной статье мы сначала кратко пробежались по определению «правильного» кода циклов. Затем возникло первое предложение: использовать функции-декораторы для улучшения производительности. В завершение на примере бизнес-сценария описали важность «дробления» кода в цикле в зависимости от исполняемых этим кодом задач.

Кратко о некоторых моментах:

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

Библиотека программиста надеется, что найдете эти подходы такими же полезными, как и мы. Удачи в обучении!

Вы пропустили

AEGIS Algorithms Android Angular Apache Airflow Apache Druid Apache Flink Apache Spark API API Canvas AppSec Architecture Artificial Intelligence Astro Authentication Authorization AutoGPT AWS AWS Aurora AWS Boto3 AWS EC2 AWS Lambda Azure Babylon.js Backend bash Beautiful Soup Bento UI Big Data Binary Tree Browser API Bun Career Cassandra Charts ChatGPT Chrome Extension Clean Code CLI ClickHouse Coding Codux Combine Compose Computer Context Fusion Copilot Cosmo Route CProgramming cron Cryptography CSS CTF Cypress DALL-E Data Analysis Data science Database dbt dbt Cloud deno Design Design Patterns Detekt Development Distributed Systems Django Docker Docker Hub Drizzle DRY DuckDB Express FastAPI Flask Flutter For Beginners Front End Development Game Development GCN GCP Geospatial Git GitHub Actions GitHub Pages Gitlab GMS GoFr Golang Google Google Sheets Google Wire GPT-3 GPT3 Gradio Gradle Grafana Graphic Design GraphQL gRPC Guidance HMS Hotwire HTML Huawei HuggingFace IndexedDB InfoSec Interview iOS Jackknife Java JavaScript Jetpack Compose JSON Kafka Kotlin Kubernetes LangChain Laravel Linux LlaMA LLM localStorage Logging Machine Learning Magento Math Mermaid Micro Frontends Mobile Mobile App Development mondayDB MongoDB Mongoose MySQL Naming NestJS NET NetMock Networks NextJS NLP Node.js Nodejs NoSQL NPM OOP OpenAI OTP Pandas PDF PHP Playwright Plotly Polars PostgreSQL Prefect Productivity Programming Prometheus Puppeteer Pushover Python Pytorch Quarkus Rabbitmq RAG Ramda Raspberry Pi React React Native Reactor Redis REST API Revolut Riverpod RProgramming Ruby Ruby on Rails Rust Scalene SCDB ScyllaDB Selenium Servers Sklearn SLO SnowFlake Snowkase Software Architecture Software Development Solara Solid Spring Boot SQL SQLite Streamlit SudoLang Supabase Swift SwiftUI Tailwind CSS Taipy Terraform Testing Transformers TURN TypeScript Ubuntu UI Design Unix UX UX Design Vim Vite VSCode Vue Web Architecture Web Components Web Development Web Frameworks Web Scraping Web-разработка Webassembly Websocket Whisper Widgets WordPress YAML YouTube Zed Наука о данных Разное Тренды

Современный подход к разработке с использованием Next.js