Источник: Nuances of Programming
Python — это язык программирования общего назначения, который широко используется в таких областях, как научные вычисления, искусственный интеллект, веб-разработки, финансовое моделирование и многих других. Одна из главных причин популярности этого языка заключается в его гибкости, предполагающей возможность множественных решений для различных операций.
Однако в большинстве случаев из всего доступного разнообразия опытные программисты Python предпочитают использовать только одно возможное и зачастую нерациональное решение. Вот поэтому в данной статье мне бы хотелось рассмотреть 10 оптимальных решений, которые вы можете использовать для рефакторинга кода на Python.
1. Представление списков, словарей и множеств
В Python существуют 3 общеизвестных изменяемых контейнера: списки, словари и множества. Если мы начинаем с итерируемого объекта, то возможен вариант с применением цикла for для его перебора и создания на его основе нового списка. Однако в этом случае оптимальное использовать представление списков со следующим синтаксисом: [expression for x in iterable if any_condition]. Обратите внимание, что определение условия является необязательной частью. Рассмотрим представление списков.
Помимо списков можно также использовать представление словарей и множеств, синтаксис которых выглядит следующим образом: для словарей — {key_expr: val_expr for item in iterable}, для множеств — {expr for item in iterable}. В следующем коде представлены примеры их применения.
2. F-строки
Строки являются стандартным базисным типом данных, который используется практически во всех наших проектах. В большинстве случаев для отображения данных строки требуется форматировать их особым образом. Это можно сделать при помощи метода в стиле C, подразумевающего использование символа % или метода format.
Однако в последних релизах Python был представлен новый компактный и хорошо читаемый метод форматирования строк. Он известен как f-строки, что значит форматированные строковые литералы. Проведем сравнение подходов.
Конечно, данный пример только отображает самый основной случай использования f-строк, которые в действительности реализуют почти все способы форматирования, поддерживаемые методами в стиле C или format. Вы можете продолжить знакомство с ними, обратившись к официальной документации или к моей статье на данную тему.
3. Множественное присваивание и распаковка кортежа
Работая с переменными, мы обычно определяем по одной из них в каждой строке. Однако при объявлении множественных переменных можно сделать это и в одной. Во избежании недопонимания следует отметить, что данный способ касается объявления семантически однородных переменных. Если же они служат разным целям, то я бы не советовал его использовать. Рассмотрим следующий рефакторинг:
По своей внутренней логике множественные присваивания включают создание кортежа на правой стороне и его распаковку на левой. Указанный ниже код демонстрирует процесс распаковки кортежа. Как видно из примера, он выглядит как множественное присваивание, так как в его основе лежит тот же самый механизм.
4. Распаковка методом Catch-All
В предыдущем разделе мы рассмотрели процесс распаковки кортежа самым основным способом, в котором число переменных соответствует числу определяемых элементов в объекте. Но в случае с кортежем, содержащим множественные элементы, иногда может потребоваться распаковка при помощи метода catch-all. Точнее говоря, все элементы, напрямую не обозначенные переменными, будут подхвачены переменной с символом * в качестве префикса. Есть и другой способ достичь того же результата, но он уже не такой оптимальный. Речь идет о методе среза (slice) для представления некоторой части последовательности, но он сопряжен с ошибками в случае пропуска правильных индексов.
Распаковка методом catch-all:
Как вы могли заметить, значения middle_numbers0 и middle_numbers1 не равноценны. Причина этого состоит в том, что распаковка методом catch-all (с использованием *) генерирует объект списка по умолчанию. Следовательно, для приведения итоговых распакованных данных к одному типу можно использовать конструктор tuple(), как показано в примере:
>>> # Конвертируем распакованный список в кортеж
>>> print(middle_numbers0 == tuple(middle_numbers1))
True
5. Выражение присваивания
Выражение присваивания больше известно как “моржовое” выражение с использованием “моржового” оператора (walrus operator) :=, вид которого напоминает одноименное животное с парой глаз и бивнями. Как следует из названия, данное выражение позволяет присваивать значение переменной, и в то же время может быть использовано в качестве выражения, к примеру в инструкции условия if. Поскольку определение звучит несколько запутанно, обратимся к примеру использования в следующем фрагменте кода:
Как показано выше, когда мы не используем выражение присваивания, нам приходится сначала получить номер счета и применить его к операции по снятию денег, что сопровождается созданием дублированного кода. В отличие от этого, мы можем исключить одну строку кода с помощью выражения присваивания, которое вызывает функцию и присваивает возвращаемое значение новой переменной, которая вычисляется параллельно.
Кто-то может сказать, что сохранение одной строки кода не играет большой роли, но мы тем самым проясняем свое намерение относительно переменной account_number, которая действует только в области оператора if. Если у вас есть опыт программирования на Swift, то использование выражения присваивания в операторе if во многом идентично методу необязательной привязки, представленному ниже. По сути, временная переменная accountNumber используется в последующей области видимости, только когда она действительна. Таким образом, с выражением присваивания стоит познакомиться, и со временем вы обнаружите, что ваш код стал более читаемым.
6. Итерация с enumerate
Практически в каждом проекте мы неизбежно вынуждаем нашу программу повторять отдельные операции со всеми элементами списка, кортежа или других контейнеров. Эти повторяющиеся операции можно выполнить с помощью цикла for. Обычно используется основная форма: for item in iterable. Однако для итерации, если требуется вести счет циклов, лучше использовать функцию enumerate, которая создаст счетчик. Более того, у нас есть возможность установить число, с которого он будет начинать отсчет.
Итерация с enumerate:
7. Объединение итерируемых объектов с помощью zip/zip_longest
Предположим, что мы начинаем с двух итерируемых объектов, и нам нужно объединить их c каждой соответствующей парой элементов. По обыкновению, мы можем использовать метод индексации, извлекая элементы из каждого итерируемого объекта для их объединения с целью создания словаря. Однако в Python есть встроенная функция zip, выполняющая как раз то, что нам нужно. По сути, она принимает несколько итерируемых объектов и создает из них один, длина которого соответствует самому короткому из числа переданных. Рассмотрим следующий пример использования функции zip с итерируемыми объектами:
Функция zip создает zip — объект-итератор, чьи элементы являются кортежами, содержащими элементы из полученных итерируемых объектов. Следует отметить, что по умолчанию эта функция останавливается, достигнув конца любой последовательности. А вот функция zip_longest, наоборот, будет использовать самый длинный итерируемый объект.
Вышеописанный оптимальный способ использует преимущество конструктора dict, способного применить итерируемый объект для создания объекта dict. Помимо уже рассмотренного примера, zip-объект может быть напрямую использован в итерации:
8. Конкатенация итерируемых объектов
В приведенном выше примере мы использовали функцию zip для объединения итерируемых объектов по элементам. А что же делать, если согласно конкретной бизнес-задаче мы должны их конкатенировать (объединить в цепочку)? Допустим, требуется перебрать два итерируемых объекта с элементами одной категории для одной и той же операции. Для этой цели можно использовать функцию chain. Обратимся к следующему примеру сцепления итерируемых объектов:
Как мы видим, отличный от оптимального способ требует создания дополнительных списков, что чревато излишними затратами памяти. В то же время функция chainсоздает итерируемый объект из уже имеющихся последовательностей. Кроме того, ей свойственна гибкость, поэтому она может принимать любые виды итерируемых объектов в Python, включая словари, множества, списки, zip-объекты, map-объекты (при помощи функции map) и многие другие.
9. Тернарное выражение
Мы можем присвоить переменной разные значения с учетом заданных условий. Для этой цели существует оператор if, устанавливающий условие и определяющий значение, которое необходимо присвоить. Обычно данная операция занимает несколько строк кода. Однако мы можем воспользоваться тернарным выражением для решения нашей задачи, используя одну строку со следующим основным синтаксисом: var = true_value if condition else false_value. Рассмотрим соответствующий пример использования:
10. Использование генераторов
Идея генераторов может быть незнакома новичкам в Python, поскольку во многих других языках программирования данный метод не столь широко распространен. Этот хитрый прием позволяет вам работать с потоком данныхбез необходимости его установки в начальную точку. Вместо этого генератор выдает следующее значение по запросу, что способствует эффективному использованию памяти.
Рассмотрим следующий простой пример. Предположим, перед нами стоит задача обработать большие объемы данных в файле. Теоретически мы можем считать весь файл в список и обработать в нем каждую строку данных. Однако если файл окажется очень большим, то велика вероятность, что ваш компьютер быстро исчерпает свой ресурс памяти. Поэтому следует воспользоваться более оптимальным и лучшим решением, сделав из файла генератор, возвращающий каждый раз только одну строку кода по требованию.
Заключение
В Python существует гораздо больше оптимальных вариантов выполнения различных операций, и в данной статье отражены только некоторые из них — в основным это те эффективные приемы, которые я использую в своих повседневных проектах. Надеюсь, они вам также помогут в процессе написания кода на Python. Рекомендую вам проводить рефакторинг, намеренно используя характерные для данного языка методы, которые обычно отличаются большей производительностью. Систематически осуществляя переработку кода, вы постепенно улучшите свои навыки программирования.
Благодарю за внимание! Да будет программирование в радость!
Читайте также:
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Yong Cui, Ph.D.: 10 Idiomatic Ways to Refactor Your Python Code